HTB - Laser

Zweilosec's writeup of the insane-difficulty Linux machine from https://hackthebox.eu

Overview

This Insane-difficulty machine from Hack The Box took me a lot longer to progress to the initial foothold than most boxes take to root! This machine had some very interesting avenues of approach that greatly differed from the standard enumeration and progression that most of the lower difficulty machines require. I had to research new protocols just to begin, and by the end had to write five python scripts to progress through the initial foothold and for later privilege escalation. All in all it was a fun, but very challenging ride!

Useful Skills and Tools

Using socat to redirect traffic to a port

  • The command below will redirect traffic intended for the local machine's port 22 and send it to port 22 on the machine at IP 172.17.0.1.

Enumeration

Nmap scan

I started my enumeration with an nmap scan of 10.10.10.201. The options I regularly use are: -p-, which is a shortcut which tells nmap to scan all ports, -sC is the equivalent to --script=default and runs a collection of nmap enumeration scripts against the target, -sV does a service scan, and -oA <name> saves the output with a filename of <name>.

The Nmap scan only showed three open ports: 22 - SSH, 9000, and 9001. I wasn't familiar with them so I searched for what uses ports 9000 and 9001. It turns out these are commonly used by printers.

Port 9000 & 9001

First I tried firing up a browser to see what kind of reply I might get from these ports. 9001 did not respond at all, while 9000 just sent back garbage characters (probably binary information).

Next, I connected with telnet but everything I sent just got back the reply ?. I did some further research to see what kind of vulnerabilities might be exposed by having these two ports open. There was plenty of information on how to exploit open printers.

One of the sources pointed out that if you can access a printer and also SNMP, then the system is pretty much yours already.

Unfortunately, it seemed as if SNMP was not enabled on this machine (or was using a non-standard port?) as this would have given a good path forward.

Printer Exploitation Toolkit (PRET)

After searching a bit more I came across a nice toolkit someone had put together for exploiting printers called the Printer Exploitation Toolkit (PRET).

https://github.com/RUB-NDS/PRET

Following the instructions, I was able to quickly get it up and running, and got a pret shell on the machine using the PS printer language. This only gave errors, so I switched to the PCL language and tried again. This time I was able to use the help command to get a list of further options to try.

Trying the ls command gave me an error message that said I could use put to upload files. None of the other commands gave anything further.

Due to its limited capabilities, PCL is hard to exploit from a security perspective unless one discovers interesting proprietary commands in some printer manufacturers's PCL flavour. The PRET tool implements a virtual, PCL-based file system which uses macros to save file content and metadata in the printer's memory. This hack shows that even a device which supports only minimalist page description languages like PCL can be used to store arbitrary files like copyright infringing material. Although turning a printer into a file sharing service is not a security vulnerability per se, it may apply as ‘misuse of service’ depending on the corporate policy.

After reading up about the difference between the printer languages I decided that this one probably didn't speak the binary language of vaporators...I mean PCL.

After testing all of the PCL commands and getting nothing I went back and tried the third language, PJL, and got another shell with many more commands available after using the help command.

This time I was able to navigate the file structure. In the folder /pjl/jobs there was a file named queued.

I made the mistake of cat-ing the queued file at first. Don't do this, unless you want to copy the extremely long base64 string and recreate the file yourself. Use get instead...

Use get. This will nicely download the file to your local machine. However, when I opened the file it was still a wall of base64 encoded text so I tried to decode it.

After removing the extra characters (that looked to indicate that it was supposed to be a python byte string) from the file I tried to base64 decode it, but it still seemed to be invalid encoding. I decided to keep enumerating to see if I could find anything to help me move forward.

I did seem to have read-write access on the local drive. I wondered if there was a way to write something malicious to the printer to execute code...

Most of the commands gave nothing useful back.

The command env showed me the values of the printer's environment variables. The line LPARM:ENCRYPTION MODE=AES [CBC] looked like it might be useful if I encountered encrypted passwords or such.

The info command seemed to give pretty much the same information as some of the other commands...meaning nothing useful.

The mirror command seemed to be pretty interesting, as it created a mirror of the print queue on my local machine. However, no one seemed to be sending new jobs to the printer.

Decoding the queued file

The nvram command had a dump operation which gave me what looked to be a possible encryption key.

I wasn't sure that the output was showing everything I needed so I started Wireshark and did a packet capture to see what other data was being dumped.

I got the above data points back from the capture. They looked like decimal encoded characters to me.

The key was decimal encoded, so I used CyberChef to decode it and got the key 13vu94r6643rv19u. For some reason the console output didn't decode it properly from the nvram command. (I'm also not sure what the 46 characters were in the middle of the two halves, but they weren't needed. Some sort of delimiter for the two halves?)

After going through pretty much all of the commands and finding very little useful information, I went back to the queued file I had downloaded. Since I now had a potential encryption key and had noted the encryption method was AES... which outputs base64 encoded material that wont decode properly...I figured I was on to something. I did some research to make sure I had all the information I needed to decode the file if it were AES-CBC encrypted.

