CVE-2024-24027 - SQL Injection trong LikeShop <2.5.7
Lỗ hổng bảo mật SQL Injection (CVE-2024-24027) của sản phẩm LikeShop < 2.5.7 trong tham số id tại endpoint /admin/distribution_member/fans.html
Chào cả lò. Mình là samuelsamuelsamuel thuộc team CookieArena a.k.a Cookie Hân Hoan. Đây là CVE đầu tiên của mình. Cảm ơn thanhlo và thầy HarryHa đã giúp đỡ mình trong quá trình “chạm đến ước mơ” ( ͡♥ ͜ʖ ͡♥)
Mục tiêu
LikeShop là một giải pháp thương mại điện tử mã nguồn mở theo mô hình B2C. Phần mềm được thiết kế để dễ dàng cài đặt, sử dụng và vận hành, phù hợp cho người mới bắt đầu và các doanh nghiệp nhỏ.
Hướng dẫn cài đặt
Thông tin lỗ hổng
Bài viết này sẽ sử dụng phương pháp phân tích Whitebox, còn được gọi là Source Code Audit hoặc Code Review để phân tích mã nguồn và tìm ra lỗ hổng. Phương pháp này phù hợp với các phần mềm mã nguồn mở giống như LikeShop. Dưới đây là cách mình tìm ra lỗ hổng SQL Injection được gán mã CVE-2024-24027 với mức độ nguy hiểm 7.2/10 cùng CVSS vector CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
Nguyên nhân gốc rễ (ROOT-CAUSE) gây ra lỗ hổng
LikeShop sử dụng Framework ThinkPHP, áp dụng kỹ thuật ORM (Object-Relational Mapping) để tự động tạo ra các truy vấn SQL. Giống như các ORM khác, giá trị truyền vào từ phía người dùng sẽ được xử lý để ngăn chặn các lỗ hổng như SQL Injection. Mặc dù rất tiện lợi, nhưng đối với các nghiệp vụ phức tạp thuộc ngành thương mại điện tử, việc dựa hoàn toàn vào ORM có thể khiến các truy vấn được tạo ra không tối ưu về mặt hiệu suất.
Để giải quyết vấn đề này, các ORM Framework cung cấp thêm tính năng cho phép lập trình viên có thể tự viết và thực thi truy vấn SQL theo ý muốn; trong ThinkPHP có thể sử dụng hàm DB::raw()
với tính năng tương tự. Tuy nhiên khi dùng các hàm như DB::raw()
, lập trình viên cần chủ động kiểm tra và xác thực dữ liệu truyền vào từ phía người dùng. Điều này có thể tạo ra nhiều nguy hiểm tiềm ẩn nếu dữ liệu không được xác thực và làm sạch đúng cách.
CVE-2024-24027
Trong file html/application/admin/logic/DistributionMemberLogic.php
, hàm getFansLists()
sử dụng DB::raw()
để tạo câu truy vấn.
...
public static function getFansLists($get)
{
$user_id = $get['id'];
$where = [];
if (!empty($get['search_key']) && !empty($get['keyword'])) {
$keyword = $get['keyword'];
$where[] = [$get['search_key'], 'like', '%' . $keyword . '%'];
}
$fans_type = $get['type'] ?? 'all';
if ($fans_type == 'all') {
$where[] = ['', 'exp', Db::raw("FIND_IN_SET($user_id, ancestor_relation)")];
} else {
$where[] = [$fans_type, '=', $user_id];
}
...
}
Hàm nhận vào 1 mảng $get
và gán giá trị $get[’id’]
cho biến $user_id
. Biến $user_id
được đưa trực tiếp vào câu truy vấn, không qua quá trình xác thực dữ liệu và loại bỏ các ký tự đặc biệt. Nếu mảng $get
ban đầu cũng không được xác thực dữ liệu, điều này có thể tiềm ẩn nguy cơ xảy ra lỗ hổng SQL Injection.
Trong file html/application/admin/controller/DistributionMember.php
, file DistributionMemberLogic.php
được import và getFansLists()
được gọi trong hàm fans()
namespace app\\admin\\controller;
use app\\admin\\logic\\DistributionMemberLogic;
...
public function fans()
{
$user_id = $this->request->get('id');
if ($this->request->isAjax()) {
$get = $this->request->get();
$this->_success('获取成功', DistributionMemberLogic::getFansLists($get));
}
$this->assign('user_id', $user_id);
return $this->fetch();
}
...
Bên trong hàm fans()
sử dụng hàm isAjax()
để kiểm tra HTTP Request gửi tới có được thực hiện bằng AJAX hay không. Nếu đúng, dữ liệu của GET Request gửi đến sẽ được lưu vào biến $get
và truyền vào bên trong method DistributionMemberLogic::getFansLists()
, bao gồm tham số id
truyền vào từ người dùng.
Qua việc phân tích, ta có thể thấy rằng tham số id
trong GET Request không được xác thực, sau đó được gán vào biến $user_id
và truyền vào câu truy vấn bên trong hàm getFansLists()
, cho thấy hệ thống có khả năng xảy ra lỗ hổng SQL Injection tại endpoint /admin/distribution_member/fans.html
.
Khai thác lỗ hổng
Việc không có các biện pháp kiểm tra hoặc xác thực dữ liệu truyền vào từ phía người dùng trong khi sử dụng DB::raw()
cho thấy hệ thống có tiềm năng xảy ra lỗ hổng SQL Injection. Kẻ tấn công có thể truyền vào các ký tự đặc biệt để phá vỡ cú pháp của câu truy vấn SQL ban đầu và chèn thêm các câu truy vấn độc hại
Ngoài tham số id
, hệ thống còn nhận thêm các tham số type
, page
và limit
. Thiếu một trong các tham số trên có thể gây ra lỗi. Ngoài ra còn cần thêm vào HTTP Request Header X-Requested-With: XMLHttpRequest
vì hệ thống chỉ chấp nhận các Request tạo ra bằng AJAX.
Request ban đầu:
Kiểm tra lỗ hổng SQL Injection trong tham số id
:
Thêm dấu nháy đơn đằng sau tham số truyền vào gây ra lỗi cú pháp câu truy vấn, khiến cho Web Server trả về lỗi 500 Internal Server Error
Vì câu truy vấn không trả về dữ liệu bản rõ, có thể khai thác lỗ hổng SQL Injection theo hướng Time-Based sử dụng hàm SLEEP()
Sử dụng SQLmap để khai thác:
Parameter: id (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 time-based blind - Parameter replace (substraction)
Payload: id=(SELECT 3426 FROM (SELECT(SLEEP(5)))zlGG)&type=all&page=1&limit=1