Full Report
Spring Boot has a pretty famous issue: when data is reflected within an error message, the Exception message uses Spring Expression Language (SpEL). For instance, $(7*7) will render 49. The authors of this post found an outdated version of Spring Boot being used which had a parameter reflected in an error message. Simple, right? The SpEL language is a templating language that can be used within Spring. When having an injection via server-side rendering, this can be used to execute Java methods, construct objects and other data to eventually get code execution. For information on SSTI in SpEL, spel-injection is a good link. Once they discover the SSTI, they try to try the simple payload ${T(java.lang.Runtime)}. This is a SpEL shorthand for referencing a Java class by name. However, the Akamai WAF blocked this request, even though they knew it was vulnerable. Now, we must learn why this was blocked by the WAF and how to work around it, which took over 500 requests to do. With Java-based code injection vulnerabilities, the first issue is finding out how to load arbitrary classes. 1${2.class} will output java.lang.Integer. To create an arbitrary class, the author tried 1${2.class.forName("java.lang.String")} which was rejected based upon the function forName. They needed to be able to create a string. The most straight forward route to RCE is .exec() is why. Reading the Java documentation (which is usually amazing), gave them the function toString() to get a non-static reference to a character. With this, they could use a toString() on an integer to return the proper character in a string they needed. Progress! With the ability to create arbitrary strings, we're getting closer. With a string at hand, we need to find a way to call java.lang.Runtime.exec. They used a known technique as follows: Use reflection (introspection feature) to get access to the Class.forName function to get an arbitrary package. Build a String with the value java.lang.Runtime to pass to the function. Use reflection to get access to the getRuntime function. Build a string for exec using the crazy method above. Call exec with our string. A GET request has a limitation of 2kb. Since each character was 45 bytes long, this limited us to 45 characters in our payload. They tried to find an optimization, but couldn't find one after hours of trying. Eventually, they settled for a small payload in the exec function. After hours of grinding, a simple uname -a response could be seen! The bug had been exploited and the WAF bypassed. To me, the interesting part of the article isn't the bypass itself but the thoughts around bypassing the WAF and problem solving.
Analysis Summary
# Vulnerability: RCE via SpEL Injection on Spring Boot Error Page
## CVE Details
- **CVE ID**: N/A (Note: This is a known architectural behavior in older Spring Boot versions, often cited in relation to [Spring Issue #4763](https://github.com/spring-projects/spring-boot/issues/4763)).
- **CVSS Score**: 9.8 (Critical) - *Estimated based on typical RCE impact.*
- **CWE**: CWE-917 (Improper Neutralization of Special Elements used in an Expression Language Statement)
## Affected Systems
- **Products**: Spring Boot Framework.
- **Versions**: Primarily versions prior to 1.2.8 and 1.3.1.
- **Configurations**: Applications using the default "Whitelabel Error Page" where user input (reflected in an Exception message) is recursively evaluated as a Spring Expression Language (SpEL) expression.
## Vulnerability Description
The vulnerability occurs when a Spring Boot application reflects unsanitized user input into an Exception. When the default Whitelabel Error Page renders this exception message, it treats the string as a SpEL template. Because the rendering engine performs recursive evaluation, an attacker can provide a payload (e.g., `${7*7}`) that the server executes. This allows for arbitrary Java method execution, object instantiation, and ultimately, Remote Code Execution (RCE).
## Exploitation
- **Status**: Exploited in the wild / PoC available.
- **Complexity**: High (Requires bypassing WAFs and navigating restricted ClassLoader environments).
- **Attack Vector**: Network (Remote).
## Impact
- **Confidentiality**: High (Full access to application data and environment variables).
- **Integrity**: High (Ability to modify files and system state).
- **Availability**: High (Ability to crash the service or execute destructive commands).
## Remediation
### Patches
- **Upgrade Spring Boot**: Ensure the application is running on a modern, supported version of Spring Boot (2.x or 3.x). The recursive evaluation issue was addressed in versions **1.2.8** and **1.3.1**.
### Workarounds
- **Disable Whitelabel Error Page**: Set `server.error.whitelabel.enabled=false` in `application.properties`.
- **Custom Error Handling**: Implement a custom `ErrorController` that does not reflect raw exception messages or uses a non-evaluating template engine.
- **Input Validation**: Sanitize inputs to prevent the injection of `${}` sequences.
## Detection
- **Indicators of Compromise**:
- Logs showing repeated `4xx` or `5xx` errors with SpEL characters (`$`, `{`, `}`, `T(`, `.class`) in URL parameters.
- Large GET requests (2kb-4kb) containing complex reflection logic (`Class.forName`, `method.invoke`).
- **Detection Methods**:
- Static Analysis (SAST) to find unsanitized input in Exception constructors.
- Dynamic Analysis (DAST) by sending `${7*7}` and looking for `49` in the response body.
## References
- **Spring Issue Tracker**: [https://github.com/spring-projects/spring-boot/issues/4763](https://github.com/spring-projects/spring-boot/issues/4763)
- **SpEL Injection Reference**: [https://github.com/jzheaux/spel-injection](https://github.com/jzheaux/spel-injection)
- **Original Research**: [https://www.pmnh.site/](https://www.pmnh.site/) (Defanged)