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 exec
s 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:
- The source of the volume mounted
build
directory is predictable per build - The
DOCKER_AUTH_CONFIG
is processed once for each created container - 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
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!