GitOops is a tool to help attackers and defenders identify lateral movement and privilege escalation paths in GitHub organizations by abusing CI/CD pipelines and GitHub access controls.

It works by mapping relationships between a GitHub organization and its CI/CD jobs and environment variables. It’ll use any Bolt-compatible graph database as backend, so you can query your attack paths with openCypher:

MATCH p=(:User{login:”alice”})-[..5]->(v:EnvironmentVariable) WHERE v.name =~ “.SECRET.*”
RETURN p

Docs

Overview

In addition to mapping relationships between your users, teams and repositories, GitOops maps relationships between those and environment variables in your CI/CD systems.

The following CI/CD systems are currently supported:

  • GitHub Actions
  • CircleCI

On top of this, GitOops ingests CI/CD configuration files from repositories for other popular CI/CD systems, enabling less refined queries on those.

Finally, GitOops will also map webhooks and status checks from commits to a repository’s pull requests and default branch. These allow you to find integrations that are typically configured server-side (e.g. AWS CodeBuild).

Install, Build & Run

You can build GitOops yourself or use our binaries.

You may also consider using it as a package if you want to run some custom ingestion.

Install

Download the latest release for your OS from the releases page or:

$ export OS=linux # or macos/windows
$ curl -Lso gitoops “https://github.com/ovotech/gitoops/releases/latest/download/gitoops-$OS”

Build

$ go version
go version go1.16.6 linux/amd64

$ git clone git@github.com:ovotech/gitoops.git
$ cd gitoops
$ make
$ ./gitoops
Usage: ./gitoops [SUBCOMMAND] [OPTIONS]…
Available subcommands:
circleci
enrich
github

CLI

Database

You will need a Bolt-compatible database. We provide a docker-compose file for Neo4j.

IMPORTANT: This sets up an unauthenticated Neo4j instance listening on localhost. You’re recommended to set a password.

$ docker-compose -f docker-compose.yml up -d

Ingest GitHub data

GitOops uses a Personal Access Token (PAT) to ingest GitHub data. You will need the read:org and repo (Full control of private repositories) scopes.

To get full coverage you should use an organization owner PAT. You can use an organization member PAT but you will get only partial coverage.

$ gitoops github \
-debug \
-organization fakenews \
-neo4j-password $NEO4J_PASSWORD \
-neo4j-uri=”neo4j://localhost:7687″ \
-token $GITHUB_TOKEN \
-ingestor default \
-ingestor secrets \
-session helloworld

Most parameters should be self-explanatory.

Please check gitoops github -h for more information on the -ingestor.

The session is just a unique identifier for this run of the ingestor. You can use this to remove old nodes and relationships that are no longer relevant (by removing any nodes and relationships that don’t have the latest session identifier from your database).

Note on Rate Limits

If you are targeting a large GitHub organization, you may encounter rate limits. If this happens you can use the -ingestor flags to limit the information you are ingesting at a time.

The following ingestors need to run first and in this particular order:

  • Organizations
  • Teams
  • Users
  • Repos

Order doesn’t matter for other ingestors.

GitHub Enterprise Server

If you are targetting a self-hosted GitHub Enterprise Server, you will want to set the -github-rest-url and -github-graphql-url parameters. These default to the GitHub cloud URLs.

Ingest CircleCI data

Unfortunately, the documented CircleCI REST API doesn’t give everything we want. Luckily there’s a “hidden” GraphQL API we can access with a cookie. With your browser, navigate to the CircleCI web UI and fetch your ring-session cookie. You should be able to find this in a request to the graphql-unstable endpoint when loading some pages.

$ export CIRCLECI_COOKIE=RING_SESSION_COOKIE_VALUE
$ gitoops circleci \
-debug \
-organization fakenews \
-neo4j-password $NEO4J_PASSWORD \
-neo4j-uri=”neo4j://localhost:7687″ \
-cookie=$CIRCLECI_COOKIE \
-session helloworld

Data enrichment

We do some very crude “enriching” of data. After you’ve ingested GitHub proceed to:

$ gitoops enrich \
-debug \
-organization fakenews \
-session helloworld \
-neo4j-password $NEO4J_PASSWORD \
-neo4j-uri=”neo4j://localhost:7687″

Schema

This file is generated by ./scripts/generate_schema_doc.py

Branch Protection Rule

Properties

KeyType
idSTRING
patternSTRING
requiresReviewsBOOLEAN
sessionSTRING

Relationships

OutboundInbound
HAS_BRANCH_PROTECTION_RULE

Circle CI Context

Properties

KeyType
allMembersBOOLEAN
idSTRING
nameSTRING
sessionSTRING

Relationships

OutboundInbound
EXPOSES_ENVIRONMENT_VARIABLEHAS_ACCESS_TO_CIRCLECI_CONTEXT

Circle CI Project

Properties

KeyType
idSTRING
repositorySTRING
sessionSTRING

Relationships

OutboundInbound
EXPOSES_ENVIRONMENT_VARIABLEHAS_CI

Environment

Properties

KeyType
customBranchPolicyBOOLEAN
idSTRING
nameSTRING
protectedBranchesBOOLEAN
sessionSTRING
urlSTRING

Relationships

OutboundInbound
EXPOSES_ENVIRONMENT_VARIABLEHAS_ENVIRONMENT

Environment Variable

Properties

KeyType
idSTRING
sessionSTRING
truncatedValueSTRING
variableSTRING

Relationships

OutboundInbound
EXPOSES_ENVIRONMENT_VARIABLE

File

Properties

KeyType
idSTRING
pathSTRING
sessionSTRING
textSTRING

Relationships

OutboundInbound
HAS_CI_CONFIGURATION_FILE

Organization

Properties

KeyType
idSTRING
loginSTRING
sessionSTRING
urlSTRING

Relationships

OutboundInbound
EXPOSES_ENVIRONMENT_VARIABLEIS_MEMBER_OF
OWNED_BY

Repository

Properties

KeyType
databaseIdINTEGER
idSTRING
isArchivedBOOLEAN
isPrivateBOOLEAN
nameSTRING
sessionSTRING
urlSTRING

Relationships

OutboundInbound
EXPOSES_ENVIRONMENT_VARIABLEHAS_PERMISSION_ON
HAS_WEBHOOK
HAS_STATUS_CHECK
OWNED_BY
HAS_BRANCH_PROTECTION_RULE
HAS_CI
HAS_CI_CONFIGURATION_FILE
HAS_ENVIRONMENT

StatusCheck

Properties

KeyType
contextSTRING
hostSTRING
idSTRING
sessionSTRING

Relationships

OutboundInbound
HAS_STATUS_CHECK

Team

Properties

KeyType
idSTRING
nameSTRING
sessionSTRING
slugSTRING
urlSTRING

Relationships

OutboundInbound
HAS_PERMISSION_ONIS_MEMBER_OF
HAS_ACCESS_TO_CIRCLECI_CONTEXT

User

Properties

KeyType
idSTRING
loginSTRING
sessionSTRING
urlSTRING

Relationships

OutboundInbound
HAS_PERMISSION_ON
IS_MEMBER_OF
HAS_ACCESS_TO_CIRCLECI_CONTEXT

Webhook

Properties

KeyType
eventsLIST OF STRING
hostSTRING
idSTRING
nameSTRING
sessionSTRING
targetSTRING
urlSTRING

Relationships

OutboundInbound
HAS_WEBHOOK