I wrote a simple python script to decode the queued file after I stripped out the extraneous characters, and wrote it out to a file q-out. It took a few tries to do since there was no IV. I read that sometimes the IV is simply the last 16 byte section of the file, which worked to decode the file. Since the file was an odd number of byte-chunks, I had to strip off the beginning extra bytes to get it to decode.

The PDF document

The file that was in the print queue turned out to be a PDF document.

The PDF File contained instructions for interacting with a print API using gRPC on port 9000. It also contained the hostname printer.laserinternal.htb which I added to my /etc/hosts file.

The GRPC python client

Now that I knew what protocol to use to communicate with the server, I needed to figure out how it worked, and how to use this to enumerate the server more.

The first line of the file specifies that you're using proto3 syntax: if you don't do this the protocol buffer compiler will assume you are using proto2. This must be the first non-empty, non-comment line of the file.

In the documentation for grpc and protocol buffers there were examples of how to communicate with this type of service using Python.

Using this information, and the information from the PDF document I recovered, I created the above .proto file to tell the grpc client how to communicate with this particular service.

Next I ran the command from the documentation for making the python libraries that would enable communication with the server. This created two files: laser_pb2_grpc.py and laser_pb2.py.

I then created a python client grpc_client.py to connect to port 9000 on the server and send my request. I set the feed URL to be my machine to test the connection. Next I created a netcat listener to catch the return message.

I got a connection back to my netcat listener, but I wasn't sure what to do with it at that point. However, since I was able to entice the server to send me connections with information I controlled I figured I could try to do a Server Side Request Forgery (SSRF) attack and maybe get it to pull a malicious file from my system.

The PDF mentioned that I should get back the message 'Pushing feeds', but I did not get any messages back at all.

While doing some testing I forgot to start my listener, and got the above error message back since my computer refused the connection. The verbose error message came in handy later.

After redirecting the target to be the internal machine I got an error message with a new hostname. I added this to my /etc/hosts file. Sending the traffic to this hostname did not resolve my connection errors.

I was able to ping the machine using that hostname, so my connectivity wasn't the problem.

Python port scanner - internal machine

I decided that there may possibly be an internal port open that I couldn't reach, therefore the errors. I needed some way of enumerating the inside. Since I knew that a closed port gave a "Connection refused error" I was able to use this to filter responses.

I used the grpc_client.py as a base to build the port scanner. The scanner was very slow as it made a full connection, sent its message, and waited for a reply for every port. I thought about making the script multithreaded, but had to go out so I left it running instead.

I did find a good example of how to do a multi-threaded port scanner at https://github.com/gh0x0st/python3_multithreading. This person's code is also very clean and well structured so thanks to them for that in addition to the great examples!

When I got back home I found that there were five ports open, and I noticed that port 8983 responded with the message of feed: Pushing feeds that was expected from the PDF.

Solr exploitation

A search for port 8983 exploit led me to

Solr is an open source application from Apache which provides searching and indexing capabilities for large amounts of data. Using the code from the exploit and my client from earlier I crafted a python script that would connect to the server, change the configuration to enable code execute, then allow for execution of arbitrary commands specified as arguments to the script.

After a lot of trial and error, I got my code to work and set up a netcat listener. I made my payload a simple callback reverse shell and crossed my fingers.

Initial Foothold

I got a connection from the machine on my waiting nc listener! I quickly upgraded my shell and checked what context I was logged in as. I was a user named solr.

Next, I put my public key in the solr user's .ssh folder so I could log in with a real shell.

I noticed in /etc/passwd that there were only two users who could log in, solr and root.

User.txt

The file user.txt was in /home/solr as expected, however I was confused at first since this was not the user's home directory.

Path to Power (Gaining Administrator Access)

Enumeration as solr

After creating an SSH key I was able to log in. Upon login it told me the IP addresses for some interfaces, which I decided to check out, since docker containers often have security vulnerabilities.

There seemed to be a docker interface running on 172.17.0.1/16, and potentially a container that looked to be hosted in the 172.18.0.1/16 range.

I checked for processes running that might give me an indication at what may have been running in the containers, and noticed an interesting process associated with a container hosted at 172.18.0.2.

sshpass

Here, the sshpass program is being used to pass root's password to SCP in order to move the file jenkins-feed to a folder on the docker container. I opened the man page for sshpass to see if I could learn any more about how it worked and what it was doing.

sshpass is a utility designed for running ssh using the mode referred to as "keyboard-interactive" password authentication, but in non-interactive mode.

ssh uses direct TTY access to make sure that the password is indeed issued by an interactive keyboard user. Sshpass runs ssh in a dedicated tty, fooling it into thinking it is getting the password from an interactive user.

The command to run is specified after sshpass' own options. Typically it will be "ssh" with arguments, but it can just as well be any other command. The password prompt used by ssh is, however, currently hardcoded into sshpass.

Options If no option is given, sshpass reads the password from the standard input. The user may give at most one alternative source for the password:

