Intro
Long time no see!
Chúng ta cùng tiếp tục series này với phần 3 dưới đây nhé. Nếu chưa đọc qua phần 1 & 2 bạn có thể xem các bài trước ở đây và ở đây.
Code & Command Injection
Có một số function trong PHP có thể được sử dụng để thực thi lệnh. Khi sử dụng các hàm này, người dùng có thể thực hiện các lệnh như ls
để liệt kê thư mục, touch
để tạo file mới, mkdir
để tạo thư mục với và rất nhiều các lệnh khác. Mặt khác, có các function PHP khác có thể cho phép chạy các lệnh PHP tùy ý như <?php phpinfo();?>
và đôi khi các plugin và theme WordPress sẽ sử dụng những hàm này để chạy lệnh và code nhằm thực hiện các công việc phục vụ chức năng của plugin đó.
Thực tế, rất hiếm khi các function này nguyên nhân duy nhất gây ra lỗ hổng trong WordPress, tuy nhiên, khi những input người dùng được truyền trực tiếp vào các function mà không có qua bất kỳ một filter, sanitizer hoặc kiểm tra quyền thì chắc chắn plugin đã bị dính lỗi RCE.
Dưới đây là danh sách một số function, tuy khác nhau đôi chút về công dụng và output, chúng đều cho phép thực thi câu lệnh hệ thống tùy ý thông qua các tham số truyền vào, hãy CỰC KỲ THẬN TRỌNG khi sử dụng các function này:
exec - Returns last line of commands output
passthru - Passes commands output directly to the browser
system - Passes commands output directly to the browser and returns last line
shell_exec - Returns commands output
\`\` (backticks) - Same as shell_exec()
popen - Opens read or write pipe to process of a command
proc_open - Similar to popen() but greater degree of control
pcntl_exec - Executes a program
Một số function có thể cho phép thực hiện một đoạn code PHP bất kỳ:
assert() - identical to eval()
preg_replace('/.*/e',...) - /e does an eval() on the match
create_function()
Viết code như dưới đây sẽ cho phép gọi một function bất kỳ thông qua tham số truyền vào từ URL:
$_GET['func_name']($_GET['argument']);
VD với tham số truyền vào từ URL: func_name=shell_exec&argument=whoami
thì đoạn code trên sẽ tương đương với đoạn code
shell_exec("whoami");
Tương tự, code như sau sẽ cho phép khởi tạo một function với nội dung code của function được truyền qua $_GET['func_name']
và thực thi function đó:
$func = new ReflectionFunction($_GET['func_name']);
$func->invoke();
$func->invokeArgs(array());
Ngoài ra, có một số function cho phép thực thi các function được truyền vào tham số của nó (hay còn gọi là callback). VD:
và đoạn code:
call_user_func('shell_exec', 'id');
sẽ tương đương với shell_exec('id')
hay như function array_filter
, dù tên trông có vẻ chẳng liên quan gì đến thực thi code cả nhưng bản chất, nó sẽ thực hiện function được truyền vào ở $callback
lên từng phần tử của array:
array_filter(array $array, ?callable $callback = null, int $mode = 0): array
Tham khảo thêm các function nguy hiểm của PHP ở link https://gist.github.com/mccabe615/b0907514d34b2de088c4996933ea1720
Đây cũng có thể coi là một gợi ý tốt để chúng ta tìm kiếm các vị trí nguy hiểm có thể gây ra lỗi RCE khi review source code.
Để ngăn chặn các lỗi này, cần:
- Thực hiện filter, sanitizer input của người dùng. Sử dụng whitelist thay vì blacklist.
- Thực hiện phân quyền với các chức năng nguy hiểm.
- Hạn chế đến mức tối đa việc sử dụng các function này, trừ khi không còn cách nào khác.
Cùng xem thử một số VD sau:
CVE-2024-6386 - WPML Multilingual CMS <= 4.6.12 - Authenticated (Contributor+) Remote Code Execution via Twig Server-Side Template Injection
Tuy miêu tả của CVE-2024-6386 có vẻ liên quan đến lỗi SSTI nhưng về bản chất vẫn là việc input của người dùng được truyền vào các function nguy hiểm mà không thông qua một bộ lọc. Ở đây chính là hàm array_filter
.
function twig_array_filter($array, $arrow)
{ if (\is_array($array)) { if (\PHP_VERSION_ID >= 50600) { return \array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); } return \array_filter($array, $arrow); } // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
}
Plugin cho phép người dùng truyền vào tham số của function filter
ở Twig template và cuối cùng đi vào biến $arrow
ở trên. PoC mẫu như dưới đây:
{{ {1: phpinfo}|filter(call_user_func) }}
sẽ trở thành call_user_func('phpinfo', 1);
và cho phép chúng ta thực thi function phpinfo
với tham số 1
CVE-2024-5932 - GiveWP – Donation Plugin and Fundraising Platform <= 3.14.1 – Unauthenticated PHP Object Injection to Remote Code Execution
Function nguy hiểm được lợi dụng cho việc khai thác PHP Object Injection lần này là call_user_func
với việc kẻ tấn công có thể kiểm soát cả 2 tham số truyền vào hàm này. Bạn có thể đọc thêm bài phân tích chi tiết ở đây: https://viblo.asia/p/phan-tich-cve-2024-5932-lo-hong-php-object-injection-trong-givewp-wordpress-plugin-x7Z4DOG0VnX
Social Warfare Plugin <= 3.5.2 Unauthenticated Remote Command Execution
Nhìn vào screenshot này, ta có thể thấy rõ ràng lỗi ở đâu rồi đúng không?
Sensitive Information Disclosure
Việc lộ các thông tin nhạy cảm có thể xuất hiện dưới rất nhiều hình thức khác nhau, từ đường dẫn các file ở trên server cho đến các thông tin cá nhân (PII) lưu trong cơ sở dữ liệu. Đặc biệt, nếu để lộ giá trị nonce cho những người dùng quyền thấp, những người dùng này có thể lợi dụng các giá trị này để thực hiện các tác vụ không được phép.
Chúng ta cần để ý đến các function có chức năng thêm thông tin vào trong nội dung trang, đặc biệt là ở path /wp-admin
, phần có thể được truy cập bởi các user đã đăng nhập:
admin_footer
: thêm dữ liệu hoặc script vào footer của trang adminadmin_head
: thêm dữ liệu hoặc script vào head của trang adminwp_footer
: thêm dữ liệu hoặc script vào footer của trang bất kỳwp_head
: thêm dữ liệu hoặc script vào head của trang bất kỳwp_enqueue_script
vàwp_localize_script
: thêm script vào trang bất kỳadmin_enqueue_script
: thêm script vào trang adminlogin_enqueue_script
: thêm script vào trang login
Cách fix ở đây vẫn là đảm bảo tốt việc phân quyền truy cập đến những phần khác nhau của trang. Cùng điểm qua một số VD sau nhé:
CVE-2024-6551 - GiveWP <= 3.15.1 - Unauthenticated Full Path Disclosure
Plugin có sử dụng Symfony và trong một số file test vẫn để bật display_errors
dẫn đến lộ đường dẫn của file trên server nếu có lỗi xảy ra:
ini_set('display_errors', 1);
CVE-2023-6582 - ElementsKit Lite <= 3.0.3 - Unauthenticated Sensitive Information Exposure
Ở CVE này, khi một trang sử dụng widget tạo bởi plugin Elementor được browser tải về thì trong code HTML/JS ở frontend sẽ có thêm giá trị nonce sau:
wp_localize_script( 'elementskit-elementor', 'ekit_config', [ 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'ekit_pro' ), ] );
VD như sau:
var ekit_config = {"ajaxurl":"http:\/\/localhost:7080\/wp-admin\/admin-ajax.php","nonce":"c58c0a43d3"};
Sử dụng nonce này, attacker có thể gọi các action AJAX: wp_ajax_ekit_widgetarea_content
và wp_ajax_nopriv_ekit_widgetarea_content
đi kèm với post_id
tùy ý để đọc nội dung của post đó.
CVE-2023-32243 - Essential Addons for Elementor <= 5.7.1 – Unauthenticated Arbitrary Password Reset to Privilege Escalation
Để có thể khai thác được lỗi ở phần reset password thì cần có nonce hợp lệ. Nonce này được tạo ra ở includes/Classes/Asset_Builder.php
$this->localize_objects = apply_filters( 'eael/localize_objects', [ 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'essential-addons-elementor' ),
Biến $this->localize_objects
tiếp tục được sử dụng tại function wp_localize_script
ở function frontend_asset_load
tại: includes/Classes/Asset_Builder.php
public function frontend_asset_load() { $handle = 'eael'; $this->post_id = get_the_ID(); $this->elements_manager->get_element_list( $this->post_id ); $this->load_commnon_asset(); $this->register_script(); -------------------- CUTTED HERE ------------------------------------ wp_localize_script( $handle, 'localize', $this->localize_objects );
}
dẫn đến việc leak nonce ra ở một trang không cần đăng nhập (unauth)
Insufficient Randomness
Đây là lỗi liên quan đến việc sử dụng các thư viện crypto sinh chuỗi random và cách tự random chuỗi do plugin thực hiện. Rất nhiều plugin, khi lưu file trên server đã chú ý đến việc tạo tên random cho file nhưng do cách impelement chưa đúng dẫn đến việc attacker vẫn có thể brutefoce được. Tương tự cho việc sinh các giá trị ngẫu nhiên dùng để làm nonce. Cùng xem thử các VD dưới đây:
Elementor (<= 3.18.1)
Khi một người dùng import JSON template, file này sẽ được lưu ở thư mục tạm trên server với một tên thư mục ngẫu nhiên. Plugin chỉ cho phép ta upload ZIP hoặc JSON file, nếu chúng ta upload một file không hợp lệ (VD file PHP để dùng làm webshell), plugin sẽ xóa file tạm đó đi.
Lỗi thứ nhất xảy ra do xử lý sai biến $upload_result
sai, dẫn đến exception và file tạm thực tế là không bị xóa:
if ( is_wp_error( $upload_result ) ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $upload_result['tmp_name'] ) ); return $upload_result;
}
Exception như sau:
PHP Fatal error: Uncaught Error: Cannot use object of type WP_Error as array in /var/www/html/wp-content/plugins/elementor/includes/template-library/manager.php:469
Lỗi thứ hai là ở function create_unique_dir
dùng để generate tên thư mục tạm đã sử dụng hàm uniqid()
sai cách, truyền thiếu tham số more_entropy
với giá trị true
khiến attacker có thể brute-force được tên thư mục trong thời gian ngắn, truy cập được vào file webshell không bị xóa => RCE
PoC: https://mega.nz/file/SgdURZhI#360-RSS6X32nqk8A7ub4DMME3no36PQV2vFgUMJO9Lc
CVE-2024-22144 - Unauthenticated RCE in Anti-Malware Security and Brute-Force Firewall GOTMLS WordPress Plugin
Trong bài viết này, researcher stealthcopter đã phân tích rất chi tiết việc, một đoạn code trông rất secure trong việc sinh số ngẫu nhiên như dưới đây vẫn có thể bị brute-force
md5(substr(number_format(microtime(true), 9, '-', '/'), 6).GOTMLS_installation_key.GOTMLS_plugin_path);
Tạm kết
Trong khuôn khổ 3 bài viết ngắn ngủi này khó có thể cover được hết các lỗ hổng đa dạng, tuy nhiên chúng ta cũng đã điểm mặt chỉ tên hầu hết các lỗ hổng phổ biến. Hi vọng series này sẽ có ích cho cả các bạn developer và cả các bạn bug hunter trên Wordpress Plugin. See ya!