Hack The Box – Luke

Here we go again, this time we are looking at Luke. What do we know before we begin? Very little, apart from it’s a FreeBSD box on the IP

So let’s start the same way as always!

nmap -sC -sV -O -oA nmap/Luke

We get a few ports that show as open:

Nmap 7.70 scan initiated Sun Jun 23 14:22:36 2019 as: nmap -sC -sV -O -oA nmap/luke
Nmap scan report for
Host is up (0.087s latency).
Not shown: 995 closed ports
21/tcp open ftp vsftpd 3.0.3+ (ext.1)
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxr-xr-x 2 0 0 512 Apr 14 12:35 webapp
| ftp-syst:
| FTP server status:
| Connected to
| Logged in as ftp
| No session upload bandwidth limit
| No session download bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 1
| vsFTPd 3.0.3+ (ext.1) - secure, fast, stable
|_End of status
22/tcp open ssh?
80/tcp open http Apache httpd 2.4.38 ((FreeBSD) PHP/7.3.3)
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.38 (FreeBSD) PHP/7.3.3
|_http-title: Luke
3000/tcp open http Node.js Express framework
|_http-title: Site doesn't have a title (application/json; charset=utf-8).
8000/tcp open http Ajenti http control panel
|_http-title: Ajenti
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:

Network Distance: 2 hops

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Jun 23 14:25:51 2019 -- 1 IP address (1 host up) scanned in 195.57 seconds


So we have 5 ports open, 3 of which are webservers and one which allows Anonymous FTP!

I know where I’m going to start! Let’s start by setting 3 dirbs running in the background while we investigate the FTP server.

dirb -w /usr/share/dirb/wordlists/common.txt > dirb/port80.txt

dirb -w /usr/share/dirb/wordlists/common.txt > dirb/port3000.txt

dirb -w /usr/share/dirb/wordlists/common.txt > dirb/port8000.txt

While they are running, let’s take a look at the FTP server. Nmap has already told us we can log in anonymously, so let’s give it a go

ftp 21

We get prompted for a password, entering anything gives us access.

We can see there is 1 folder called webapp. Within there is a file called for_Chihiro.txt

Let’s get that back to our box

get for_Chihiro.txt

We now have this on our box. Cat’ing the file shows it contains a message:

Dear Chihiro !!

As you told me that you wanted to learn Web Development and Frontend, I can give you a little push by showing the sources of 
the actual website I've created .
Normally you should know where to look but hurry up because I will delete them soon because of our security policies !


Now, what the fuck does that mean?! I’m guessing maybe we need to look at the source of each of the websites on each of the 3 webservers!

Shall we start with the old fashioned port 80.

Heading over to the website we get a very basic website, with some text and 3 links which take you to further down the page. From looking at the source we do know that there is a lot of javascript on the website.

Unfortunately I know absolutely nothing about javascript or how to exploit it. Let’s see if our dirb came back with any results for this webserver.

It came back with a few pages and directories








Management, LICENSE and index look quite interesting, lets have a look.

First up, management. We get a pop up!

If we can find some creds, I know where I’m coming back to.

LICENSE shows the MIT License, I think this is for the bootstrap JS stuff we saw earlier.

Index is the main page that we see, which I don’t think has much on.

In terms of folders, member is empty, css is just the display schemes, vendor has a list of all the javascript used:




It looks like the jQuery is v3.3.1, jQuery Easing is v1.4.1 and Bootstrap is v4.2.1.

We may come back to these for exploits or at least more googling and the version numbers are bound to be useful.

The final folder, is /js/ This just looks to contain the scrolling-nav, which the hotlinks use to scroll down the page.

As I have no idea what we could even do here, let’s go look at the other ports.

Port 3000 appears to just have some JSON on it

This looks a lot like what we found on Port 3000. We quickly ran away then but maybe another viewing of ippsecs video will give us a hint.

However, the dirb that I ran did find 3 directories:




Let’s go check them out.

Both Login and login give a JSON message, it’s the same on both and simply says:

"please auth"

Users has a slightly different answer:

success false
message  "Auth token is not supplied"

It’s not much better!

Let’s skip over these for now and head over to port 8000. Aha, slightly more interesting:

It’s a login prompt. So we have 3 areas that require auth:

Now, just to find some creds I guess!

The directory wordlist that I used was a different one to normal, so I’m going to try using dirbuster which isn’t my favourite to look at but gives good results with my normal wordlist:


Let’s see if there is anything extra we get.

We get an additional hit on port 80 which I’m not sure how we missed first time around:


This gives us some db information:
$dbHost = 'localhost'; $dbUsername = 'root'; $dbPassword = 'Zk6heYCyv6ZE9Xcg'; $db = "login"; $conn = new mysqli($dbHost, $dbUsername, $dbPassword,$db) or die("Connect failed: %s\n". $conn -> error);

So we have a username and password, potentially. Let’s go see if it works on the logins!

So trying that password with:




Unfortunately none of these worked with that password.

I have also gone back to basics and looked at the default password for Ajenti which is:


