Long time no see!
Đã lâu rồi tôi không đăng gì hết, một phần vì công việc đang có nhiều biến động và phần khác thì những blog tôi muốn viết đa phần đang ở trạng thái draft.
Hôm nay, tôi sẽ viết về 0day mà bản thân đã tìm thấy trên wordpress plugin, liên quan đến lỗi PHP Object Injection.
I. Tổng quan
Nếu là anh em đã chơi reproduce 1 days, Ndays thì chắc hẳn sẽ thấy đa số các 0days ngày nay tìm thấy đều từ reproduce 1days, Ndays đi lên. Trong qua trình làm, chúng ta sẽ đặt những câu hỏi và tìm hiểu nguyên nhân, cách fix, patch và xem xét rằng lỗ hổng đã thực sự fix triệt để chưa. Ngữ cảnh của CVE này cũng thế.
Tinh thần blog này sẽ không hướng dẫn các bạn xây dựng và debug cũng như phân tích một CVE như thế nào (bởi vì nó có đầy trên mạng). Tôi chỉ phân tích con đường và các tiếp cận của mình để từ việc reproduce Ndays cho đến tìm ra 0day.
Chi tiết về 0day của tôi thì bạn có thể xem ở đây: https://patchstack.com/articles/critical-vulnerability-patched-in-givewp-plugin/
II. Cài đặt môi trường
Với wordpress, việc cài đặt môi trường khá đơn giản, đây là cách mình làm. Tôi dùng môi trường linux để triển khai, xây dựng và reproduce.
2.1 Cài đặt wordpress bằng docker-compose
|
|
Với cấu hình này chúng ta chỉ cần chạy docker-compose up -d
là xong
#tip1: Một điểm lưu ý trong cấu hình này là
/usr/local/etc/php/xdebug
dùng để cài đặt debug. Với tôi, đây là cách nhanh nhất và hay dùng để cài đặt debug cho wordpress#tip2: Để dễ dàng debug, hãy cấp full quyền trên project wordpress.
2.2 Debug
1. Nội dung file php.ini
- Sau khi triển khai thành công vào container của wordpress với folder
/usr/local/etc/php
và copy filephp.ini-production
thànhphp.ini
- Thêm nội dung sau vào cuôi file
php.ini
|
|
-
Trong trường hợp làm xong các bước sau mà không thành công, hãy thay đổi giá trị
xdebug.client_host
thàn giá trị IP
giá trị này dựa vào ip hiện tại của container
2. Rebuild xdebug.so file
- Tiếp theo, hãy clone
https://github.com/xdebug/xdebug
vào/usr/local/etc/php/xdebug
. Vào container và chạy file/.rebuild.sh
, bạn sẽ có được đường dẫn chứa file xdebug.so. Hãy di chuyển nó tới folder/usr/local/etc/php
3. Cấu hình trên Vscode
- Hình ảnh dưới đã nói lên tất cả. Lưu ý duy nhất là PathMapping phải trỏ đến đúng folder
4. Cài plugin
- Bước này khá đơn giản, chỉ có một vấn đề hay gặp đó là lỗi sau.
Lỗi này google search tí là ra ngay, cách tôi hay sửa là thêm nội dung sau và cuối file.htaccess
1 2 3 4
php_value upload_max_filesize 64M php_value post_max_size 64M php_value max_execution_time 300 php_value max_input_time 300
II. Từ reproduce CVE tới tìm 0 day.
Như đã đề cập ở tiêu đề, bài viết này sẽ không phân tích chi tiết từ source đến sink như những bài phân tích CVE trước đó, thay vào đó tôi sẽ cố truyền đạt những idea và phương pháp suy luận.
Với PHP Object Injection nói riêng và họ lỗi Insecure Deserializion nói chung thì đều có hai phần: từ source tới sink và Gadget chain.
Từ góc độ lập trình: những ứng dụng đã thiết kế để serialize và deserialize dữ liệu có nhận thức về lỗ hổng này, họ sẽ đảm bảo dữ liệu được xử lý trước khi đưa vào các hàm deserialize, đặc biệt các dữ liệu nhận trực tiếp từ người dùng.
Với góc độ của attacker, researcher: Đây là nơi để tìm kiếm lỗi:
- Tìm các nơi nhập dữ liệu trực tiếp và đưa vào hàm deserialize mà không bị kiểm tra
- Tìm lỗ hổng trong các bộ lọc, kiểm tra để vượt qua.
Tôi bắt đầu với CVE-2024-9634. Các bước để thực hiện reproduce CVE tôi đã đề cập nhiều ở CVE trước (Các bạn có thể xem thử).
Một số thông tin cơ bản như sau:
- Lỗ hổng xảy ra ở WordPress GiveWP Plugin
- Type bug: PHP Object Injection
- GiveWP – Donation Plugin and Fundraising Platform <= 3.16.3 – Unauthenticated PHP Object Injection to Remote Code Execution
Trong quá trình thu thập thông tin, tôi thấy có các CVE-2024-5932 và CVE-2024-8353 đã có trước đó, có blog kỹ thuật chi tiết.
Sau khi thực hiện các bước đọc hiểu về CVE-2024-9634, diff patch source vẫn không thấy hàm bị lỗi, tôi tiến hành đọc, phân tích các CVE cũ ở trên để xem vấn đề này có liên quan đến việc mình cần giải quyết không.
2.1 CVE-2024-5932
2.1.1. Nguyên nhân lỗi
Khi phân tích CVE này, ta nhận thấy:
- Thiết kế của ứng dụng hướng tới việc deserialize dữ liệu bằng PHP unserialize.
- Họ đã nhận thức được về nguy cơ tìm tàng của lỗ hổng này nên đã có cơ chế xử lý. Mục tiêu là đảm bảo các dữ liệu trước khi đưa vào hàm
unserialize()
đã được kiểm tra và xử lý. Hàmgive_donation_form_has_serialized_fields
có nhiệm vụ này, xác định xem những hàm trường dữ liệu nào chứa dữ liệu serialize. - Nguyên nhân của lỗi này xảy ra researcher phát hiện những hàm có thể truyền giá trị serialize mà không nằm trong whitelist của hàm
give_donation_form_has_serialized_fields
.
2.1.2. Cách fix
Cách fix sau đó là bổ sung các field bị thiếu vào whitelist.
Điều này có nghĩa, nếu bạn tìm được các field khác trong tương lai cũng có thể khai thác tiếp.
2.2 CVE-2024-8353
2.2.1. Nguyên nhân lỗi
Lỗ hổng được mô tả là bản bypass của CVE 2024-5932. Mục tiêu ở đây là bypass bộ lọc: hàm is_serialize()
Hãy chú ý đến hàm kiểm tra:
- Idea ở đây là bạn thêm dấu
\
vào trước payload của mình. Ví dụ:
|
|
Khi vào hàm is_serialize()
thì điều kiện ':' !== $data[1]
sẽ thỏa mãn, chương trình sẽ xem giá trị này không phải là chuỗi serialize.
Sau khi chương trình tiếp tục, trước khi giá trị này đi vào hàm unserialize()
sẽ được gọi qua hàm stripslashes_deep
để xóa đi các giá trị \
. Điều này vô tình giúp payload của chúng ta khôi phục về mã khai thác gốc, mã khai thác sẽ thực thi thành công.
2.2.2. Cách fix
Đây là cách họ fix
Question:
- Đã an toàn chưa?
- nếu ký tự đầu tiên truyền vào không phải là
\
thì có ký tự nào khác để bypass hàm is_serialized ko? - ⇒ Bản chất để bypass của is_serialized là gì? Dùng nó để tùy biến payload
- Câu trả lời là chưa, tôi có thể bypass với
\0
Đây là finding thứ nhất của tôi. Tôi có thể dùng kí tự \0
để bypass bản pacth này.
2.3 Finding 1:
Với hai CVE ở trên, tôi có thể đánh giá việc khai thác tìm hàm ngoài whitelist và bypass bộ lọc validation đều có thể. Do đó, tôi bắt đầu quay lại với vấn đề ban đầu của mình.
Như đã đề cập trước đó, finding 1 của tôi dùng ký tự \0
để bypass việc hàm xóa đi \
. Mã khai thác này cho phép tôi có thể khai thác thành công đến bản 3.17.0, trong khi CVE-2024-8353 thông báo chỉ khai thác thành công trước bản 3.16.2
Tôi đã report nhưng failed vì tại thời điểm report, bản mới nhất đã ra và vô tình fix luôn cách khai thác này :(
2.4 Finding 2:
Tôi tiếp tục không bỏ cuộc và quay về với vấn đề ban đầu, tôi đã thành công reproduce CVE-2024-9634. Nguyên nhân lỗi này gần giống như CVE-2024-5932. Điểm khác ở đây là với tham số give_company_name
thì payload được trực tiếp vào hàm unserialize()
mà không qua cơ chế xác thực nào. Khá easy!
Tuy nhiên mục tiêu bây giờ của tôi đã là bypass cơ chế xác thực ở hàm mới nhất.
Hãy xem cơ chế này:
Hãy giành thời gian để phân tích và suy nghĩ các hướng khai thác này nhé!!!!
Có 2 cách bypass ở trong cơ chế này:
-
Cách dễ nghĩ nhất chính là bypass hàm
selft:isSerialized
bằng cách tận dụng cơ chế xóa đi các ký tự\
để bypass regex (cách này tôi nghĩ ra và đã thành công)1
\O:5:%22TCPDF%22:2:{s:12:%22%00*%00imagekeys%22;a\:1:{i:0;s:34:%22/tmp/../var/www/html/wp-config.php%22;}s:10:%22%00*%00file_id%22;s:32:%22202cb962ac59075b964b07152d234b70%22;}
-
Cách này khá hay (từ đồng nghiệp tôi PetrusViet), tận dụng chính cơ chế
safeUnserialize()
của nó.- Bằng cách truyền vào 1 chuỗi serialize hợp lệ nhưng ở dạng string. Sau khi hàm
selft:isSerialized
được thỏa mãn thì hàmsafeUnserialize
sẽ được gọi - Hàm
safeUnserialize
sẽ deserialize ở dạng an toàn, và deserialize string chúng ta truyền vào thành payload ban đầu
1
%00s:xxx:O:5:"TCPDF":2:{s:12:"*imagekeys";a:1:{i:0;s:12:"/tmp/newCVEs";}s:10:"*file_id";s:32:"202cb962ac59075b964b07152d234b70";}
- Bằng cách truyền vào 1 chuỗi serialize hợp lệ nhưng ở dạng string. Sau khi hàm
Hai cách khai thác ở 2 vấn đề khác nhau, sau khi thương lượng chúng tôi quyết định để PetrusViet report trước, sau đó nếu may mắn tôi sẽ report bug của mình
2.5 Finding 3
Đồng nghiệp tôi đã report, họ đã chấp nhận và gán CVE. Tôi háo hức bay vào với payload của mình và …. xui vãi ò. Họ fix luôn bug của tôi
Cay thật sự, thế là tôi lại tiếp tục deep vào (đi đến đây rồi chẳng lẽ lại không được gì??)
Sau đó, tôi lại nhớ đến một cách bypass của hàm is_serialized()
hàm core của wordpress trong các phiên bản cũ.
Idea là chèn các ký tự biểu tượng vào để hàm is_serialized
xác định không phải là dữ liệu serialize. Khi lưu xuống database thì do bảng mã cũ (UTF-8) thì các ký tự symbol này bị xóa đi và payload thật sự sẽ lưu xuống DB
Với Idea như thế, tôi bắt đầu set và tiến hành debug từng dòng… và cuôi cùng, tôi đã thành công (https://patchstack.com/articles/critical-vulnerability-patched-in-givewp-plugin/)
III Tổng kết
- Về kỹ thuật, CVE trên không phải thuộc dạng quá phức tạp và thích hợp với người mới bắt đầu.
- Điều hay ở CVE trên nằm ở quá trình reproduce, cách tiếp cận cũng như cách bypass. Nó cho thấy sự thú vị và cuộc đấu trí giữa researcher và developer (fix-bypass-fix-bypass-fix…). Đây cũng là ý nghĩa của nghiên cứu: Tìm ra điểm yếu, bypass, giúp hệ thống an toàn và kiến thức của người nghiên cứu cũng được tăng lên.
- Để có thể hiểu rõ hơn, hãy tiến hành setup môi trường và bắt tay vào debug.