Wordpress Plugin và những lỗ hổng bảo mật thường gặp (Phần 2)
Intro
Trong phần trước chúng ta đã biết đến lỗ hổng Broken Access Control (BAC), một trong những lỗ hổng phổ biến nhất trong các Wordpress plugins. Trong lần này chúng ta cùng điểm qua thêm một số lỗ hỗng khác nhé
File Access/Usage Weaknesses/Missing File Upload Validation
Kiểu lỗ hổng này cho phép kẻ tấn công đọc hoặc xóa file bất kỳ trên server, đồng thời cho phép kẻ tấn công có thể tấn công Local/Remote File Inclusion (LFI / RFI). Việc truy cập, đọc, ghi, xóa file rất thường xuất hiện trong các plugins hoặc themes, dùng để load các file theme hoặc template. Nhiều plugin cũng có chức năng backup hoặc import config, tất nhiên là sẽ đi kèm các xử lý với file.
Dưới đây là danh sách các hàm cần chú ý:
- Load/Remote File Includes:
include_once()
include()
require_once()
require()
- Đọc file:
file_get_contents()
fread()
fopen()
- Xóa file:
wp_delete_file()
unlink()
- Ghi file:
file_put_contents()
fwrite()
move_uploaded_file()
Trong trường hợp lỗ hổng xảy ra, kẻ tấn công có thể sử dụng một trong nhiều phương pháp dưới đây để tấn công và hậu quả cao nhất có thể là RCE hoặc chiếm quyền (take-over):
- Đọc file bất kỳ: đọc các file chứa thông tin nhạy cảm như wp-config.php: file chứa thông tin về key, salt, thông tin kết nối DB, etc
- Xóa file bất kỳ: hacker có thể xóa file wp-config.php đi, lúc này, Wordpress sẽ hiểu là trang chưa được cài đặt và hiện giao diện install khi truy cập. Hacker có thể setup một trang wordpress mới với một DB bất kỳ, gán quyền admin cho bản thân và take-over.
- Ghi file bất kỳ: trong thường hợp tệ nhất, hacker có thể upload file webbshell .php lên và RCE. Trường hợp extension .php đã bị chặn, kẻ tấn công có thể upload file có extension bất kỳ rồi kết hợp với lỗ hổng LFI để khai thác RCE.
Cùng xem một số ví dụ nào:
WordPress Welcart e-Commerce Plugin <= 2.7.7 - Unauthenticated Directory Traversal
Lỗ hổng nằm ở file progress-check.php
như hình trên. Đối với các file PHP được include trong thư viện/code thì ở phần đầu của file sẽ có các đoạn tương tự như dưới đây:
<?php
if ( ! defined( 'WPINC' ) ) {
die;
}
// or
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
các đoạn code này sẽ có nhiệm vụ kiểm tra xem có phải file đang được truy cập trực tiếp từ URL không hay đang được include vào. Nếu là truy cập trực tiếp thì sẽ ngay lập tức bị chặn lại. Tuy nhiên, code ở trên thiếu mất phần này, kèm với việc cho phép đọc file bất kỳ với tham số lấy từ $_GET['progressfile']
dẫn tới việc unauthenticated hacker có thể đọc file tùy ý như PoC dưới đây:
Download Monitor Plugin <= 4.8.3 - Unauthenticated Arbitrary File Upload
Với đoạn code như dưới đây, plugin thao tác trực tiếp với file người dùng tải lên thông qua $_FILES
rồi sau đó thực hiện ghi file vào trong thư mục uploads mà không có bất cứ đoạn kiểm tra extension nào. Plugin dựa vào việc ở thư mục dlm_uploads
sẽ có một file .htaccess
mặc định được tạo sẽ ngăn chặn việc truy cập trực tiếp vào thư mục và ngăn chạy code PHP, tuy nhiên cơ chế này có thể bị bypass dễ dàng bằng việc upload một file .htaccess
mới, overwrite file cũ:
public function upload_file() {
$uploadedfile = $_FILES['file'];
$image_url = $uploadedfile['tmp_name'];
$upload_dir = wp_upload_dir();
$image_data = file_get_contents( $image_url );
$filename = $uploadedfile['name'];
$file = $upload_dir['basedir'] . '/dlm_uploads/' . date( 'Y/m/' ) . $filename;
if ( ! file_put_contents( $file, $image_data ) ) {
wp_send_json_error( array( 'errorMessage' => esc_html__( 'Failed to write the file at: ', 'download-monitor' ) . $file ) );
}
$wp_filetype = wp_check_filetype( $filename, null );
$attachment = array(
'post_mime_type' => $wp_filetype['type'],
'post_title' => sanitize_file_name( $filename ),
'post_content' => '',
'post_status' => 'inherit',
);
$attach_id = wp_insert_attachment( $attachment, $file );
if ( ! is_wp_error( $attach_id ) ) {
$attach_data = wp_generate_attachment_metadata( $attach_id, $file );
} else {
wp_send_json_error( array( 'errorMessage' => $attach_id->get_error_message() ) );
}
wp_update_attachment_metadata( $attach_id, $attach_data );
wp_send_json_success( array( 'file_url' => wp_get_attachment_url( $attach_id ) ) );
}
Bài học rút ra từ hai lỗ hổng này là:
- Kiểm tra quyền để đảm bảo chỉ có user có quyền phù hợp mới được thao tác với file. Sử dụng hàm
wp_upload_dir()
để chỉ định đường dẫn file được tải lên. Dùng hàmsanitize_file_name()
để lọc filename và hàmwp_handle_upload()
để xử lý việc upload file. - Luôn luôn kiểm tra extension của file được upload lên. Tốt nhất là sử dụng whitelist các extension được phép. Có thể sử dụng các hàm
wp_check_filetype()
hoặcwp_check_filetype_and_ext()
để kiểm tra.
Unprepared SQL Queries
Hầu hết dữ liệu của một trang WordPress, bao gồm các settings của trang web, password, nội dung bài viết, pages, cũng như cài đặt của các plugin, được lưu trữ trong cơ sở dữ liệu, thường là cơ sở dữ liệu MySQL. Tất cả các dữ liệu lưu trữ trong DB có thể được truy cập bằng cách sử dụng SQL query. Do đó, việc sử dụng các truy vấn SQL trong các plugin và theme là rất phổ biến để SELECT, INSERT, UPDATE hoặc DELETE dữ liệu từ DB.
Wordpress đã cung cấp sẵn một số hàm để các lập trình viên có thể truy vấn SQL một cách an toàn, đó là $wpdb->prepare
. Ngoài ra còn có các hàm khác tương tự:
$wpdb->update()
$wpdb->insert()
$wpdb->delete()
Nhưng, có phải bao giờ các lập trình viên cũng dùng đâu . Sử dụng các hàm sau để truy vấn dữ liệu, với dữ liệu đầu vào chưa được kiểm tra kỹ hoàn toàn có thể dẫn đến lỗ hổng SQLi:
$wpdb->get_results()
$wpdb->query()
$wpdb->get_row()
$wpdb->get_col()
Cùng xem thử chúng ta có thể tìm kiếm lỗ hổng SQLi trong các plugin như thế nào nhé.
Photo Gallery by 10Web < 1.5.55 - Unauthenticated SQL Injection
Với cách đơn giản nhất, chúng ta có thể sử dụng công cụ regex. Chỉ bằng một regex đơn giản như dưới đây: tìm kiếm tất cả các vị trí sử dụng biến $wpdb
nhưng không dùng prepare
, chúng ta có thể tìm đến các vị trí có nguy cơ bị dính lỗi. Và trong trường hợp của plugin Photo Gallery by 10Web thì là câu truy vấn dưới đây.
Tuy nhiên tìm được câu truy vấn thì chúng ta cũng cần phải lần ngược về đầu để có thể kiểm tra được là biến được sử dụng trong câu truy vấn này có phải là user-controllable hay không?
Và với chỉ bằng cách sử dụng regex thôi, chúng ta có thể tìm ra được kha khá các lỗi SQLi luôn:
Hãy chú ý đến câu try vấn cuối ở trong slide. Rõ ràng là tham số $_GET['id']
đã được đưa qua hàm esc_attr
trước khi đưa vào SQL query. esc
thì chắc là viết tắt cho escape
rồi, phải chăng là dữ liệu đầu vào đã được lọc? Ở đây có một sai lầm (pitfall) hay gặp của các lập trình viên Wordpress plugin là họ đôi khi bị lẫn lộn giữa các hàm dùng để làm sạch dữ liệu. Cùng xem document của hàm này viết gì nhé.
Vậy là hàm này chỉ có tác dụng chống lại lỗ hổng XSS thôi, và không may trong trường hợp này, để khai thác SQLi chúng ta không cần dùng đến dấu '
nên vẫn có thể khai thác OK nhé . Đặc biệt, có một hàm bị sửa dụng sai khá nhiều đó là sanitize_text_field
Nhưng hãy thử kiểm tra lại document một lần nữa:
Nếu chỉ đọc mà không chú ý đến chi tiết, chúng ta sẽ hiểu nhầm rằng hàm này dùng để làm sạch dữ liệu trước khi đưa vào DB. Nhưng sự thật thì ngược lại, nó chỉ có tác dụng chống lại XSS thôi nhé. Document còn gây lú như thế này thì đúng là đôi lúc cũng khó mà trách được dev
Đến đây chúng ta đã có thể nhận thấy được sự bất cập trong việc phát triển plugin khi mà hoàn toàn không có một framework chuẩn giúp chũng ta chống lại các loại lỗ hổng thường gặp giống như lập trình web thông thường
XSS
Thật là thiếu sót nếu nhắc đến lỗ hổng trong Wordpress plugin mà không nhắc đến lỗ hổng XSS. XSS chính là class lỗ hổng nhiều nhất, chiếm đến hơn 38% trong tất cả các loại lỗ hổng đã được tìm thấy theo thống kê từ trang https://wpscan.com. Và cũng giống như các lỗ hổng còn lại, tất cả đều bắt nguồn từ việc không escape các dữ liệu user-controllable trước khi output ra giao diện trang web. Wordpress plugin thì có đủ cả 3 loại: Reflected XSS, Stored XSS và DOM-based XSS. Điều nguy hiểm hơn là với lỗ hổng XSS này, nếu người trigger lỗ hổng là admin, kẻ tấn công có thể khai thác leo quyền (escalate) lên cao hơn thông qua việc chạy javascript để tạo mới một người dùng có quyền admin, từ đó chiếm quyền toàn bộ trang.
Điểm khởi đầu (source) của lỗ hổng này (và các lỗ hổng khác) thì vẫn là các biến user-controllable: $_GET
, $_POST
, $_REQUEST
, $_SERVER
, $_COOKIE
, $_FILES
. Ngoài ra các hàm đặc biệt sau của Wordpress cũng có thể dẫn đến nguy cơ xuất hiện lỗ hổng XSS:
add_query_arg()
vàremove_query_arg()
dùng để thêm hoặc xóa tham số từ một string. Các tham số đưa vào hàm này sẽ được echo/output ra ngoài trang web.- Quyền
unfiltered_html
cho phép user có thể thêm raw HTML vào các pages hoặc posts.
Cùng xem thử một ví dụ nào:
WP Customer Reviews < 3.4.3 - Multiple Unauthenticated and Low Priv Authenticated Stored XSS
Đây là một plugin cho phép người dùng có thể comment đánh giá review sản phẩm. Người dùng có thể vào trang đánh giá và điền các payload XSS vào các field: "Reviewer Name", "Website", "Review Title" ví dụ như:
" autofocus onfocus='alert(document.domain)'>
Các giá trị này sẽ được lưu vào post metadata thông qua hàm update_post_meta
ở đoạn code dưới đây, đây cũng có thể coi là 1 source cần lưu ý khi tìm kiếm các lỗ hổng stored XSS:
Và khi plugin bê nguyên các giá trị này ra output khi admin truy cập vào "Edit Customer Review" để trả lời người dùng, XSS sẽ được trigger:
Để ngăn chặn lỗ hổng này, lập trình viên có thể sử dụng các hàm sau để làm sạch tham số đầu vào:
sanitize_text_field()
sanitize_email()
sanitize_file_name()
sanitize_html_class()
sanitize_key()
sanitize_meta()
sanitize_mime_type()
sanitize_option()
sanitize_title()
sanitize_title_for_query()
sanitize_title_with_dashes()
sanitize_user()
Sử dụng các hàm sau để escape các dữ liệu đầu ra tương ứng với từng vị trí ở trong code HTML (output ra ở HTML, ở thuộc tính của tag, ở code javascript...)
esc_attr()
esc_html()
esc_js()
esc_textarea()
esc_url()
esc_url_raw()
To be continue...
Ở phần 3, mình sẽ đi vào nốt một số lỗ hổng khác nữa, ít gặp hơn nhưng cũng không kém phần nguy hiểm. See ya ~
References
All rights reserved