Insecure Deserialization trong PHP


Insecure Deserialization: Là một lỗ hổng bảo mật cho phép kẻ tấn công (attacker) có thể chỉnh sửa, thay đổi kết cấu của các đối tượng (object) bằng cách tác động vào các untrust data khi tiến hành khi thực hiện Deserialize của ứng dụng.

- Serialize: Khi dữ liệu muốn được lưu trữ, hay muốn truyền tải qua mạng hay một ứng dụng khác, người ta sẽ serialize dữ liệu tức là chuyển đổi dữ liệu gốc (orginal data) thành một mảng byte (byte stream). Điều này giúp cho việc chuyển tiếp dữ liệu đảm bảo được tính toàn vẹn, tối ưu hệ thống, tăng đáng kể tốc độ tính toán,...

- Deserialize: Là quá trình ngược của Serialize, dùng để biến đổi mảng byte (byte stream) lại thành giữ liệu gốc dựa vào byte stream đã được cung cấp.

Ý tưởng của việc tấn công này là sẽ cung cấp byte steam fake, khi ứng dụng des -> nó bị tấn công

--------------------------------------------------------------

Lười sửa và viết, nào rảnh sửa lại

Ví dụ về 1 bài insecure deserialize bằng PHP

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

Dễ thấy untrust data được lấy qua tham số str và sẽ được unserialize. Nhưng trước tiên phải thỏa điều kiện is_valid nhỉ =))) Cũng không cần thiết lắm nhỉ vì cơ bản payload sẽ có đủ những cái này rồi :Vv

Nói sơ qua về class của PHP nó sẽ có một số magic method cần lưu ý:

__construct(): phương thức này dùng để tự động gọi mỗi khi một đối tượng (object) được khởi tạo. Điều đó có nghĩa là khi tạo 1 object bạn có thể chèn các parameters vào

Lấy ví dụ

class x {
    public $a;
    public $b;

    public function __construct($a,$b){
        $this->a = $a;
        $this->b = $b;
    }
}
$name = new x("konchan",28);

__destruct(): phương thức này sẽ được kích hoạt khi bạn hủy nó bằng unset(), khi script kết thúc hoặc là khi không còn đối tượng tham chiếu nữa, php sẽ tự động tìm và kích hoạt để giải phóng bộ nhớ.

class x {
    public $a;
    public $b;

    public function __construct($a,$b){
        $this->a = $a;
        $this->b = $b;
    }
    public function __destruct(){
        echo "DIe";
    }
}
$name = new x("konchan",28);

__call(): Hàm này sẽ được gọi nếu có gắng gọi một phương thức không tồn tại hoặc không thể truy cập được trong một đối tượng (object).


class x {
    public $a;
    public $b;

    public function __construct($a,$b){
        $this->a = $a;
        $this->b = $b;
    }
    public function __destruct(){
        echo "DIe";
    }
    public function hacker(){
        echo "this_is_hacker";
    }
    public function __call($name,$argurment){
        echo "phương thức $name không tồn tại hoặc hiện không khả dụng";
    }
}
$name = new x("konchan",28);
$name->ok();

__callStatic(): được gọi khi hàm mà ta cố gắng gọi một hàm tĩnh nhưng ko được phép truy cập hay tồn tại của lớp (class) 

class MyClass {
    public static function __callStatic($name, $arguments) {
        echo "Bạn vừa gọi phương thức tĩnh '$name'\n";
    }
}

MyClass::methodDoesNotExist(); // Kích hoạt __callStatic()

__get(): dùng để đọc các thuộc tính không thể truy cập, bao gồm các protected và private

__set(): ghi dữ liệu vào các thuộc tính không thể truy cập bao gồm các protected và private

__isset(): kích hoạt khi gọi isset() hoặc empty() trên các thuộc tính không thể truy cập hoặc không tồn tại

__unset(): được gọi khi unset() được dùng trên các thuộc tính không thể truy cập hoặc không tồn tại

__sleep(): khi serialize được thực thi sẽ kiểm tra xem có __sleep() không, nếu tồn tại sẽ được thực thi đầu tiên trước bất kì serialization nào, 

__serialize()

__unserialize()

__toString()

__invoke()

__set_state()

__clone()

__debugInfo().


Đọc thêm về Insecure Deserialize trong Java tại đây 

https://sec.vnpt.vn/2020/02/co-gi-ben-trong-cac-gadgetchain/

Nhận xét

Bài đăng phổ biến từ blog này

CVE-2023–41425 but only RCE part