
Stirling-PDF v3.2.0 - Critique - Distant - H2 RUNSCRIPT résiduel - CREATE OR REPLACE ALIAS import-database
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · stirling-h2-import-leftover
Stirling-PDF 3.2.0 - Stirling Tools
Classe : résidu RUNSCRIPT H2 de GHSA-mrr8. Portée : ROLE_ADMIN distant.
POST /api/v1/database/import-database est protégé par @PreAuthorize("hasRole('ADMIN')"). validateSqlContent est censé bloquer CREATE ALIAS. La liste de refus est CREATE\s+(FORCE\s+)?ALIAS. Le parseCreate de H2 autorise OR REPLACE avant ALIAS, donc CREATE OR REPLACE ALIAS … FOR "java.lang.…" correspond à la liste d'autorisation \bCREATE\b et échappe à la liste de refus. Le validateur assainit une copie. verifyBackup et la production exécutent RUNSCRIPT FROM ? sur le fichier original.
GHSA-mrr8-934j-4g8m / CVE-2026-85714 a fermé les chaînes d'origine FILE_READ / CREATE ALIAS / CSVWRITE. Ce résidu est la faille OR REPLACE sur ce même validateur.
Un import ROLE_ADMIN crée un alias Java que la liste de refus est censée bloquer. Un téléchargement de sauvegarde ultérieur contient le SQL de l'attaquant. Le RCE hôte via FILE_READ / FileUtils issu de l'avis d'origine est déduit, non prouvé en laboratoire dans ce pack.
| ID | pas encore de CVE |
| CWE | CWE-94 / CWE-184 |
| CVSS | 9.1 CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H |
| Produit | Stirling-PDF |
| Affecté | v3.2.0 (a7e593728dd9aa81e75318a949789d3c2e9ee754), image stirlingtools/stirling-pdf:3.2.0-fat |
| Auth | ROLE_ADMIN, H2 embarqué par défaut. Premier admin par défaut admin / stirling jusqu'à modification |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18152 uniquement |
Résidu de la série Stirling-PDF v3.2.0 leftovers. Voisin : stirling-storage-folder-idor (racine de stockage implicite du dossier de traitement). Ce pack est le résidu de l'import H2.
ROLE_ADMIN sur login-on proprietary/fat avec le H2 embarqué par défaut. Le premier admin par défaut est admin / stirling lorsque initialLogin est vide. Aucun accès non authentifié n'est nécessaire. L'image Lite et les sources de données PostgreSQL n'embarquent pas ce contrôleur.
Table des privilèges (exhaustive). Même barème que Signal-Server Dropwizard admin : méthode + chemin, ce que cet appel accorde, reconnaissance vs intégrité vs disponibilité, prouvé en labo vs déduit, surface adjacente qui reste fermée.
| Axe | Méthode + chemin | Accorde | Provenance |
|---|---|---|---|
| Intégrité | ROLE_ADMIN POST /api/v1/database/import-database avec CREATE OR REPLACE ALIAS lab_nano FOR "java.lang.System.nanoTime" plus INSERT INTO lab_w VALUES('STIRLING-H2-IMPORT-WITNESS') | 200 importIntoDatabaseSuccessed. Alias Java créé. SQL de l'attaquant validé sous RUNSCRIPT | Prouvé en labo |
| Recon | ROLE_ADMIN GET /api/v1/database/createDatabaseBackup puis GET /api/v1/database/download/backup_202610172328.sql | La sauvegarde de 50432 octets contient STIRLING-H2-IMPORT-WITNESS | Prouvé en labo |
| Recon / fichier hôte | même import encapsulant FILE_READ('/etc/os-release') dans EXECUTE IMMEDIATE | 500 Backup verification failed. Octets de l'hôte absents d'une sauvegarde | Échec supplémentaire en labo |
| Intégrité / RCE | alias org.h2.store.fs.FileUtils plus CSVWRITE d'un .pth user-site Python, puis conversion office | Exécution de commandes sous le compte de service | Déduit de GHSA-mrr8. Non testé en labo ici |
| Disponibilité | aucun revendiqué | L'import peut remplacer les tables de l'application | Déduit. Pas l'oracle du labo |
Reste fermé sur cette version figée
| Axe | Méthode + chemin | Pourquoi c'est fermé |
|---|---|---|
| Intégrité | POST /api/v1/database/import-database non authentifié | 401 Authentication required. La sécurité de méthode ADMIN reste active quand HTTP est en permitAll |
| Intégrité | import ROLE_ADMIN d'un simple CREATE ALIAS | 500 SQL script contains disallowed operations and was rejected |
| Intégrité | image lite / DISABLE_ADDITIONAL_FEATURES | DatabaseController absent |
| Intégrité | source de données PostgreSQL personnalisée | @Conditional(H2SQLCondition.class) |
Je ne revendique pas de reverse shell, et je ne revendique pas FILE_READ sur l'hôte comme prouvé en labo. Ce qui reste, c'est l'oubli dans la liste de refus qui laisse passer CREATE OR REPLACE ALIAS … FOR "java.lang.…" via RUNSCRIPT.
v3.2.0 embarque toujours le validateur GHSA-mrr8. Je me suis arrêté sur ce tag et j'ai lu DENIED_PATTERNS. La ligne des alias est CREATE\s+(FORCE\s+)?ALIAS. Le parseur de H2 accepte CREATE OR REPLACE ALIAS. Les tests couvrent les chaînes évidentes CREATE ALIAS / FILE_READ(. Ils ne couvrent pas OR REPLACE.
Le labo se connecte en tant qu'admin par défaut, termine change-password-on-login, importe un CREATE ALIAS refusé (500), puis importe CREATE OR REPLACE ALIAS plus un témoin INSERT (200). Un téléchargement de sauvegarde contient le témoin.
Fausses pistes : considérer l'import non authentifié comme dans le périmètre (401) ; considérer EXECUTE IMMEDIATE + FILE_READ comme le reste (verifyBackup 500) ; considérer le CREATE ALIAS original non obfusqué comme toujours actif (il est refusé).
python3 stirling-h2-import-leftover-Abraxas-Labs.py
ou
cd lab
./run.sh
Ciblez uniquement http://127.0.0.1:18152. Image stirlingtools/stirling-pdf:3.2.0-fat. Docker requis. Connexion activée. Le lanceur fait un chdir dans lab/ et exécute run.sh.
IOC unauth-import http=401
IOC negative-import http=500 ... disallowed operations ...
IOC leftover-import bypass=create-or-replace-alias http=200 ... importIntoDatabaseSuccessed
IOC download file=backup_202610172328.sql http=200 bytes=50432 witness=1
SUCCESS stirling-h2-import-leftover negative-denied=yes bypass=create-or-replace-alias backup-has-witness=yes file=backup_202610172328.sql unauth-import=401 STIRLING-H2-IMPORT-WITNESS
Étendre la denylist afin que OR REPLACE ne puisse pas se trouver entre CREATE et ALIAS. Exécuter validateSqlContent sur les mêmes octets que RUNSCRIPT exécutera. Refuser EXECUTE IMMEDIATE sauf si le SQL interne est également analysé.
Pattern.compile("(?i)\\bCREATE\\s+(OR\\s+REPLACE\\s+)?(FORCE\\s+)?ALIAS\\b")
Relancez le client loopback contre une build corrigée : l'import résiduel doit renvoyer 400/500 comme le fait déjà CREATE ALIAS seul.