This also doesn’t work, but would indicate it could be where the root password goes.  Looking at searchsploit for Ajenti we have 1 hit:

It’s cross-site scripting. Not too sure how useful that will be here.

I think maybe we need to go back to the javascript on the main webserver and have a look at what we have.

The one thing that keeps bugging me is port 3000 and the message please auth or auth token is not supplied for users.

Maybe we can use the password as an auth token? Event getting a list of users at this point would be good.

Thinking about the list of users, there is the directory /users/ we tried that earlier and got a please auth message. What if we try some user names after that so /users/chihiro

We get:

Whereas if we try derry we get:

What this means, is we get a different result for a real user, therefore we can enumerate usernames.

So let’s put that request through burp and send it over to intruder. Dirb has a good name list which is available at /usr/share/wordlists/dirb/others/names.txt

So in Intruder, we set the position around derry

We load in the wordlist under Payload (Simple List) and add in a few others including derry, luke and admin. Then within the options we add under Grep Match

Hit start attack and let’s watch the results come pouring in! After we hit start attack we filter out any 404 errors!

I have just realised that doing a match isn’t the best, as what if there is a website that loads straight away, we won’t see if as it doesn’t match our expected outcome.

So re-running the test without that grep. Anything that doesn’t load will still come back as a 404 so we can filter those out.

While that was running, a colleague had mentioned trying curl (not sure if he knows something I don’t, has read something or is just also clutching at straws), so I looked at some curl commands to see if we could get any more clues.


{"success":false,"message":"Auth token is not supplied"}

Hmm ok, how about /login

"please auth"

So, let’s try and add authentication, we have a username and password

curl --user root:Zk6heYCyv6ZE9Xcg

Another “please auth”, so wrong user or password or syntax/format. Let’s try some other users.

curl --user admin:Zk6heYCyv6ZE9Xcg
curl --user Admin:Zk6heYCyv6ZE9Xcg
curl --user superadmin:Zk6heYCyv6ZE9Xcg
curl --user SuperAdmin:Zk6heYCyv6ZE9Xcg
curl --user administrator:Zk6heYCyv6ZE9Xcg
curl --user Administrator:Zk6heYCyv6ZE9Xcg
curl --user derry:Zk6heYCyv6ZE9Xcg
curl --user Derry:Zk6heYCyv6ZE9Xcg

All the exact same result:

I guess we wait for the Burp Intruder to finish and see if we have any other usernames we can try out.

After what can only be described as …..ages….. I remembered that intruder is very slow on burp community edition. However, we are enumerating directories, why don’t we go back to dirbuster for this!

So, running dirbuster, much quicker we get a list of users which exist:

That runs super quickly and we have another couple of usernames:




So, we can now try logging into each auth prompt with these 2 additional usernames. None of the 3 direct auth prompts work for either username with the password found from the config.php file.

Heading to /users/<name> still gives the “Auth token is not supplied”

This is, frustrating to say the least!

Doing some research and finding some pretty diagrams to explain JWTs it look slike we need to authenticate to /login with a username and password, then the server will create a JWT which we can then use the get to all the other subdirectories.

So the big question is how do we authenticate using /login which doesn’t have any sort of GUI. We will need to do this by putting the requests directly in a POST request, however we don’t know the field names for username and password, we don’t know which user to use or even if the password is correct.

That’s far too many variables for my liking!

With this new knowledge, I did some more googling on authenticating to express JS, I revisitied the idea of CURL that we did earlier and read through this article: https://medium.com/@evangow/server-authentication-basics-express-sessions-passport-and-curl-359b7456003d

This showed:

client $ curl -X POST  http://localhost:3000/login -c cookie-file.txt -H 'Content-Type: application/json' -d '{"email":"test@test.com", "password":"password"}'You were authenticated & logged in!

So, I knew that we didn’t have an e-mail, but maybe it was username, so I tried that. I figured at this stage, we needed this to be admin but I was ready to do a lot of manual trying through all the usernames if that didn’t work.

My final request was:

curl -X POST -c cookie-file.txt -H 'Content-Type: application/json' -d '{"username":"admin", "password":"Zk6heYCyv6ZE9Xcg"}'

This resulted in:

{"success":true,"message":"Authentication successful!","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxODg2NDQzLCJleHAiOjE1NjE5NzI4NDN9.-I_yLS8xOvod3dCyGeKqdmUrClsfXW7ESf_GaZF540E"}

We now have a token!

Next step, how to use that!

Firstly, I was interested to see what the token was made up of, so I went over to calebb.net which decodes JWT tokens, the results showed:











So the username is admin, it has a code and an expiry date (I assume), it doesn’t really help us but it’s interesting to know what’s there.

Looking at a JWT for beginners page here: https://jwt.io/introduction/

It mentions:

“Whenever the user wants to access a protected route or resource, the user agent should send the JWT, typically in the Authorization header using the Bearer schema. The content of the header should look like the following:

Authorization: Bearer <token>"

Therefore, we are going to include that in Burp and see what we get.

We forward the request and get:

We get Dory’s username and password!

So, going through all the users we have found, we get a list of usernames and passwords.

