flawopen.com/Incidents/postgresql-psql-injection-beyondtrust-treasury-december-2024
CVE-2025-1094 : injection SQL dans psql PostgreSQL
Un UTF-8 invalide contournait libpq lors de la création d’entrées psql avec le texte échappé : injection SQL ou \!. Corrigé le 13 févr. 2025.
Imaginez un traducteur chargé de mettre entre guillemets tout ce que vous dites, pour que rien ne soit pris pour un ordre. Vous découvrez que si vous lui donnez un mot dans un alphabet qu'il ne connaît pas, il s'embrouille et oublie un des guillemets. Ce que vous avez dit sort alors des guillemets et est lu comme une instruction, alors que le traducteur a fait exactement ce pour quoi il était conçu avec toutes les entrées testées jusque-là.
Concepts Clés et Termes
PQescapeLiteral- Une fonction de libpq qui met une valeur entre apostrophes et l'échappe pour l'insérer dans du texte SQL. PQescapeIdentifier, PQescapeString et PQescapeStringConn étaient aussi concernées.
UTF-8 invalide- Une suite d'octets mal formée dans l'encodage déclaré. Le code d'échappement et psql n'étaient pas d'accord sur le nombre de caractères suivants que ces octets absorbent.
Méta-commande psql- Une commande avec barre oblique inverse traitée par le client psql et non par le serveur. \! exécute le reste de la ligne comme commande du shell du système.
CVE-2024-12356- Une faille critique d'injection de commandes dans Privileged Remote Access et Remote Support de BeyondTrust. Dans les tests de Rapid7, obtenir une exécution de code par ce biais exigeait
CVE-2025-1094. Paramètre lié- Une valeur envoyée au serveur séparément du texte SQL, si bien qu'aucune décision de guillemets ou d'échappement n'est jamais prise à son sujet.
Correspondances de référentiels
Chronologie de l'Incident
BeyondTrust publie l'avis BT24-10 sur CVE-2024-12356 après enquête sur la compromission de certaines instances SaaS de Remote Support ; le Trésor américain a ensuite indiqué que cet incident avait atteint ses systèmes.
La CISA ajoute CVE-2024-12356 à son catalogue Known Exploited Vulnerabilities.
PostgreSQL publie les versions 17.3, 16.7, 15.11, 14.16 et 13.19 corrigeant CVE-2025-1094, et Rapid7 publie son analyse.
Déroulement de l'Attaque Étape par Étape
L'entrée atteint une fonction d'échappement
Une application prend un texte influencé par l'attaquant et le passe à PQescapeLiteral ou à une fonction voisine de libpq pour construire une requête SQL.
L'UTF-8 invalide cache une apostrophe
D'après l'avis de PostgreSQL, une séquence multioctet invalide placée avant une apostrophe amenait la routine d'échappement à laisser cette apostrophe non échappée.
psql ferme la chaîne trop tôt
Quand la requête est passée à psql, l'apostrophe non échappée ferme le littéral de chaîne, et le reste de l'entrée est interprété comme du SQL ou des commandes psql.
Une méta-commande lance un shell
Rapid7 a montré que le texte injecté peut contenir la méta-commande \!, que psql exécute comme commande système sur la machine où il tourne.
Code Source : Vulnérable vs Sécurisé
Python illustratif avec psycopg 3, pas le code de BeyondTrust. La version vulnérable reproduit le schéma risqué cité par l'avis : échapper une valeur avec libpq, l'insérer dans du texte SQL et envoyer ce texte à psql. La version durcie supprime les deux moitiés : elle rejette l'UTF-8 mal formé avant tout traitement et envoie la valeur comme paramètre lié via un vrai pilote, si bien qu'il n'y a ni étape d'échappement à tromper ni interpréteur de méta-commandes psql sur le chemin. Mettre à jour libpq reste nécessaire pour tout code qui échappe encore.
import subprocess
import psycopg
def lookup_user(conn: psycopg.Connection, name: bytes) -> bytes:
# PQescapeLiteral de libpq (avant 17.3/16.7/15.11/14.16/13.19) ne
# neutraliser un guillemet masqué derrière une séquence d’octets UTF-8 invalide.
literal = conn.pgconn.escape_literal(name)
script = b"SELECT id FROM users WHERE name = " + literal + b";\n"
# psql exécute son entrée comme un script, y compris les méta-commandes telles que \!
result = subprocess.run(["psql", "-At", "-d", "app"], input=script,
capture_output=True, check=True)
return result.stdout
import psycopg
MAX_NAME_CHARS = 128
def lookup_user(conn: psycopg.Connection, name: bytes) -> list[int]:
try:
text = name.decode("utf-8") # reject malformed input at the boundary
except UnicodeDecodeError:
raise ValueError("name is not valid UTF-8") from None
if len(text) > MAX_NAME_CHARS:
raise ValueError("name too long")
with conn.cursor() as cur:
# La valeur est transmise comme paramètre lié, séparément du texte SQL,
# et aucun processus psql (ni donc aucun analyseur de méta-commandes) n’intervient.
cur.execute("SELECT id FROM users WHERE name = %s", (text,))
return [row[0] for row in cur.fetchall()]
Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Cherchez dans le code les appels subprocess à psql dont l'entrée est construite à partir de données applicatives, remplacez-les par un pilote de base de données avec paramètres liés et ajoutez une règle de CI qui échoue sur tout nouvel appel.
- ✓Rejetez à la frontière toute entrée mal formée dans son encodage déclaré, avant qu'elle n'atteigne un analyseur, une fonction d'échappement ou la base ; ajoutez un test qui envoie b'\xc0\'' et attend une erreur de validation.
- ✓Mettez à jour libpq et les outils clients, pas seulement le serveur :
CVE-2025-1094est corrigée dans PostgreSQL 17.3, 16.7, 15.11, 14.16 et 13.19, et les paquets clients des serveurs applicatifs doivent aussi être mis à jour. - ✓Là où l'échappement ne peut pas encore être supprimé, ajoutez un test de non-régression qui envoie de l'UTF-8 invalide suivi d'une apostrophe dans le chemin d'échappement et vérifie que le résultat est rejeté ou correctement protégé.
- ✓Considérez les outils d'accès distant à privilèges comme faisant partie de votre surface d'attaque : restreignez leurs interfaces d'administration, appliquez les correctifs dès l'avis de l'éditeur et surveillez l'usage de leurs clés d'API depuis des emplacements inattendus.
Sources
- PostgreSQL — avis de sécurité pour CVE-2025-1094
- Rapid7 — CVE-2025-1094 : injection SQL dans psql de PostgreSQL, trouvée en analysant la CVE-2024-12356 de BeyondTrust
- Injection SQL — référence complète de la classe en 11 langages
- Injection de commandes — référence complète de la classe
- Les procédures stockées empêchent-elles l'injection SQL ?