Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2023-39361 — CVE-2023-39361에 대한 단계별 분석 및 실습 환경 설정, Cacti v1.2.24의 인증되지 않은 SQL 인젝션 취약점의 공격 및 완화 과정 안내. | Kitploit
도구/GitHubGitHub/hpt-intern-task-submission/cve-2023-39361
Vulnerability AnalysisExploitationWeb Application ExploitationLearning & EducationLabs & Practice
GitHubhpt-intern-task-submission/cve-2023-39361

CVE-2023-39361

CVE-2023-39361에 대한 단계별 분석 및 실습 환경 설정, Cacti v1.2.24의 인증되지 않은 SQL 인젝션 취약점의 공격 및 완화 과정 안내.

저장소 보기

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
102년 전아직 검토되지 않음
공유

[CVE-2023-39361] Cacti v1.2.24의 인증되지 않은 SQL 인젝션

개요

Cacti는 PHP, MySQL/MariaDB로 작성된 오픈소스 운영 모니터링 도구로, 친숙한 인터페이스를 제공합니다. 이 취약점은 2023년에 발견되었으며, 1.2.24 이전의 모든 버전에 영향을 미칩니다. 이 보안 결함은 SQL 쿼리에 값을 삽입할 때 부적절한 구현으로 인해 발생합니다. 이는 공격자가 데이터베이스를 수정하고 원격으로 코드를 실행할 수 있게 하는 심각한 SQL 인젝션 취약점입니다.

실습 환경 설정:

이 분석에서는 간편함을 위해 Docker에서 Cacti를 실행하겠습니다. 먼저 아래와 같이 docker-compose.yml 파일을 만들고 docker-compose up -d 명령어를 실행합니다.

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:

컨테이너를 빌드한 후, http://localhost:80으로 이동하여 Cacti에 접속할 수 있습니다. 다음 단계는 Cacti를 1.2.24 버전으로 업데이트하는 것입니다. 업그레이드 스크립트는 여기에서 다운로드할 수 있습니다: https://pastebin.com/NfRiHLjR. docker-compose.yml 파일과 같은 디렉터리에 해당 파일을 저장하고 upgrade_cacti.sh로 이름을 변경합니다. 그런 다음 다음 두 명령어를 실행합니다:

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이며, 이 파일에 접근하려면 guest 사용자만 있으면 되므로 누구나 이 취약점을 악용할 수 있습니다. 이 파일에서 안전하지 않은 함수는 grow_right_pane_tree()입니다.

grow_right_pane_tree

이 함수를 자세히 살펴보기 전에, 함수가 어떻게 실행되는지 거꾸로 추적해야 합니다.

<?php
switch (get_nfilter_request_var('action')) {
//.... 
// 다른 많은 case
case  'tree_content':
	//..... 여기 약간의 코드
	// ---------
	if (isset_request_var('node')) {
		$parts = explode('-', sanitize_search_string(get_request_var('node')));
		// 트리 앵커 확인
		if (strpos(get_nfilter_request_var('node'), 'tree_anchor') !== false) {
			$tree_id = $parts[1];
			$node_id = 0;
		} 
	//..... 여기 약간의 코드

	if ($tree_id > 0) {
		if (!is_tree_allowed($tree_id)) {
			header('Location: permission_denied.php');
			exit;
		}
		// 취약한 함수가 여기에서 호출됨
		grow_right_pane_tree($tree_id, $node_id, $hgdata);
	}

위 코드 조각에서 볼 수 있듯이, $tree_id > 0일 때 함수가 실행됩니다. 각 단계는 다음과 같이 설명할 수 있습니다:

  • 먼저, 프로그램은 사용자 입력에서 action 매개변수의 값을 가져와 switch/case 문을 실행합니다.

  • tree_content case에서 코드는 사용자로부터 요청 매개변수 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로, WHERE 절 내부의 RLIKE 피연산자에 직접 전달됩니다. 취약하게 만드는 요인은 rfilter가 큰따옴표 "로 감싸여 있지만, 매개변수가 html_validate_tree_vars() 함수에 의해 검사된다는 점입니다. 이 함수는 다음과 같습니다:

function  html_validate_tree_vars() {
/* ================= 입력 검증 및 세션 저장 ================= */
$filters = array(
	// .......
	'rfilter' => array(
			'filter' => FILTER_VALIDATE_IS_REGEX,
			'pageset' => true,
			'default' => '',
			),
	// ..........
	);

	validate_store_request_vars($filters, 'sess_grt');
	/* ================= 입력 검증 ================= */
	// ........
}

rfilter의 필터 유형은 FILTER_VALIDATE_IS_REGEX로 설정되어 $filters에 저장된 후, $filter가 validate_store_request_vars() 함수로 전달됩니다. 이 함수를 자세히 살펴보겠습니다. 이 함수가 분석의 나머지 부분을 밝혀줄 것입니다:

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() 는 pattern이 주어진 subject와 일치하면 1을 반환하고, 일치하지 않으면 0을 반환하며, 실패 시 false 를 반환합니다.

if 문은 엄격한 비교를 사용하므로, 오류가 발생한 경우에만 if 문 안으로 들어가지 않습니다. $regex(사용자 입력)는 두 개의 작은따옴표 ' 사이에 감싸져 있습니다. 개발자의 의도는 SQL 인젝션 공격을 시작하는 데 자주 사용되는 작은따옴표 '를 사용자가 주입하지 못하도록 방지하는 것입니다. 이는 견고한 보안 구현처럼 보이지만, rfilter가 WHERE 절에 전달되는 방식을 다시 한 번 살펴보겠습니다:

$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() 함수를 호출합니다. 이 함수는 다음과 같습니다:

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");
도구 다운로드