GitHub Actions Injections.

Author: Philipp Gayret

Date: 2022-09-18

Introduction

This entry contains tips on what you should and shouldn't do in GitHub Actions with bash.

Options & Errors

Bash has options that you can set, with set, notably:

By default, GitHub Actions runs shell: bash sections with these options. Note that bash is the default shell. However, when you invoke other bash scripts from your actions, you need to set these options yourself like so;

#!/bin/bash
set -e -o pipefail

All above options are part of action-setup-bash

Errors & Handling

When dealing with errors in bash from GitHub Actions it can be fairly difficult to pinpoint where an error occurred. Although you could throw in -x to trace all commands, it may be too noisy for end users of workflows. Luckily, bash has options for this;

In other words, these options will make sure that any errors that occur in functions or subshells are also caught by the trap command. We can configure the trap command to print the line number and command that caused the error like so;

#!/bin/bash
set -e -o pipefail
set -o errtrace -o functrace
trap_error_report() {
    lineno=$1
    command=$2
    echo "Erred in bash at line $lineno on command: $command" >&2
}

trap 'trap_error_report "${LINENO}" "${BASH_COMMAND}"' ERR

Now when you run a script that contains an error, you'll always get a pretty error message, such as;

Erred in bash at line 5 on command: false

All above options are part of action-setup-bash

Bash Injections

Code injection is a fairly common attack vector in GitHub Actions for workflows. Likely because GitHub's own documentation of how to use GitHub Actions is full of them.

If you are worried about the following;

  1. You have public repositories.
  2. These public repositories contain workflows.
  3. These workflows must run with higher permissions than the triggering actor.

Or

  1. Your organization's users may become compromised.
  2. Rather than git cloning all accessible repositories & organization data you believe an attacker can pivot with workflows.

Then you'll want to prevent situations where an attacker can take over workflows. Here's the first example workflow by GitHub on their page on how to get started with GitHub Actions.

name: GitHub Actions Demo
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - (...)

Unfortunately the suggested ${{ ... }} in run sections is a recipe for disaster; it's GitHub Actions syntax for a string replacement at the YAML level. That means if your branch name ends with, say, $(env), we get;

- run: echo "🔎 The name of your branch is demo-$(env) and your repository ...

Which is going to run env, printing out all environment variables on the runner like so;

It shouldn't be too difficult for an attacker to go from "env" to say a reverse shell on your privileged runner. There is a fix, and it's fairly simple; Move templating into environment variables, like so;

name: GitHub Actions Demo
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    env:
        EVENT_NAME: ${{ github.event_name }}
        RUNNER_OS: ${{ runner.os }}
        BRANCH_NAME: ${{ github.ref }}
        REPOSITORY: ${{ github.repository }}
    steps:
      - run: echo "🎉 The job was automatically triggered by a $EVENT_NAME event."
      - run: echo "🐧 This job is now running on a $RUNNER_OS server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is $BRANCH_NAME and your repository is $REPOSITORY."
      - (...)

This moves the templating out of bash, and into the more safe to use environment variables. Note that for example with github.ref, most characters other than spaces are allowed as branch or tags names, PR titles, usernames, and so forth. You only need $() or "; to perform such an injection. So, be careful with what you run through the templating.

Conclusion

That concludes the entry! Shoutout to GitHub Copilot for writing half of the blog (GitHub Co-Author?). Shoutout to DALL E 2 for making the art.