User Input in ScriptEngine
Passing user-provided data directly to ScriptEngine.eval() without sanitization, allowing code execution.
Remote code execution vulnerabilities occur when untrusted user input is passed to ScriptEngine.eval(), allowing attackers to execute arbitrary code by injecting malicious scripts into the evaluation context.
@RestControllerpublic class ScriptController { private ScriptEngine scriptEngine; public ScriptController() { ScriptEngineManager manager = new ScriptEngineManager(); // VULNERABLE: Using JavaScript engine with full access this.scriptEngine = manager.getEngineByName("JavaScript"); } @PostMapping("/api/calculate") public ResponseEntity<Object> calculate(@RequestBody Map<String, String> request) { String expression = request.get("expression"); try { // VULNERABLE: Direct evaluation of user input Object result = scriptEngine.eval(expression); return ResponseEntity.ok(result); } catch (ScriptException e) { return ResponseEntity.badRequest().body("Invalid expression"); } } @PostMapping("/api/transform") public ResponseEntity<String> transformData(@RequestBody Map<String, Object> request) { String script = (String) request.get("script"); Object data = request.get("data"); try { // VULNERABLE: User-controlled script execution scriptEngine.put("inputData", data); Object result = scriptEngine.eval(script); return ResponseEntity.ok(result.toString()); } catch (ScriptException e) { return ResponseEntity.status(500).body("Script execution failed"); } } @GetMapping("/api/dynamic-logic") public ResponseEntity<Object> executeDynamicLogic(@RequestParam String logic) { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("Groovy"); try { // VULNERABLE: Creating new engine instance for each request Object result = engine.eval(logic); return ResponseEntity.ok(result); } catch (ScriptException e) { return ResponseEntity.badRequest().body("Logic execution error"); } }}@RestControllerpublic class SecureCalculatorController { // SECURE: Use expression evaluator instead of script engine private static final Set<String> ALLOWED_OPERATIONS = Set.of("+", "-", "*", "/", "(", ")"); private static final Pattern SAFE_EXPRESSION = Pattern.compile("^[0-9+\-*/().\s]+$"); @PostMapping("/api/calculate") public ResponseEntity<Object> calculate(@RequestBody Map<String, String> request) { String expression = request.get("expression"); // SECURE: Validate input before processing if (!isValidMathExpression(expression)) { return ResponseEntity.badRequest().body("Invalid mathematical expression"); } try { // SECURE: Use safe mathematical parser instead of script engine double result = evaluateMathExpression(expression); return ResponseEntity.ok(result); } catch (Exception e) { return ResponseEntity.badRequest().body("Calculation error"); } } private boolean isValidMathExpression(String expression) { if (expression == null || expression.trim().isEmpty()) { return false; } // Only allow mathematical characters if (!SAFE_EXPRESSION.matcher(expression).matches()) { return false; } // Additional validation for balanced parentheses, etc. return isBalancedParentheses(expression); } private boolean isBalancedParentheses(String expression) { int count = 0; for (char c : expression.toCharArray()) { if (c == '(') count++; else if (c == ')') count--; if (count < 0) return false; } return count == 0; } private double evaluateMathExpression(String expression) { // SECURE: Use a proper mathematical expression parser // This is a simplified example - use libraries like exp4j in production return new MathExpressionParser().parse(expression).evaluate(); } // SECURE: Using predefined templates instead of arbitrary scripts @PostMapping("/api/transform") public ResponseEntity<String> transformData(@RequestBody Map<String, Object> request) { String templateName = (String) request.get("template"); Object data = request.get("data"); // SECURE: Allowlist of predefined transformation templates Map<String, Function<Object, String>> templates = Map.of( "uppercase", obj -> obj.toString().toUpperCase(), "lowercase", obj -> obj.toString().toLowerCase(), "reverse", obj -> new StringBuilder(obj.toString()).reverse().toString(), "length", obj -> String.valueOf(obj.toString().length()) ); Function<Object, String> transformer = templates.get(templateName); if (transformer == null) { return ResponseEntity.badRequest().body("Invalid template name"); } try { String result = transformer.apply(data); return ResponseEntity.ok(result); } catch (Exception e) { return ResponseEntity.status(500).body("Transformation failed"); } } // SECURE: Using configuration-based logic instead of dynamic scripts @GetMapping("/api/business-logic") public ResponseEntity<Object> executeBusinessLogic(@RequestParam String ruleId) { // SECURE: Predefined business rules with IDs Map<String, Supplier<Object>> businessRules = Map.of( "discount-calculation", () -> calculateDiscount(), "tax-calculation", () -> calculateTax(), "shipping-cost", () -> calculateShipping() ); Supplier<Object> rule = businessRules.get(ruleId); if (rule == null) { return ResponseEntity.badRequest().body("Invalid rule ID"); } try { Object result = rule.get(); return ResponseEntity.ok(result); } catch (Exception e) { return ResponseEntity.status(500).body("Business logic execution failed"); } } private Object calculateDiscount() { // Predefined discount calculation logic return Map.of("discount", 0.1, "type", "percentage"); } private Object calculateTax() { // Predefined tax calculation logic return Map.of("tax_rate", 0.08, "type", "sales_tax"); } private Object calculateShipping() { // Predefined shipping calculation logic return Map.of("cost", 5.99, "method", "standard"); }}// If scripting is absolutely necessary, use strict sandboxing@Componentpublic class SandboxedScriptExecutor { private static final Set<String> ALLOWED_CLASSES = Set.of( "java.lang.String", "java.lang.Math", "java.util.Collections" ); public Object executeInSandbox(String script) throws ScriptException { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); // SECURE: Install security manager and restrictions SecurityManager originalManager = System.getSecurityManager(); System.setSecurityManager(new RestrictiveSecurityManager()); try { // Set limited execution context engine.put("allowedClasses", ALLOWED_CLASSES); // Validate script doesn't contain dangerous operations if (!isScriptSafe(script)) { throw new SecurityException("Script contains dangerous operations"); } return engine.eval(script); } finally { System.setSecurityManager(originalManager); } } private boolean isScriptSafe(String script) { String[] dangerousKeywords = { "java.lang.Runtime", "java.lang.Process", "java.io.File", "java.net.Socket", "System.exit", "Class.forName", "java.lang.reflect", "javax.script", "eval" }; String lowerScript = script.toLowerCase(); for (String keyword : dangerousKeywords) { if (lowerScript.contains(keyword.toLowerCase())) { return false; } } return true; } private static class RestrictiveSecurityManager extends SecurityManager { @Override public void checkPermission(Permission perm) { if (perm instanceof java.io.FilePermission || perm instanceof java.net.SocketPermission || perm instanceof java.lang.RuntimePermission) { throw new SecurityException("Operation not allowed in sandbox"); } } }}The vulnerable code was updated to address the security issue.
Passing user-provided data directly to ScriptEngine.eval() without sanitization, allowing code execution.
Sourcery automatically identifies remote code execution via scriptengine injection and many other security issues in your codebase.