Xin chào mọi người, đã lâu rồi mình mới viết một bài phân tích CVE. Dạo gần đây xoay quanh nhiều chuyện nên ít thời gian để viết lách. Bài viết này là một cơ hội tình cờ để mình tìm kiếm lỗ hổng trên WordPress. Tuy nhiên, mình đã bỏ qua rất nhiều trường hợp, và mình muốn chia sẻ với các bạn về một lỗ hổng cụ thể.
CVE-2023-23488
CVE-2023-23488 được miêu tả rõ ràng tại đường link https://www.tenable.com/security/research/tra-2023-2. Đây là một lỗ hổng Unauth SQL injection ảnh hưởng đến plugin WordPress Paid Memberships Pro phiên bản < 2.9.8. Kẻ tấn công có thể gửi các truy vấn SQL tùy ý đến cơ sở dữ liệu của trang web thông qua tham số code
của đường dẫn REST /pmpro/v1/order
.
Để khai thác lỗ hổng này, có thể sử dụng payload TimeBased SQL Injection thông thường. Cách khai thác rất đơn giản như sau (không cần xác thực):
GET /?rest_route=/pmpro/v1/order&code=a%27%20OR%20(SELECT%201%20FROM%20(SELECT(SLEEP(1)))a)--%20- HTTP/1.1
Vậy tại sao lại dẫn tới lỗ hổng này, cài đặt và phân tích code plugin này theo version 2.9.7 có chứa lỗ hổng tại: https://downloads.wordpress.org/plugin/paid-memberships-pro.2.9.7.zip
Cực kỳ dễ dàng chúng ta có thể tìm thấy lỗ hổng được thấy ở đây. Sử dụng REST API của WordPress được đăng ký theo router
nhảy vào function pmpro_rest_api_permissions_get_order
với
GET /?rest_route=/pmpro/v1/order HTTP/1.1
Sau đó truyền thêm tham số code
vào khởi tạo MemberOrder, nhảy trực tiếp đến __construct
Ở đây ta truyền giá trị code
không phải là số sẽ nhảy sang function getMemberOrderByCode($id)
Tại đây giá trị code
được truyền vào hoàn toàn không được filter hay escape gì, tại đây chúng ta có thể inject SQL vào đây theo kiểu TimeBased như PoC bên trên mình đã nêu. Mọi bước thực hiện đều không cần xác thực.
Nhưng điều kỳ lạ ở đây là mình hoàn toàn, thường xuyên gặp những đoạn code viết kiểu này trên WordPress, mình thường xuyên bỏ qua những đoạn code này vì nó biến được ngăn cách bởi '" . $code . "'
khi này WordPress sẽ tự động thêm slash vào mỗi khi truyền '
hoặc "
thành '
hoặc "
=> không tấn công SQL Injection được do không thể bypass qua dấu '
hoặc "
Addslash tại WordPress
Test thử với đoạn code sau
$code = $_GET['code']; var_dump("SELECT id FROM $wpdb->pmpro_membership_orders WHERE code = '" . $code . "' LIMIT 1"); die();
Với response từ WordPress
Chúng ta có thể thấy rằng giá trị $_GET['code']
đã tự động được thêm slash vào, từ đây chúng ta không thể nào break ra khỏi nháy đơn để thoát khỏi string. Từ đây mình luôn tưởng viết code theo kiểu bên trên là an toàn do mình không thể thoát khỏi nháy đơn!!
Sau một hồi hỏi ChatGPT, WordPress sẽ tự động xử lý và áp dụng addslashes()
cho các giá trị này để đảm bảo rằng chúng an toàn khi được sử dụng trong các câu lệnh SQL hoặc các tình huống khác. Hàm được gọi lên ngay từ wp-setting.php
Với ghi chú rõ ràng rằng sử dụng để escape cho wpdb
Mình đã test thử thì với mọi $_GET, $_POST, $_COOKIE, $_SERVER
khi truyền từ input người dùng thì đều sẽ bị thêm slash vào.
Tuy nhiên, tại sao khi sử dụng REST API thì WordPress lại xoá mất dấu slash đi, điều này dẫn đến lỗ hổng CVE-2023-23488. Chúng ta có thể break ra khỏi dấu nháy đơn, inject câu lệnh SQL tuỳ ý.
Vậy tại sao lại như thế, WordPress thêm slash xong rồi lại xoá slash đi là sao?? Lại đi hỏi ChatGPT thì hoá ra WordPress có sử dụng một cơ chế để loại bỏ dấu backslash được thêm vào khi sử dụng REST API.
Tại đây request được gửi tới REST API sẽ được load vào rest_api_loaded()
, sau đó tiếp tục được handle tại WP_REST_Server::serve_request()
Và được unslash()
tại đây
Thật vậy, thử var_dump($request)
chúng ta được kết quả
Kết luận
Vậy mình đã bỏ qua kha khá nhiều lỗ hổng SQL Injection vì bỏ qua cách viết nối chuỗi này. Lúc nào mình cũng tin rằng không thể bypass được qua dấu nháy khi code dạng đó . Với REST API của WordPress thì hoàn toàn có thể.
Một số cách phòng tránh lỗ hổng SQL Injection khi sử dụng REST API trong WordPress, ví dụ:
- Sử dụng
esc_sql()
filter input từ người dùng truyền vào - Sử dụng
prepare()
để truy vấn an toàn trong WordPress - …