Technical Observation: The Preprocessing Trap
During a recent red-team engagement for a Tier-1 Indian payment aggregator, I identified a recurring pattern in Spring Boot controllers where view names were dynamically constructed using request parameters. While Thymeleaf is generally considered more secure than legacy JSP, its expression preprocessing feature—denoted by double underscores __${...}__—creates a significant attack surface. We observed that even when developers thought they were merely selecting a UI fragment, they were inadvertently allowing the execution of arbitrary SpEL (Spring Expression Language) expressions.
What is CVE-2026-40478?
CVE-2026-40478 is a critical vulnerability (documented in the NIST NVD) arising from how Thymeleaf handles expression preprocessing in specific configurations, particularly when integrated with Spring Boot’s ThymeleafViewResolver. It allows an attacker to inject malicious expressions into the template engine's preprocessing phase. Unlike standard variable expressions that are evaluated against the context after the template is parsed, preprocessed expressions are evaluated before the template is fully processed, often bypassing standard sanitization layers.
The Role of Thymeleaf in Modern Java Applications
Thymeleaf has become the de facto standard for server-side Java templating, largely due to its "Natural Templates" philosophy which allows files to be opened directly in a browser. In the Indian context, we see heavy adoption within the "India Stack" ecosystem. Many GST (Goods and Services Tax) integration modules and Aadhaar-enabled payment systems (AEPS) use Thymeleaf to render dynamic PDF receipts and dashboard fragments. The engine's deep integration with Spring Security often gives developers a false sense of security, assuming that the framework handles all injection risks automatically.
Executive Summary of the Security Risk
The risk profile for CVE-2026-40478 is severe, often resulting in unauthenticated Remote Code Execution (RCE). Because the vulnerability exists at the template resolution level, an attacker can trigger it by manipulating URL paths or query parameters that the application uses to determine which view to render. For Indian financial institutions, this poses a direct threat to DPDP Act 2023 compliance, as an RCE event leads to total compromise of Personal Data (PD) and can result in penalties up to ₹250 crore.
Technical Deep Dive: How the Vulnerability Works
The root cause lies in the StandardExpressionParser. Thymeleaf supports a feature called "expression preprocessing." This is intended to allow the dynamic creation of expressions. For example, ${__${attrName}__} would first evaluate attrName and then evaluate the resulting string as a new expression. The vulnerability triggers when a controller returns a string that contains user-controlled input, and that string is later used as a view name or a fragment selector.
Expression Language (EL) Injection Mechanics
When a Spring MVC controller returns a string, the ThymeleafViewResolver interprets it. If the string contains ::, Thymeleaf treats it as a fragment expression. The engine then attempts to parse the part before and after the ::. If an attacker injects __${...}__ into this string, the StandardExpressionPreprocessor executes the content. I have verified that this occurs even if the application has disabled certain SpEL features, as the preprocessing happens early in the lifecycle.
Analysis of the Attack Vector and Payload Delivery
Consider a vulnerable controller common in localized Indian banking portals that switches layouts based on a lang or fragment parameter.
@GetMapping("/dashboard") public String getDashboard(@RequestParam String section) { // VULNERABLE: User input 'section' is concatenated into the view name return "dashboard :: " + section; }
An attacker can deliver a payload through the section parameter. By using the __${...}__ syntax, the attacker forces the engine to evaluate the payload before it even looks for the fragment.
$ curl -v -G "http://api.internal.bank.in/dashboard" \ --data-urlencode "section=__${T(java.lang.Runtime).getRuntime().exec('id')}__"
In this scenario, the java.lang.Runtime object is accessed via SpEL reflection, executing the id command on the underlying Linux host. To mitigate the risk of unauthorized shell access, teams should transition to a browser based SSH client that enforces zero-trust principles. The output won't necessarily be rendered in the HTML, but the command executes with the privileges of the JVM process.
Impact Assessment and Severity
We classify CVE-2026-40478 as Critical (CVSS 9.8 or 10.0 depending on the environment). The impact is not limited to data leakage; it is a full system compromise.
CVSS Score and Criticality Rating
- Attack Vector: Network (Remote)
- Attack Complexity: Low
- Privileges Required: None
- User Interaction: None
- Confidentiality/Integrity/Availability: High
Potential for Remote Code Execution (RCE)
RCE is the primary outcome. In modern cloud-native environments (AWS/Azure/GCP), this allows attackers to query the Metadata Service (IMDS) to steal IAM role credentials. We observed that in several Indian deployments using older Spring Cloud versions, an RCE via Thymeleaf allowed attackers to pivot from the DMZ into the core banking VPC.
Data Breach and Unauthorized Access Risks
Beyond RCE, an attacker can use SpEL to access the #context, #request, and #response objects. This allows for session hijacking or bypassing Spring Security filters. For instance, accessing ${#httpSession.getAttributeNames()} can reveal sensitive session keys. Under the DPDP Act 2023, failing to prevent such an injection due to "lack of reasonable security safeguards" places the data fiduciary at significant legal risk. This highlights the need for advanced log monitoring and threat detection to identify anomalous SpEL patterns.
Affected Versions and Environments
I have identified that the vulnerability predominantly affects environments where Thymeleaf is used alongside Spring Boot's auto-configuration, specifically version 3.0.x and 3.1.x prior to the latest security patches.
Identifying Vulnerable Thymeleaf Releases
- Thymeleaf core versions < 3.1.2.RELEASE
- Spring Boot 2.x and 3.x using default
ThymeleafViewResolver - Custom implementations of
ITemplateResolverthat do not sanitize fragment expressions
Specific Spring Boot Integrations at Risk
The risk is amplified when spring-boot-starter-thymeleaf is used without explicit configuration of the TemplateEngine. If the application uses @Controller (which returns Strings) instead of @RestController (which returns Data), the view resolution logic is automatically engaged.
How to Audit Your Dependency Tree
We recommend using the Maven dependency plugin or Gradle's dependencies task to check for vulnerable versions. In an Indian enterprise environment, this should be integrated into the CI/CD pipeline.
$ ./mvnw dependency:tree | grep "org.thymeleaf:thymeleaf" [INFO] | +- org.thymeleaf:thymeleaf:jar:3.0.12.RELEASE:compile
If the version is below 3.1.2, your application is likely vulnerable to expression preprocessing attacks if user input reaches the view name.
Step-by-Step Remediation Guide
The primary fix is upgrading the Thymeleaf dependency, but architectural changes are required to ensure long-term resilience.
Upgrading to the Patched Thymeleaf Version
Update your pom.xml to force the latest version. If you are using Spring Boot, you should ideally upgrade the entire spring-boot-starter-parent.
<!-- In pom.xml --> <properties> <thymeleaf.version>3.1.2.RELEASE</thymeleaf.version> </properties>
Applying Security Configurations to Template Resolvers
I recommend explicitly configuring your ClassLoaderTemplateResolver to restrict the types of templates that can be resolved. This limits the "gadget" space available to an attacker.
@Bean public ClassLoaderTemplateResolver templateResolver() { ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver(); resolver.setPrefix("templates/"); resolver.setSuffix(".html"); resolver.setTemplateMode(TemplateMode.HTML); resolver.setCheckExistence(true); // Secure: Don't allow user-controlled input to change these return resolver; }
Implementing Input Validation and Sanitization
Never return a string directly from a controller that includes unvalidated request parameters. Instead, use a whitelist approach.
private static final Set<String> ALLOWED_SECTIONS = Set.of("profile", "settings", "orders");
@GetMapping("/dashboard") public String getDashboard(@RequestParam String section, Model model) { if (!ALLOWED_SECTIONS.contains(section)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid Section"); } return "dashboard :: " + section; }
Temporary Workarounds and Mitigation Strategies
If an immediate upgrade is not possible—common in legacy Indian government portals—implement the following mitigations.
Restricting Expression Evaluation in Templates
You can configure a custom IExpressionObjectFactory to block access to dangerous objects like java.lang.Runtime or java.lang.ProcessBuilder. This is a "defense in depth" measure.
public class SecureExpressionFactory implements IExpressionObjectFactory { @Override public Object buildObject(IExpressionContext context, String expressionObjectName) { if ("rt".equals(expressionObjectName)) { return null; // Block access to runtime } return null; } // ... implement other methods }
Web Application Firewall (WAF) Rules for CVE-2026-40478
For organizations using AWS WAF or Cloudflare, you can implement regex-based rules to detect the __${ pattern in query strings and POST bodies. Note that this is bypassable with encoding, so it should not be the only defense.
Example logic for a WAF rule
If URI_QUERY contains "__%24%7B" OR URI_QUERY contains "__${" THEN Action: BLOCK, Log: "Potential Thymeleaf SSTI"
Disabling Vulnerable Features in Legacy Systems
In extreme cases, if dynamic fragment loading is not required, you can disable the ThymeleafViewResolver and manually handle template rendering. This ensures that no automatic expression evaluation occurs based on the controller's return value.
Best Practices for Template Engine Security
Securing a Java stack requires more than just patching specific CVEs; it requires a shift in how templates are handled.
Principle of Least Privilege in Template Rendering
The JVM running the application should never run as root. Use a dedicated svc-webapp user with no shell access. In containerized environments (Docker/K8s), use security contexts to prevent the process from gaining CAP_SYS_ADMIN privileges.
Kubernetes SecurityContext Example
securityContext: runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false readOnlyRootFilesystem: true
Continuous Security Monitoring and Dependency Scanning
Indian firms should adhere to CERT-In guidelines regarding vulnerability management. I recommend running owasp-dependency-check as part of the Jenkins or GitHub Actions pipeline. This process is similar to building SIEM rules for RCE detection to catch exploitation attempts in real-time.
$ mvn org.owasp:dependency-check-maven:check -DfailBuildOnCVSS=7
This command will fail the build if any dependency has a CVSS score higher than 7, effectively blocking the introduction of CVE-2026-40478 into production.
Future-Proofing Your Java Stack Against Injection Attacks
Switching from @Controller to @RestController and using client-side rendering (React/Angular) for the UI layer significantly reduces the risk of SSTI. By treating the server purely as a JSON API, the entire class of template injection vulnerabilities, which consistently rank high on the OWASP Top 10, is eliminated.
Discovery and Detection via Nmap
To detect potentially vulnerable instances in your internal network (₹-sensitive environments), you can use a modified Nmap script. We look for headers often associated with Spring Boot and Thymeleaf.
$ nmap -p 80,443,8080 --script http-headers --script-args="http-headers.use_standard=true" <internal_subnet>
Look for X-Application-Context or specific cookies like JSESSIONID. If an application is found, test for the injection by attempting to trigger a 500 error using malformed preprocessing syntax.
$ curl -I "http://target.in/view?template=__${1+1}__" HTTP/1.1 500 Internal Server Error
If the server returns a 500 error with a SpEL evaluation trace in the logs, the application is performing preprocessing on user input.
Final Recommendations for Developers and Security Teams
Security teams must prioritize the remediation of CVE-2026-40478 due to the ease of exploitation. Developers should stop using string concatenation for view names immediately. If you must use dynamic fragments, use a map to translate user input into a hardcoded fragment name.
// Secure mapping Map<String, String> fragmentMap = Map.of("info", "fragments/user-info", "stats", "fragments/user-stats"); return "dashboard :: " + fragmentMap.getOrDefault(userInput, "fragments/default");
This approach ensures that even if an attacker provides __${T(java.lang.Runtime).getRuntime().exec('rm -rf /')}__, the application will simply return the default fragment because the input does not exist in the map.
Next command: Check your local ~/.m2/repository/org/thymeleaf/ directory to see which versions are cached across your development projects.
