
Preuve de concept pour CVE-2026-44590, une injection de commande dans le workflow GitHub Actions de Sherlock permettant une exécution de code à distance (RCE) et l'exfiltration de GITHUB_TOKEN via pull_request_target.
Découvert et signalé par : Astaruf
Analyse complète : https://nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590/
Avis en amont : Avis GHSA de sherlock-project/sherlock
Entrée NVD : https://nvd.nist.gov/vuln/detail/CVE-2026-44590
Enregistrement CVE : https://www.cve.org/CVERecord?id=CVE-2026-44590
Ce dépôt contient la preuve de concept pour CVE-2026-44590, une injection de commande dans le workflow GitHub Actions validate_modified_targets.yml de sherlock-project/sherlock. N'importe quel utilisateur GitHub peut ouvrir une pull request qui déclenche l'exécution arbitraire de commandes dans le contexte CI privilégié, exfiltrer le GITHUB_TOKEN du workflow et auto-approuver la PR malveillante, le tout sans aucune interaction humaine.
Pour l'analyse technique complète (analyse de la cause racine, déroulement de l'exploitation, discussion sur l'impact et un chapitre sur ce qu'un attaquant pourrait faire dans des scénarios réels), consultez l'article de blog :
nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590
Ce README se concentre exclusivement sur le script PoC : ce qu'il fait, comment l'exécuter et à quoi s'attendre.
poc.pypoc.py est un script Python autonome unique (stdlib uniquement) qui automatise toute la chaîne d'attaque de bout en bout :
sherlock-project/sherlock si nécessairemaster du fork au commit précédant le correctif afin que le bug puisse être reproduit même après le correctif en amontinteractsh-client)GITHUB_TOKEN du rappel OAST (en --mode exfil) et le décode en clairVULNERABILITY CONFIRMED ou FIX VERIFIEDinteractsh-client)Il y a exactement une étape manuelle requise (cliquer sur la bannière « I understand my workflows » de GitHub la première fois par fork), car aucune API publique ne permet de la faire disparaître. Le script détecte ce cas et fait une pause avec une invite claire.
python3 poc.py --fork-owner <votre-nom-d-utilisateur-github>
Fork le dépôt (si nécessaire), synchronise avec l'amont (master corrigé), ouvre une PR malveillante, exécute la chaîne d'attaque et rapporte FIX VERIFIED car le workflow corrigé bloque la charge utile avant qu'aucune commande shell ne s'exécute.
python3 poc.py --fork-owner <votre-nom-d-utilisateur-github> --vulnerable
Identique à ci-dessus, mais ramène d'abord le master du fork au commit précédant le correctif (271608fb). Verdict attendu : VULNERABILITY CONFIRMED.
python3 poc.py --fork-owner <votre-nom-d-utilisateur-github> --vulnerable --mode exfil
La charge utile d'exfiltration envoie git config --list à l'OAST et dort pendant 180 secondes pour maintenir le workflow (et donc le GITHUB_TOKEN) actif. Pendant que le workflow dort, le script extrait le jeton du journal OAST, le décode et appelle immédiatement l'API GitHub pour approuver la PR. La PR se retrouve approuvée par github-actions[bot].
gh (GitHub CLI), authentifié :
gh auth login
gitinteractsh-client (optionnel mais recommandé). Lorsqu'il est installé, le script le lance automatiquement et vérifie le rappel dans le script :
go install github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
Si vous préférez utiliser votre propre point de terminaison OAST (Burp Collaborator, oast.fun via l'interface web, requestbin, etc.), transmettez-le avec --oast-url et l'étape de vérification automatique sera ignorée.Le script ne nécessite pas qu'un fork existe au préalable, il en crée un automatiquement.
--mode harmless (par défaut)La charge utile est un seul POST curl avec une chaîne de confirmation statique. Aucun secret n'est lu, aucun appel API n'est effectué, le seul effet secondaire est le rappel OAST. Utilisez ce mode pour confirmer que la vulnérabilité existe sans exposer aucune information d'identification.
--mode exfilLa charge utile envoie git config --list (qui contient le GITHUB_TOKEN encodé en base64 sous http.https://github.com/.extraheader) à l'OAST, puis dort pendant 180 secondes. Le script ensuite :
x-access-token:ghs_XXXXXXXX....x-access-token: et utilise le jeton brut pour appeler POST /repos/<fork>/pulls/<n>/reviews avec la charge utile d'approbation standard ({"event":"APPROVE","body":"All checks passed. LGTM!"}).github-actions[bot], impossible à distinguer d'une automatisation CI légitime.Une fois l'approbation enregistrée, le script ignore le reste de la période de sommeil de 180 secondes du workflow, car la chaîne d'attaque est terminée et attendre que le runner expire n'apporte rien.
| Drapeau | Description |
|---|---|
--fork-owner <user> | Obligatoire. Nom d'utilisateur GitHub qui possède (ou possédera) le fork |
--fork-name <name> | Nom du dépôt du fork (par défaut : sherlock) |
--oast-url <url> | Point de terminaison OAST recevant le rappel. S'il est omis, le script lance automatiquement interactsh-client et exécute le verdict dans le script |
--mode harmless|exfil | Type de charge utile (par défaut : harmless) |
--vulnerable | Force la réinitialisation du master du fork au commit précédant le correctif (271608fb) avant l'exécution. Implique --no-sync |
--no-sync | Ignore la synchronisation du fork avec l'amont (utile lors du test d'un commit épinglé) |
--base-branch <name> | Branche cible de la PR sur le fork (par défaut : master) |
--keep-branch | Ne supprime pas la branche PoC après la fin |
--no-poll | Ignore l'interrogation de l'exécution du workflow et quitte après la création de la PR |
Par conception, le PoC ouvre la PR depuis une branche du fork vers le master du même fork. Il ne cible pas directement sherlock-project/sherlock. Il y a deux raisons à cela.