Now that we have these, let’s head over to our login pages and see which works where.

First up, port 8000 for ajenti log in.

Unfortunately all 4 sets of creds still give us auth failure.

Let’s head over to port 80 /management and /login and see what we have,

Within management, the credentials for Derry worked and we get:

First up is config.json which has 1 very interesting line in it:

ajenti.plugins.munin.client.MuninClient "{\"username\": \"username\", \"prefix\": \"http://localhost:8080/munin\", \"password\": \"123\"}"

So we will check that out.

Config.php is the document we found earlier with the mysql creds.

Login.php takes us to the login page that we found earlier.

So let’s go take a look at port 8080/munin. Unexpectedly that doesn’t exist, if it did our port scan earlier would have caught it. Nothing works on the main login page either.

So somewhere, we have missed something.

Let’s look at our progress here. We found config.php on port 80, which gave us the creds to get the JWT token on port 3000. Then from there, we got the creds for Derry that let us get into management on port 80. From there it appears to be a deadend. I think we need to re-look at that, we must have missed something.

The full config.json might be where to go back to:

"users": {
"root": {
"configs": {
"ajenti.plugins.notepad.notepad.Notepad": "{\"bookmarks\": [], \"root\": \"/\"}", 
"ajenti.plugins.terminal.main.Terminals": "{\"shell\": \"sh -c $SHELL || sh\"}", 
"ajenti.plugins.elements.ipmap.ElementsIPMapper": "{\"users\": {}}", 
"ajenti.plugins.munin.client.MuninClient": "{\"username\": \"username\", \"prefix\": \"http://localhost:8080/munin\", \"password\": \"123\"}", 
"ajenti.plugins.dashboard.dash.Dash": "{\"widgets\": [{\"index\": 0, \"config\": null, \"container\": \"1\", \"class\": \"ajenti.plugins.sensors.memory.MemoryWidget\"}, {\"index\": 1, \"config\": null, \"container\": \"1\", \"class\": \"ajenti.plugins.sensors.memory.SwapWidget\"}, {\"index\": 2, \"config\": null, \"container\": \"1\", \"class\": \"ajenti.plugins.dashboard.welcome.WelcomeWidget\"}, {\"index\": 0, \"config\": null, \"container\": \"0\", \"class\": \"ajenti.plugins.sensors.uptime.UptimeWidget\"}, {\"index\": 1, \"config\": null, \"container\": \"0\", \"class\": \"ajenti.plugins.power.power.PowerWidget\"}, {\"index\": 2, \"config\": null, \"container\": \"0\", \"class\": \"ajenti.plugins.sensors.cpu.CPUWidget\"}]}", 
"ajenti.plugins.elements.shaper.main.Shaper": "{\"rules\": []}", 
"ajenti.plugins.ajenti_org.main.AjentiOrgReporter": "{\"key\": null}", 
"ajenti.plugins.logs.main.Logs": "{\"root\": \"/var/log\"}", 
"ajenti.plugins.mysql.api.MySQLDB": "{\"password\": \"\", \"user\": \"root\", \"hostname\": \"localhost\"}", 
"ajenti.plugins.fm.fm.FileManager": "{\"root\": \"/\"}", 
"ajenti.plugins.tasks.manager.TaskManager": "{\"task_definitions\": []}", 
"ajenti.users.UserManager": "{\"sync-provider\": \"\"}", 
"ajenti.usersync.adsync.ActiveDirectorySyncProvider": "{\"domain\": \"DOMAIN\", \"password\": \"\", \"user\": \"Administrator\", \"base\": \"cn=Users,dc=DOMAIN\", \"address\": \"localhost\"}", 
"ajenti.plugins.elements.usermgr.ElementsUserManager": "{\"groups\": []}", 
"ajenti.plugins.elements.projects.main.ElementsProjectManager": "{\"projects\": \"KGxwMQou\\n\"}"
"password": "KpMasng6S5EtTy9Z", 
"permissions": []
"language": "", 
"bind": {
"host": "", 
"port": 8000
"enable_feedback": true, 
"ssl": {
"enable": false, 
"certificate_path": ""
"authentication": true, 
"installation_id": 12354

So what I missed before, was the other password of KpMasng6S5EtTy9Z. This doesn’t appear to have a username attached with it, but maybe we can try some obvious ones, like root, admin etc.

Root did the trick! We are into the interface!

You may notice what I did straight away. There is a tool called “Terminal” let’s try that first!

It works! We have a terminal on the box, a very quick look around and boom, we have the user flag!

Amazing, we got the user flag!

Also what you might notice, is that cheeky whoami told me that we are root. It can’t be that easy can it?

Oh. It was that easy.

Well, that was Luke! I feel we learnt a lot about JWT creation. How to authenticate with it and bit of a hunt for creds and reminding ourselves that good enumeration is key!

There could definitely have been some priv esc, but I wonder if it’s because it’s FreeBSD and people aren’t too familiar with it? Anyway, a fun box and learnt a heap! Good times!

Leave a Reply

Your email address will not be published. Required fields are marked *