FreeMarker is a common Java engine (Spring, Struts, standalone). Confirm with ${7*7} returning 49. FreeMarker's ? built-ins include several that construct and invoke Java objects, which is the route to RCE.
The classic primitive is the Execute utility, instantiated with the new built-in and then called:
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id") }
"freemarker.template.utility.Execute"?new() builds an instance of a class that runs a command when called, and ex("id") executes it. Two other built-ins give alternative paths. ?api exposes the underlying Java API of a value, allowing access to a classloader:
${ "freemarker.template.utility.ObjectConstructor"?new()("java.lang.ProcessBuilder","id").start() }
ObjectConstructor constructs an arbitrary object from a class name and constructor arguments, so building a ProcessBuilder and calling .start() runs the command. A third form reaches a ProcessBuilder through ?api.getClass() and reflection when the utility classes are blocked.
FreeMarker added new_builtin_class_resolver to restrict which classes ?new can instantiate. When the application sets it to TemplateClassResolver.SAFER_RESOLVER or ALLOWS_NOTHING_RESOLVER, Execute and ObjectConstructor are rejected and ?new is effectively closed. The ?api built-in is likewise gated by the api_builtin_enabled setting, off by default. On a hardened configuration, test each built-in: if all are blocked, the injection is limited to reading exposed data model variables. On a default or permissive configuration the Execute?new() one-liner is immediate RCE.
Tools#
- tplmap, SSTImap
References#
- Apache FreeMarker documentation: ?new, ?api, new_builtin_class_resolver
- PortSwigger Web Security Academy: Server-side template injection