apps/ssti/labs/filtered.py · view on GitHub
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | """SSTI lab: filtered — INTENTIONALLY VULNERABLE. Same render_template_string sink as `basic`, but with a substring blocklist that strips a handful of "scary" tokens before rendering. The filter is the kind of fix someone applies in production after a Bug Bash finding without realizing how many ways there are around it. Bypass paths the blocklist misses: - attribute access via |attr filter: foo|attr("__class__") - attribute access via dict-style: foo["__cl"+"ass__"] - string concat / hex escape in the attribute name - request.application or self introspection """ from __future__ import annotations from pathlib import Path from flask import Blueprint, render_template, render_template_string, request bp = Blueprint("ssti_filtered", __name__, url_prefix="/filtered") # INTENTIONAL: ineffective deny-list. Real bypass surface is enormous. BLOCKED = ("__class__", "__mro__", "__subclasses__", "__globals__", "subprocess", "os.system", "popen", "import", "request.application") META = { "slug": "filtered", "title": "Jinja2 SSTI behind a substring blocklist", "summary": "render_template_string with a substring deny-list. Trivially bypassable.", "hint": ( f"The blocklist strips these tokens before rendering: {BLOCKED}. " "Jinja2 lets you reach the same attributes without those literal " "strings via the |attr filter: " "{{ ''|attr('__cl' ~ 'ass__')|attr('__mr' ~ 'o__') }}, or via " "dict-style indexing on the string concat. The lab flag is at " "{{ config['VULNLAB_SSTI_FILTERED'] }}." ), "sink": "flask.render_template_string (after substring blocklist)", "source_path": str(Path(__file__).resolve()), "vulnerable": True, } def _filter(s: str) -> str: out = s for token in BLOCKED: out = out.replace(token, "") return out @bp.route("/", methods=["GET"]) def lab(): raw = request.args.get("greeting", "") filtered = _filter(raw) if raw else "" rendered = error = None if filtered: try: rendered = render_template_string(filtered) except Exception as e: error = f"{type(e).__name__}: {e}" return render_template( "lab_filtered.html", meta=META, raw=raw, filtered=filtered, rendered=rendered, error=error, blocked=BLOCKED, ) |