0%

[NCTF2019]phar matches everything

[NCTF2019]phar matches everything

BUU继续重拳出击,总觉得刷题的意义正在逐渐消失,不想这么单纯的刷体了,感觉只能让我成为一个赛棍。应该学点什么,以后水一点的题就不记了,起码要学到一点点东西再说

题解

buu上给出了源码,两个关键文件,一个文件上传,一个反序列化,文件上传限制了后缀和文件名,反序列化处有一个经典用phar来触发的操作,反序列化的结果就是一次curl

反序列化的操作非常简单不在赘述,稍微要考虑一下的就是文件上传。检测后缀对phar来说并没有什么问题,因为phar协议指定去读一个文件也不管他是什么后缀。唯一要过的点就是那个imagesize函数的检测,也很简单,用那个经典的#define width 1就能过,就放在那个压缩进phar的文件里就行,要另起一行才能正确识别

传上去phar触发反序列化,从buu给的源码里已经看到了是要打内网一个FPM了,估计是这个FPM还设定了监听0.0.0.0:9000,所以要先摸出来这个机子所在的ip段。读/proc/net/arp可以得到当前机子的ip,网段内加几下就能找到那台FPM主机的ip

使用经典攻击FPM脚本生成流量,用gopher一把梭。
这里的攻击思路之前都没注意,还是这回现学的,之前是用FPM去bypass disable function,那是已经拥有了对机器的shell,所以是传一个.so上去,用PHP_VALUE修改PHP配置,加载.so。然后直接用这个.so执行命令来绕过disable function

这里我没有目标机器的shell,就得先攻击FPM拿到一个shell。同样是修改PHP_VALUE,添加一个auto_prepend_file=php://input,再在PHP_ADMIN_VALUE中开启allow_url_include=On,就可以直接include POST的数据,在没有目标机器任何权限的情况下变出来一个shell。
拿到shell之后依旧是disable funcion+open basedir拉满,不过这里由于可以控制PHP_ADMIN_VALUE,可以自行修改disable function和open basedir,不过由于disable func只能增不能减,就算修改了也只是增加过滤,因此直接加一条open_basedir=none直接读取根目录flag

curl与二次编码

这里有一个比较奇怪的思考部分。往常用gopher打内网的时候,提交的参数一般是GET或者POST,所以通常我们要二次编码,使得数据在被服务端接收到解码一次之后还是被编码的数据。然而这里因为payload是随文件上传到远端的,所以只需要进行一次编码,也很合理。但是出现了一点小小的意外。PHP的urlencode函数会将空格编码为+而不是%20,这并没有问题,因为正常的服务端会把+解析回空格。而这次进行攻击时,我发现FPM收到的内容仍然是加号,而剩下的URL编码却正确的解码了。似乎,解码的情况有些不对?是FPM对收到的内容解码但不能正确解码加号?然后我直接nc一个端口,让curl给我发送一段一次编码过的gopher请求,收到的结果确是已经解码的?
我之前一直认为二次编码的意义在于服务端收到时解一次码,服务端ssrf请求发出去之后,被攻击的服务端收到再解一次码,这样子进行二次的编码。但似乎这里情况有些不一样,这个请求在发送出来之前就已经被解码了一次,且解码的时候只能正确的识别%xx的形式,不能识别加号

经过反复的实验,我发现似乎问题出在协议上,curl在发送http协议时,不会对url进行解码,而是等待接收消息的服务端自行解码,而发送gopher协议时,会在发送时就对url进行解码,以防接收端不会进行解码(编码是必须的,因为curl如果收到了回车之类的特殊字符是没法正常跑起来的)。这样一来,无论是怎么样,ssrf都要进行二次编码的操作应该是正确的,但换一步想,如果要用gopher打内网http POST的话,是不是得三重编码?