SECURITY CONSIDERATIONS First and foremost, users of sshpass should realize that ssh's insistance on only getting the password interactively is not without reason. It is close to impossible to securely store the password, and users of sshpass should consider whether ssh's public key authentication provides the same end-user experience, while involving less hassle and being more secure.

The -p option should be considered the least secure of all of sshpass's options. All system users can see the password in the command line with a simple "ps" command. Sshpass makes a minimal attempt to hide the password, but such attempts are doomed to create race conditions without actually solving the problem. Users of sshpass are encouraged to use one of the other password passing techniques, which are all more secure.

Essentially it seemed as if this program was written to bypass the security of SSH's "user presence" check when passing plaintext passwords. From the man page for sshpass I found information that points out that there is a vulnerability in the implementation of the -p option. I tried searching on Google for more information about this race condition, but wasn't able to find anything related to exploiting this. I decided to look around and see if the source code was available to see if I could determine what this race condition was caused by and if I could exploit it.

I found the code for sshpass on GitHub: https://github.com/kevinburke/sshpass/blob/master/main.c

After analyzing the source code, I found out that the -p option takes in the original password from argument input and obfuscates it by replacing each character with a 'z', one character at a time. In the ps aux output I got earlier I saw the command sshpass -p zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz followed by the SCP command the password was being supplied to. Since the command is originally input with the password in plaintext, the race condition exists where the plaintext password must be able to be read from memory prior to being obfuscated.

I copied the string of 'z's from the output to check its length. The password was 32 characters long. I tried logging in with passwordpasswordpassword but no luck...

Getting root's password

I wrote a python script to read the contents of each process in /procs and see if they had a cmdline file in them. If the file existed then it looked in the data for the word sshpass. Then, if it found that command it would check to see if that process also contained multiple 'z' characters, and if not, would print the command line that made that process.

After waiting for quite some time, I got what I wanted. Cleaning up the output made the command:

The password was c413d115b3d87664499624e7826d8c5a.

I verified the length of the password I found to make sure that it matched what I found earlier.

Getting a root shell (on the container)

Using this password and SSH I was able to log in as root to the docker container. Next I tried a standard docker container privilege escalation method where I mount the root filesytem into the container.

First I verified that the docker program was on the host system (to copy to the container if need be).

Next I tried to copied the program to /dev/shm folder on the container, but unfortunately there was no space to copy the file.

I used the df command on the container to verify open space, and noticed it was just running memory space that was full (/dev/shm).

Since there seemed to be plenty of free space elsewhere in the filesystem I copied the file to /tmp instead.

Unfortunately while trying to use this to escalate privileges I ran into two problems: the service was not running on the container and I couldn't start it because I didn't have privileges to work with containers on the host.

clear.sh

I went back to reading some of the output I had gotten from my memory reading script to see if I noticed anything else that was useful.

While running my python script to find the password (before I put the check for zzz's) I noticed another process being run by sshpass that was executing a script clear.sh in the /tmp directory. At first I thought it was trying to run the script from the container, but a closer look revealed that there was a separator \x00 between root@172.18.0.2 and /tmp/clear.sh. This meant that the command was trying to log in to the container as root, then run the clear.sh script from the host's /tmp folder.

I checked the /tmp directory to see what this script did, but there was no script there. This seemed like a perfect opportunity for me to supply one for them. I made a script to copy root's SSH key to my machine, since the process was running in the context of root (verified by checking ps aux again).

My bash script was very simple, and just copied root's SSH to my machine using TCP sockets

Since the process that was running the clear.sh script on the machine was owned by root, and since it was trying to run by connecting to port 22 on the container, I figured I needed a way to redirect that connection back to the local machine since there was no script named clear.sh there in the /tmp directory. This would give me the perfect opportunity to supply one for them for my own purposes. After doing some research on how to redirect ports without using SSH, I found the easiest way was by using socat.

Using socat I tried to redirect port 22 traffic from the container to the host so it would execute but the port was already in use (by SSH).

Next, I stopped the SSH service to clear the port.

For some reason I was unable to SSH in at one point while fixing up everything (it took me a number of tries to get it to work properly, as there was something causing the connection to the container to timeout after a short time!). Running the command it mentioned in the error message:ssh-keygen -f "/var/solr/.ssh/known_hosts" -R "172.18.0.2" cleared the error and allowed me to log in again.

The process that would run my malicious clear.sh also deleted my file, so I had to keep putting it back in place. After recreating the script I started the socat redirect again.

I received the SSH key back at my waiting listener almost immediately after starting the socat redirect!

Root.txt

After that I logged in with the SSH key and collected my hard-earned proof!

In the root directory I also found the scripts that explained how this machine worked.

Thanks to MrR3boot & R4J for creating such a fun and interesting test of my python abilities. I think this is the first time that I have had to write so many scripts for just one machine. It was nice to learn about some new protocols, and also work in some API interaction as well.

If you like this content and would like to see more, please consider buying me a coffee!

Last updated

Was this helpful?