Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2023-39361 — Пошаговый анализ и настройка лабораторной среды для CVE-2023-39361, неавторизованной SQL-инъекции в Cacti v1.2.24, с прохождением эксплуатации и смягчения последствий. | Kitploit
Инструменты/GitHubGitHub/hpt-intern-task-submission/cve-2023-39361
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийОбучение и ОбразованиеЛаборатории и Практика
GitHubhpt-intern-task-submission/cve-2023-39361

CVE-2023-39361

Пошаговый анализ и настройка лабораторной среды для CVE-2023-39361, неавторизованной SQL-инъекции в Cacti v1.2.24, с прохождением эксплуатации и смягчения последствий.

Репозиторий

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
2 лет назадЕщё не проверено

[CVE-2023-39361] Неаутентифицированная SQL-инъекция в Cacti v1.2.24

Обзор

Cacti — это инструмент мониторинга с открытым исходным кодом, написанный на PHP, MySQL/MariaDB, предоставляющий удобный интерфейс. Уязвимость была обнаружена в 2023 году и затрагивает все версии до 1.2.24. Эта проблема безопасности заключается в некорректной реализации при вставке значений в SQL-запрос. Это критическая уязвимость SQL-инъекции, которая позволяет атакующим изменять базу данных, а также удаленно выполнять код.

Настройка лаборатории:

В этом анализе для простоты я запущу Cacti в Docker. Сначала создадим файл docker-compose.yml как указано ниже и выполним команду docker-compose up -d.

root@kitploit:~
version: '3.5'

services:

  
  

cacti:

image: "smcline06/cacti"

container_name: CVE-2023-39361

domainname: example.com

hostname: cacti

ports:

- "80:80"

- "443:443"

environment:

- DB_NAME=cacti_master

- DB_USER=cactiuser

- DB_PASS=cactipassword

- DB_HOST=db

- DB_PORT=3306

- DB_ROOT_PASS=rootpassword

- INITIALIZE_DB=1

- TZ=America/Los_Angeles

volumes:

- cacti-data:/cacti

- cacti-spine:/spine

- cacti-backups:/backups

links:

- db

  
  

db:

image: "mariadb:10.3"

container_name: CVE-2023-39361_db

domainname: example.com

hostname: db

ports:

- "3306:3306"

command:

- mysqld

- --character-set-server=utf8mb4

- --collation-server=utf8mb4_unicode_ci

- --max_connections=200

- --max_heap_table_size=128M

- --max_allowed_packet=32M

- --tmp_table_size=128M

- --join_buffer_size=128M

- --innodb_buffer_pool_size=1G

- --innodb_doublewrite=ON

- --innodb_flush_log_at_timeout=3

- --innodb_read_io_threads=32

- --innodb_write_io_threads=16

- --innodb_buffer_pool_instances=9

- --innodb_file_format=Barracuda

- --innodb_large_prefix=1

- --innodb_io_capacity=5000

- --innodb_io_capacity_max=10000

environment:

- MYSQL_ROOT_PASSWORD=rootpassword

- TZ=America/Los_Angeles

volumes:

- cacti-db:/var/lib/mysql

  
  

volumes:

cacti-db:

cacti-data:

cacti-spine:

cacti-backups:

После сборки контейнера мы можем получить доступ к Cacti, перейдя по адресу http://localhost:80. Следующий шаг — обновить Cacti до версии 1.2.24. Вы можете загрузить скрипт обновления здесь: https://pastebin.com/NfRiHLjR. Сохраните файл в той же директории, где находится файл docker-compose.yml, и переименуйте его в upgrade_cacti.sh. Затем выполните следующие 2 команды:

root@kitploit:~
docker cp upgrade_cacti.sh CVE-2023-39361:/tmp/upgrade_cacti.sh && docker exec -it CVE-2023-39361 bash -c "bash /tmp/upgrade_cacti.sh"

Теперь всё готово, давайте углубимся в код, чтобы понять, что произошло, что позволяет провести атаку SQL-инъекции. Уязвимый файл — graph_view.php, и для доступа к этому файлу нам нужен только гостевой пользователь, что позволяет любому использовать эту уязвимость. В этом файле небезопасная функция — grow_right_pane_tree().

grow_right_pane_tree

Прежде чем мы углубимся в эту функцию, нужно проследить обратный путь, чтобы понять, как она выполняется.

