CVE-2026-12894
Patch Available.
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Quarkus is a Kubernetes-native Java framework optimized for cloud-native applications, containers, and serverless workloads. It builds on standard libraries (Jakarta EE, Eclipse Vert.x, Hibernate, Netty) and compiles to both JVM and GraalVM native images for fast startup and low memory use. Qute is the Quarkus templating engine, shipped in the same repository, and it renders HTML pages, mail bodies, and other text output by resolving expressions in a template against the data objects supplied by the application.
A Remote Code Execution (RCE) vulnerability (CVE-2026-12894) has been identified in the Qute template engine, which allows an attacker who can influence template content to walk a reflection chain from an ordinary enum value to a java.lang.Class instance, obtain its class loader, and load and invoke arbitrary classes in the context of the running Java process.
Per OWASP: Code Injection is the general term for attack types which consist of injecting code that is then interpreted/executed by the application. This type of attack exploits poor handling of untrusted data.
This issue affects the Qute template engine of Quarkus.
Details
Module Info
- Product: NES for Quarkus
- Affected packages: io.quarkus:quarkus-qute, io.quarkus.qute:qute-core
- Affected versions:
- io.quarkus:quarkus-qute: <3.39.2
- io.quarkus.qute:qute-core: <3.39.2
- GitHub repository: https://github.com/quarkusio/quarkus
- Published packages: https://central.sonatype.com/artifact/io.quarkus/quarkus-qute, https://central.sonatype.com/artifact/io.quarkus.qute/qute-core
- Package manager: Maven
- Fixed in:
- NES for Quarkus 2.16.15 and 3.20.8
- OSS Quarkus 3.27.5.2, 3.33.3.2, and 3.39.2
Vulnerability Info
This High-severity vulnerability is found in the io.quarkus:quarkus-qute package in the Qute template engine of Quarkus.
Qute resolves an expression such as {item.name} by asking each registered value resolver whether it can supply the named member of the current base object. ReflectionValueResolver is the general fallback resolver: when no specialized or build-time generated resolver matches, it reflects over the base object's class hierarchy and invokes any public instance method whose name matches the expression part. The only guard on which methods may be reached is isMethodCandidate(), and its sole security check rejects methods declared on java.lang.Object:
// io.quarkus.qute.ReflectionValueResolver
private static List<Method> findMethods(Class<?> clazz, String name, int numberOfParams) {
// ... walk the class hierarchy ...
for (Method method : clazzToTest.getMethods()) {
if (isMethodCandidate(method) && name.equals(method.getName())) {
foundMatch.add(method);
method.trySetAccessible();
}
}
// ...
}
private static boolean isMethodCandidate(Method method) {
return method != null
&& Modifier.isPublic(method.getModifiers())
&& !Modifier.isStatic(method.getModifiers())
&& !method.getReturnType().equals(Void.TYPE)
&& !method.isBridge()
&& !Object.class.equals(method.getDeclaringClass());
}
Blocking only java.lang.Object blocks getClass(), but it does nothing about the many other methods that hand back a java.lang.Class instance. Enum.getDeclaringClass() is declared on java.lang.Enum, is public, non-static, and returns a value, so it passes every part of the filter. Any enum reachable in the template data model, say a status field, is therefore enough to start the reflection chain: once the base object is a java.lang.Class, methods declared on java.lang.Class such as getClassLoader() also pass the filter, and from the class loader an attacker can call loadClass(), then Class.getMethod() and java.lang.reflect.Method.invoke(), because ReflectionValueResolver accepts methods with parameters as well. A template expression of the form below is enough to run arbitrary code in the JVM that renders the template:
{status.declaringClass.classLoader.loadClass('java.lang.Runtime')...}
Anything that lets untrusted input reach template source makes this reachable: user-authored or user-uploaded templates, template fragments assembled from request data, and the built-in {#eval} section, which evaluates a string as a template at render time and was registered by default for every engine built through EngineBuilder.addDefaultSectionHelpers():
// io.quarkus.qute.EngineBuilder
public EngineBuilder addDefaultSectionHelpers() {
return addSectionHelpers(new IfSectionHelper.Factory(), new LoopSectionHelper.Factory(iterationMetadataPrefix),
new WithSectionHelper.Factory(), new IncludeSectionHelper.Factory(), new InsertSectionHelper.Factory(),
new SetSectionHelper.Factory(), new WhenSectionHelper.Factory(), new EvalSectionHelper.Factory(),
new FragmentSectionHelper.Factory());
}
Because the reflective invocation happens inside the application's own JVM with the application's privileges, successful exploitation yields full remote code execution, not merely disclosure of template data.
Mitigation
Only recent versions of Quarkus are community-supported. The 2.16.x and 3.20.x lines were already End-of-Life when this CVE was published and will not receive public updates to address this issue. Quarkus LTS releases are maintained for 12 months from release; see the project's LTS release policy.
Users of the affected components should apply one of the following mitigations:
- Upgrade Quarkus to a currently supported release that contains the fix.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.
After upgrading, templates that use the {#eval} section are no longer rendered by the default engine; build-time validation does not flag them, so the failure surfaces at render time unless the application registers EvalSectionHelper explicitly (for example from an @Observes EngineBuilder method).
Credits
- icysun (finder)