Gh Actions - Context Script Injections
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Entendiendo el riesgo
GitHub Actions renders expressions ${{ … }} before the step executes. El valor renderizado se pega en el programa del step (para run steps, un shell script). Si interpolas input no confiable directamente dentro de run:, el atacante controla parte del programa del shell y puede ejecutar comandos arbitrarios.
Docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions and contexts/functions: https://docs.github.com/en/actions/learn-github-actions/contexts
Puntos clave:
- El renderizado ocurre antes de la ejecución. El script de run se genera con todas las expresiones resueltas y luego es ejecutado por el shell.
- Muchos contextos contienen campos controlados por el usuario dependiendo del evento que lo desencadena (issues, PRs, comments, discussions, forks, stars, etc.). Consulta la referencia de untrusted input: https://securitylab.github.com/resources/github-actions-untrusted-input/
- El quoting del shell dentro de run: no es una defensa fiable, porque la inyección ocurre en la etapa de renderizado de la plantilla. Los atacantes pueden romper las comillas o inyectar operadores mediante input especialmente creado.
Patrón vulnerable → RCE en el runner
Vulnerable workflow (se activa cuando alguien abre un nuevo issue):
name: New Issue Created
on:
issues:
types: [opened]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: New issue
run: |
echo "New issue ${{ github.event.issue.title }} created"
- name: Add "new" label to issue
uses: actions-ecosystem/action-add-labels@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: new
Si un atacante abre un issue titulado $(id), el paso renderizado se convierte en:
echo "New issue $(id) created"
La sustitución de comandos ejecuta id en el runner. Salida de ejemplo:
New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created
Por qué las comillas no te salvan:
- Las expresiones se renderizan primero, luego se ejecuta el script resultante. Si el valor no confiable contiene $(…),
;,"/', o saltos de línea, puede alterar la estructura del programa a pesar de tus comillas.
Patrón seguro (shell variables via env)
Mitigación correcta: copia la entrada no confiable en una variable de entorno, luego usa la expansión nativa del shell ($VAR) en el script de ejecución. No vuelvas a insertar con ${{ … }} dentro del comando.
# safe
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: New issue
env:
TITLE: ${{ github.event.issue.title }}
run: |
echo "New issue $TITLE created"
Notas:
- Evita usar ${{ env.TITLE }} dentro de run:. Eso reintroduce el renderizado de templates en el comando y supone el mismo riesgo de inyección.
- Prefiere pasar entradas no confiables vía env: mapping y referenciarlas con $VAR en run:.
Superficies activables por el lector (trátalas como no confiables)
Las cuentas con solo permiso de lectura en repositorios públicos aún pueden desencadenar muchos eventos. Cualquier campo en los contextos derivados de estos eventos debe considerarse controlado por un atacante a menos que se demuestre lo contrario. Ejemplos:
- issues, issue_comment
- discussion, discussion_comment (las orgs pueden restringir las discusiones)
- pull_request, pull_request_review, pull_request_review_comment
- pull_request_target (peligroso si se usa incorrectamente, se ejecuta en el contexto del repo base)
- fork (cualquiera puede forkear repos públicos)
- watch (marcar un repo con star)
- Indirectamente vía cadenas workflow_run/workflow_call
Qué campos específicos están controlados por un atacante depende del evento. Consulta la guía de inputs no confiables de GitHub Security Lab: https://securitylab.github.com/resources/github-actions-untrusted-input/
Consejos prácticos
- Minimiza el uso de expressions dentro de run:. Prefiere env: mapping + $VAR.
- Si debes transformar la entrada, hazlo en el shell usando herramientas seguras (printf %q, jq -r, etc.), empezando siempre desde una variable de shell.
- Ten especial cuidado al interpolar nombres de branch, títulos de PR, nombres de usuario, labels, títulos de discusión y PR head refs en scripts, flags de línea de comandos o rutas de archivos.
- Para reusable workflows y composite actions, aplica el mismo patrón: mapear a env y luego referenciar $VAR.
Referencias
- GitHub Actions: A Cloudy Day for Security - Part 1
- GitHub workflow syntax
- Contexts and expression syntax
- Untrusted input reference for GitHub Actions
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks Cloud

