前言
今天做了一道 RCE 的题目 , 里面有一个 preg_replace()
过滤
其中 , [^\W]
包含所有的数字 , 字母 以及 下划线 , 而 (?R)
则是 DEELX 正则表达式扩展语法 中的递归表达式 , 表示递归引用整个模式 .
这代表着我们可以输入递归的字符串( 比如 abc();
, abc(def());
, abc(def(ghi()));
, ... ) , 但若我们输入一个带参数的字符串 , 正则匹配将会失败 . 简单的来说 , 就是我们可以递归输入无参函数 , 但不能输入有参函数 .
想了很久都没有什么绕过思路 , 没有参数怎么传输我要执行的代码? 于是去 Google , 然后找到了 一叶飘零师傅的 PHP Parametric Function RCE 这篇文章 .
过去没有接触过 PHP 无参数实现RCE 的题目 , 这里学习记录一下 , 供以后参考~
getenv()
PHP 中有众多的超全局变量
注意 $_ENV
这个超全局变量 , 我们可以使用 getenv()
函数来获取一个包含当前环境变量的数组 .
可以在 phpinfo() 中查看能获取到的环境变量
可以看到 , 很多时候服务器的环境变量不止一个 , 多个环境变量放在一个数组中 , 那么如何从这个数组中取出一个指定的值呢 ?
array_rand()
array_rand() 方法用于从一个数组中随机( 伪随机 )取出一个或者多个单元 , 并返回随机条目的一个或者多个键
但这样只能获得数组的键 , 可以通过 array_flip() 函数获取数组的值
array_flip()
该函数用于交换数组中的值和键 , 返回一个 键 与 值 相互交换后的数组
getenv()
, array_rand()
, array_flip()
这三个函数配套使用 , 可以爆破出数组中需要的内容 . 比如下面就拿到了 " PATH " 变量对应的值 .
但在实际环境中 , $_ENV
都是空数组,主要是为了安全起见,不让它获取操作系统信息 !
getallheaders()
getenv()
可以获取到所有环境变量的列表 , 但我们有更好利用的点 ,比如获取到 HTTP Header
的信息 .
在 Apache 环境中 , 可以使用 getallheaders()
函数来返回 HTTP Header 信息
这里我们可以自定义一些 HTTP Header
可以使用 end()
函数将数组的最后一项提取出来
这样我们就拿到了自定义的 HTTP Header 字段, 并且该字段可以被我们自由使用 , 比如通过 eval()
函数执行 .
也可以利用该函数完成 RCE ~
get_defined_vars()
上述的 getallheaders()
函数仅能在 Apache 环境下完成 RCE , 那么如果目标中间件不是 Apache( 比如说 Nginx ) , 该如何完成 RCE 呢?
get_defined_vars()
函数返回由所有已定义变量所组成的多维数组 . 这些变量包括环境变量 , 服务器变量 , 用户定义的变量 .
该函数会回显全局变量
$_GET
因此我们可以利用全局变量( 比如 $_GET
) 来实现 RCE ,但这里 $_GET
不在数组最后一位 , 因此无法用 end()
函数 , 但我们还可以使用 current()
函数获取数组的第一个单元 .
和之前的思路一样 , 在 URL 中添加想要执行的参数即可
由于 Payload 在 URL 中 , 所以别忘了对其中某些特殊字符进行编码后再提交
但大多数网站都会对 $_GPC
( $_GET
, $_POST
, $_COOKIE
) 做全局过滤 , 因此大部分时候我们要从 $_FILES
入手 .
$_FILES
构造如下 Python 脚本
我们将文件名作为将要执行的 Payload , 这样就不需要再使用 array_flip()
函数了 . 查看输出 , 发现我们的 Payload 位于 $_FILES
变量中 .
接下来我们的任务就是从中提取出写好的 Payload 了 , 比如这里可以使用 current(current(end()))
这组函数来拿到指令
然后通过 eval()
函数执行 , 就可以拿到 RCE 了~
$_COOKIE
$_COOKIE
的利用方式与 $_FILE
非常类似 , 我们常使用 session_id()
函数来设置或获取当前会话的 SessionID . 这里 SessionID 是可以利用的
虽然 SessionID 仅支持字母和数字 , 不支持一些符号( 比如 " ; " 或者 " ' " ) . 但这都不是事 , 可以利用编码( 比如 HEX )来绕过这个限制
构造如下 Python 脚本
这里发现 $_COOKIE
字段不在数组的开头或者末尾 , 因此 current()
函数 和 end()
函数是不能用了 , 我去 PHP 官网上看了下 PHP数组函数 , 找到半天也没有找到适合的函数 .
其实方法还是有的 , 比如你可以参考 boring_code 这道题 , 其中给出了很多思路 , 这里就不深入下去了
但大佬给出了另外一种方式 : session_start()
函数
session_start() 函数常常用于创建新会话 . 但如果通过 GET , POST , 或者 COOKIE 提交了 Session ID, 则会重用现有 Session . 该函数返回值为 True / False
在重用了现有会话后 , 可以使用 session_id()
来获取该会话的Session_ID
在获取了 Session_id
后 , 仅需要对其进行 hex2bin
解码 , 就可以拿到之前构造的 Payload 了
通过 eval()
即可拿到 RCE !
Read Directly
可以尝试直接读取本地文件 , 其中最大的问题就是如何切换目录来遍历目录 .
当然 , 使用该方法读取文件的局限性很大 , 比如只能从当前目录递归遍历到根目录 , 而无法读取其他的目录的内容 . 基本上用处不太大~
总结
这种无参数完成 RCE 的方式还是蛮有意思的 , 需要在多研究下~
并且这里可以做一个总结
getchwd() : 返回当前工作目录
scandir() : 返回指定目录中的文件和目录的数组
dirname() : 返回路径中的目录部分
chdir() : 改变当前的目录
readfile() : 输出一个文件
current() : 返回数组中的当前单元, 默认取第一个值
pos() : current() 的别名
next() : 将内部指针指向数组中的下一个元素,并输出
end() : 将内部指针指向数组中的最后一个元素,并输出
array_rand() : 返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组
array_flip() : 用于反转/交换数组中所有的键名以及它们关联的键值
chr() : 从指定的 ASCII 值返回字符
hex2bin : 转换十六进制字符串为二进制字符串
getenv() : 获取一个环境变量的值(在7.1之后可以不给予参数)