A-Z.fi

Last modified: 2025-03-18

HTB - Spookifier Writeup by Mcshoothy

Challenge Description

The Spookifier challenge presents a web application that transforms your name into a spooky variant using multiple font styles. However, users later discovered that the application not only spookified their names but also altered their real names without consent. In this writeup, we analyze the application and pinpoint the vulnerable parts of the code that lead to a critical Server-Side Template Injection (SSTI) issue.


Provided Files

main.py

from flask import Flask, jsonify
from application.blueprints.routes import web
from flask_mako import MakoTemplates

app = Flask(__name__)
MakoTemplates(app)

def response(message):
    return jsonify({'message': message})

app.register_blueprint(web, url_prefix='/')

@app.errorhandler(404)
def not_found(error):
    return response('404 Not Found'), 404

@app.errorhandler(403)
def forbidden(error):
    return response('403 Forbidden'), 403

@app.errorhandler(400)
def bad_request(error):
    return response('400 Bad Request'), 400

routes.py

from flask import Blueprint, request
from flask_mako import render_template
from application.util import spookify

web = Blueprint('web', __name__)

@web.route('/')
def index():
    text = request.args.get('text')
    if(text):
        converted = spookify(text)
        return render_template('index.html', output=converted)

    return render_template('index.html', output='')

util.py

Note: The full content of util.py is not repeated here. Its key functions are:

  • spookify(text): Converts the user-supplied text into four different font styles.
  • generate_render(converted_fonts): Formats the transformed text into an HTML template using Mako.

Vulnerability Focus: The generate_render function directly injects user-controlled data into the Mako template without proper sanitization:

def generate_render(converted_fonts):
    result = '''
        <tr>
            <td>{0}</td>
        </tr>

        <tr>
            <td>{1}</td>
        </tr>

        <tr>
            <td>{2}</td>
        </tr>

        <tr>
            <td>{3}</td>
        </tr>
    '''.format(*converted_fonts)

    return Template(result).render()

This unsanitized string formatting makes the application vulnerable to SSTI.


Dockerfile

FROM python:3.8-alpine

RUN apk add --no-cache --update supervisor gcc
# Upgrade pip
RUN python -m pip install --upgrade pip

# Install dependencies
RUN pip install Flask==2.0.0 mako flask_mako Werkzeug==2.0.0

# Copy flag
COPY flag.txt /flag.txt

# Setup app
RUN mkdir -p /app

# Switch working environment
WORKDIR /app

# Add application
COPY challenge .

# Setup supervisor
COPY config/supervisord.conf /etc/supervisord.conf

# Expose port the server is reachable on
EXPOSE 1337

# Disable pycache
ENV PYTHONDONTWRITEBYTECODE=1

# start supervisord
ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

Additional Note: The application is using outdated dependency versions: Flask==2.0.0 and Werkzeug==2.0.0, which may lack important security patches available in newer releases.


Key Code Analysis

The vulnerability arises from how user input is processed and rendered into Mako templates without proper sanitization. The following flowchart illustrates the data flow and highlights the vulnerable code sections:

graph TD; A[User Input] -->|text parameter| B[/web route/]; B -->|Calls| C[spookify]; C -->|Returns formatted text| D[render_template]; D -->|Renders HTML| E[Flask-Mako Engine]; E -->|Potential Injection Point| F[SSTI Exploit];

Problematic Code Sections:

  • In routes.py:

The index() function directly takes the text parameter from the request and passes it to the spookify() function:

  @web.route('/')
  def index():
      text = request.args.get('text')
      if(text):
          converted = spookify(text)
          return render_template('index.html', output=converted)

      return render_template('index.html', output='')

Issue: User-controlled input is processed without any validation, making it possible to inject malicious payloads.

  • In util.py (within the generate_render() function):

The function uses Python's string formatting to insert the font-converted text directly into an HTML template:

  def generate_render(converted_fonts):
      result = '''
          <tr>
              <td>{0}</td>
          </tr>

          <tr>
              <td>{1}</td>
          </tr>

          <tr>
              <td>{2}</td>
          </tr>

          <tr>
              <td>{3}</td>
          </tr>
      '''.format(*converted_fonts)

      return Template(result).render()

Issue: The unsanitized injection of converted_fonts allows for arbitrary Python code execution via Mako's inline evaluation, leading to an SSTI vulnerability.

Exploiting the Vulnerability

Once we identified the SSTI vulnerability in Flask-Mako, we tested payloads to confirm code execution within the template rendering engine. By injecting ${7*7} into the text parameter, we received 49 in response, confirming the template was evaluating our input. To retrieve the flag, we leveraged Python's open() function to read flag.txt:

http://target:1337/?text=${open('/flag.txt').read()}

Since the application did not have any sandboxing mechanisms in place, the server executed our payload, revealing theflag as plain as text.

FunkyHotspot HTB