(VietNamese) WordPress POP Chain
Introduction
Trong quá trình thực hiện task nghiên cứu về POP Chain trên WordPress dưới sự hướng dẫn của người anh, mình đã phát hiện ra lỗ hổng Insecure Deserialization bắt nguồn từ việc plugin xử lý dữ liệu đầu vào thiếu an toàn. Hiện tại, PoC mới chỉ chứng minh được khả năng khởi tạo class bất kỳ và bypass filter, chưa đạt đến mức thực thi shell (RCE) trên máy chủ. Vì vậy, hướng tiếp cận sắp tới là rà soát các POP Chain trong WordPress Core để mở rộng phạm vi khai thác.
Setup Lab Enviroment
Với những điều kiện trên, để mô phỏng được WordPress POP Chain mình cài đặt bản mới nhất Wordpress Core Version 6.9.4 và sử dụng Docker để dựng lại môi trường.
Tạo thêm 1 file để hỗ trợ debug trong quá trình mô phỏng ở đây tôi tạo 1 file xdebug.ini
1 | xdebug.mode=debug |
Set cấu hình xdebug vào docker
1 | FROM wordpress:6.9.4 |

Step-by-step analysis
Sau khi quá trình setup hoàn tất và những thứ liên quan trong việc mô phỏng thì mình tiếp tục tạo tiếp 1 plugin như sau để có thể debug được Wordpress POP Chain này.
Như bài blog của người anh đi trước có nói ở my-plugin này có sử dụng thêm hàm wp-unslash vì lí do trong wordpress core sẽ tự động gọi đến hàm add_magic_quotes nhằm mục đích tránh các lỗi SQL Injection hay XSS do đó payload chúng ta truyền vào sẽ bị offset error trong quá trình Deserialize payload.
Chính vì điều này nên để thực hiện Insecure Deserialization thành công ở trên Wordpress thì ở control data cần phải có các function như Base64_decode, wp_unslash, urldecode…
Ở đây mình sẽ bắt đầu bước đầu tiên ở chain nằm ở class ParagonIE_Sodium_Core_Poly1305_State ở wordpress/wp-includes/sodium_compat/src/Core/Poly1305/State.php.

Khi gọi đền function __destruct() nó lấy các giá trị offset tăng dần theo $this->r và lúc này biến $this->r mình sẽ set nó là Object của class WP_Block nằm trong file class-wp-block-list.php
1 | class WP_Block_List implements Iterator, ArrayAccess, Countable { |
Ở đây chúng ta có thể thấy ở WP_Block_List gọi đền function offsetGet và may mắn thay lúc này đồng thời class WP_Block_List đã implement class ArrayAccess. Sau khi đi theo flow chain WP_Block chúng ta có thể xem tiếp ở class WP_Block
Khi đi vào class WP_Block nó khởi tạo 1 construct sau đó gọi đến function get_registered của class nào được khai báo ở trong biến $this->registry. Và tiếp tục ở đây biến $registry mình sẽ set nó là Object của class WP_Block_Patterns_Registry nằm trong file class-WP_Block_Patterns_Registry
Lúc này function get_registered nó nhận $pattern_name sau đó thực hiện gọi đến function get_content($pattern_name) và lúc này function get_content là điểm sink cuối cùng gây ra lỗi chúng ta cùng xem xét function nó xử lý.
1 | private function get_content( $pattern_name, $outside_init_only = false ) { |
Ở đây nó có filter một số như là nó sẽ check đầu vào filepath chuẩn hóa đầu vào tránh PathTraversal và kiểm tra Objecct có phải là to_string sau đó nó sẽ kiểm tra thêm filepath chúng ta có phải kết thuscc là .php hay .html sau đó gọi đến include với giá trị include $patterns[ $pattern_name ]['filePath']; đưa thẳng vào include file.
Bypass Filter
Với filter của điểm cuối get_content trên là chưa đủ attacker có thể bypass với đoạn code trên như sau
Lần 1:
realpath((string) $file_path)ép Object String để kiểm tra.
Lần 2:include $patterns[ $pattern_name ]['filePath'];nó ép String lần nữa và nếu lúc nàyfile_pathlúc này làObject to_string()đổi giá trị 2 lần gọi, thì lần 1 nó check là Path A còn include nó dùng Path B.
Còn phần kết thúc.phpchúng ta có thể sử dụng tool php_filter_chain_generator để bypass
Code Demo Bypass
1 | class Exploit_ToString_Bypass { |
Và những cái phân tích trên là Sink POP Chain vì ở đây chúng ta có thể sử dụng PHP Filter Chain để LFI to RCE.
Work Flow POP Chain
WP_Block_Patterns_Registry -> WP_Block_List -> ParagonIE_Sodium_Core_Poly1305_State -> __destruct()
POC RCE
- Generate Payload

- Generate Payload PHP Serialize Kèm thêm Class Bypass Filter
1 |
|

Tuy nhiên nhược điểm của chain này là vì class ParagonIE_Sodium_Core_Poly1305_State không hề tồn tại trong quá trình chạy Wordpress core ở Linux instance và mặc định nó đã tắt nên chúng ta không thể gọi đến function offsetGet của class WP_Block_List được. Chính vì vậy để khai thác được thì chúng ta còn phải xem thử sau khi plugin thực hiện class WP_Block_List có tiến hành xử lí thêm như là access vào index của một property nào đó của object đó không.