root@kitploit:~
<?php
switch (get_nfilter_request_var('action')) {
//.... 
// Many other cases
case  'tree_content':
	//..... Some code here
	// ---------
	if (isset_request_var('node')) {
		$parts = explode('-', sanitize_search_string(get_request_var('node')));
		// Check for tree anchor
		if (strpos(get_nfilter_request_var('node'), 'tree_anchor') !== false) {
			$tree_id = $parts[1];
			$node_id = 0;
		} 
	//..... Some code here

	if ($tree_id > 0) {
		if (!is_tree_allowed($tree_id)) {
			header('Location: permission_denied.php');
			exit;
		}
		// Vulnerable function is called here
		grow_right_pane_tree($tree_id, $node_id, $hgdata);
	}

Из приведенного выше фрагмента кода видно, что функция выполняется, если $tree_id > 0. Все шаги можно описать ниже:

  • Сначала программа берет значение параметра action из ввода пользователя и попадает в оператор switch/case.

  • В случае tree_content код берет параметр запроса node от пользователя. Затем он разделяет ввод по символу -, сохраняет результат в $part и, наконец, проверяет, присутствует ли tree_anchor в node. Если все условия совпадают, $tree_id будет присвоен как второй элемент в $part. Например, tree_content-1 или 1-2-tree_content будут корректны, и значение $tree_id будет соответствующим.

Таким образом, наш URL будет выглядеть так: http://localhost:80?action=tree_content&node=tree_anchor-1. Теперь пришло время проанализировать функцию grow_right_pane_tree(), чтобы использовать ошибку.

grow_right_pane_tree_analyze

Уязвимый параметр — rfilter, который напрямую передается в операнд RLIKE внутри предложения WHERE. Что делает его уязвимым, так это то, что rfilter заключен в двойные кавычки ", однако параметр проверяется функцией html_validate_tree_vars(), которая выглядит так:

root@kitploit:~
function  html_validate_tree_vars() {
/* ================= input validation and session storage ================= */
$filters = array(
	// .......
	'rfilter' => array(
			'filter' => FILTER_VALIDATE_IS_REGEX,
			'pageset' => true,
			'default' => '',
			),
	// ..........
	);

	validate_store_request_vars($filters, 'sess_grt');
	/* ================= input validation ================= */
	// ........
}

Тип фильтра для rfilter установлен в FILTER_VALIDATE_IS_REGEX и сохранен внутри $filters, затем $filter передается в функцию validate_store_request_vars(). Давайте углубимся в эту функцию — это раскроет остальную часть нашего анализа:

root@kitploit:~
function validate_store_request_vars($filters, $sess_prefix = '') {
	// .......
	if (cacti_sizeof($filters)) {
		foreach($filters as $variable => $options) {
		// ....
		elseif ($options['filter'] == FILTER_VALIDATE_IS_REGEX) {
					if (is_base64_encoded($_REQUEST[$variable])) {
						$_REQUEST[$variable] = base64_decode($_REQUEST[$variable]);
					}
					$valid = validate_is_regex($_REQUEST[$variable]);
					if ($valid === true) {
						$value = $_REQUEST[$variable];
					} else {
						$value = false;
						$custom_error = $valid;
					}
				}
// ........
function validate_is_regex($regex) {
// ........
    if (@preg_match("'" . $regex . "'", NULL) !== false) {
		ini_set('track_errors', $track_errors);
		return true;
	}

Функция validate_store_request_vars() отвечает за проверку пользовательского ввода. Когда тип filter равен FILTER_VALIDATE_IS_REGEX, функция вызывает validate_is_regex() с функцией preg_match, чтобы проверить, является ли наш ввод корректным. Согласно документации PHP:

preg_match() возвращает 1, если pattern соответствует заданному subject, 0 — если нет, или false в случае ошибки.

В операторе if используется строгое сравнение, то есть только при возникновении ошибки мы не попадаем внутрь if. $regex, то есть наш ввод, заключен между двумя одинарными кавычками '. Намерение разработчиков — предотвратить внедрение пользователем одинарной кавычки ', которая часто используется для начала атаки SQL-инъекции. Это кажется надёжной реализацией безопасности, но давайте еще раз посмотрим, как rfilter передается в предложение WHERE:

root@kitploit:~
$sql_where  .=  ' (gtg.title_cache RLIKE "'  .  get_request_var('rfilter') .  '" OR gtg.title RLIKE "'  .  get_request_var('rfilter') .  '")';

О-хо, усилия по валидации пользовательского ввода становятся бессмысленными, так как rfilter заключен в двойные кавычки ", что позволяет атакующим легко внедрить ", чтобы выйти из оператора.

Поняв, почему это уязвимо, теперь мы углубимся в запрос, куда передается $sql_where.

grow_right_pane_tree_analyze

После того как $sql_where назначен, программа вызывает функцию get_allowed_tree_header_graphs(). Функция будет выглядеть так:

root@kitploit:~
function  get_allowed_tree_header_graphs($tree_id, $leaf_id = 0, $sql_where = '', $sql_order = 'gti.position', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	// ......
	if ($sql_where != '') {
		$sql_where = " AND ($sql_where)";
}
	$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;
	
	$graphs = db_fetch_assoc("SELECT gti.id, gti.title, gtg.local_graph_id, h.description, gt.name AS template_name, gtg.title_cache, gtg.width, gtg.height, gl.snmp_index, gl.snmp_query_id
	FROM graph_templates_graph AS gtg
	INNER JOIN graph_local AS gl
	ON gl.id = gtg.local_graph_id
	INNER JOIN graph_tree_items AS gti
	ON gti.local_graph_id = gl.id
	LEFT JOIN graph_templates AS gt
	ON gt.id = gl.graph_template_id
	LEFT JOIN host AS h
	ON h.id = gl.host_id
	$sql_where
	$sql_order
	$sql_limit");

	$sql = "SELECT  COUNT(*)
	FROM graph_templates_graph AS gtg
	INNER JOIN graph_local AS gl
	ON gl.id=gtg.local_graph_id
	INNER JOIN graph_tree_items AS gti
	ON gti.local_graph_id=gl.id
	LEFT JOIN graph_templates AS gt
	ON gt.id=gl.graph_template_id
	LEFT JOIN host AS h
	ON h.id=gl.host_id
	$sql_where";
	$total_rows = get_total_row_data($user_id, $sql, array(), 'graph');
	return  $graphs;
}

Здесь $sql_where передаётся в запрос. Обратите внимание, что переменная назначается ещё 2 раза перед передачей: $sql_where = " AND ($sql_where)"; и $sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;. В сочетании с первым присваиванием вот как окончательно формируется $sql_where:

root@kitploit:~
$sql_where  .=  ' (gtg.title_cache RLIKE "'  .  get_request_var('rfilter') .  '" OR gtg.title RLIKE "'  .  get_request_var('rfilter') .  '")';
$sql_where = " AND ($sql_where)";
$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;

После этих 3 присваиваний $sql_where будет выглядеть так:

root@kitploit:~
$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id) AND ((gtg.title_cache RLIKE "' . get_request_var('rfilter') . '" OR gtg.title RLIKE "' . get_request_var('rfilter') . '"))";

Запрос, в который передается $sql_where, выглядит громоздким, но мы можем создать другой простой запрос с похожей структурой:

select * from users WHERE (TRUE) AND ((username RLIKE "' get_request_var('rfilter') '" -- что-то там дальше не обязательно))

Чтобы успешно внедрить полезную нагрузку, нам нужно выйти из " и )). Поэтому такая нагрузка может сработать: "));SELECT SLEEP(5) --. Наша нагрузка будет выглядеть так: select * from users WHERE (TRUE) AND **((username RLIKE "'"));SELECT SLEEP(5) --. Выглядит хорошо! Попробуем на Cacti

failed_injection_1

Причина, по которой сервер не ожидает 2 секунды, в том, что мы вызвали ошибку в функции preg_match(). Давайте снова посмотрим на функцию

root@kitploit:~
if (@preg_match("'"  .  $regex  .  "'", NULL) !== false) {
ini_set('track_errors', $track_errors);
return  true;
}

Эта функция принимает наш ввод как регулярное выражение. А ) используется для группировки набора символов. Поскольку она не закрыта, функция вернула ошибку. С другой стороны, если мы попробуем "(());SELECT SLEEP(5) --, мы не сможем выйти из (( в SQL-запросе. Однако есть хитрость, и это "OR"(("));SELECT SLEEP(5) --. Позвольте мне объяснить: я оборачиваю (( в двойные кавычки, чтобы сделать их строкой в SQL-запросе, и использую OR для сопоставления с username перед ними. Затем двойная кавычка " в регулярном выражении считается просто обычным символом, поэтому мы можем использовать эту особенность, чтобы выйти из SQL-запроса.

successful_injection

Основываясь на времени отклика, мы можем убедиться, что успешно внедрили SQL в базу данных. Это критическая уязвимость SQL-инъекции. Злоумышленник может использовать эту ошибку, чтобы перехватить учетную запись администратора или даже удаленно выполнить команды.

Устранение

Как обычно, уязвимости внедрения в целом возникают из-за отсутствия проверки ввода. Однако в этом случае разработчики применили валидацию, но некорректно. Чтобы это исправить, мы просто оборачиваем параметр rfilter внутри одинарной кавычки ' вместо двойной "

fix

С этим простым исправлением усилия по валидации пользовательского ввода теперь будут работать должным образом. Давайте снова попробуем нагрузку, чтобы увидеть, осталась ли уязвимость.

fixed_request

С той же нагрузкой, но теперь ответ очень быстрый, что означает, что мы больше не можем внедрить SQL-команду.


Из этого анализа CVE мы можем понять, что даже незначительная ошибка может привести к катастрофическим последствиям. Мы должны тщательно проверять код и регулярно тестировать наш сайт, чтобы делать его всё более и более защищенным. Это конец анализа, надеюсь, вы узнали что-то полезное сегодня. Удачного хакинга!

Скачать инструмент