Skip to content

Commit

Permalink
Reworked script engine detector
Browse files Browse the repository at this point in the history
Check for containment of payload in script content in all overloads of
eval.
  • Loading branch information
bertschneider committed Jun 13, 2023
1 parent ea9a05f commit 7dcfa35
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 148 deletions.
3 changes: 1 addition & 2 deletions examples/src/main/java/com/example/CommonsTextFuzzer.java
@@ -1,4 +1,4 @@
// Copyright 2022 Code Intelligence GmbH
// Copyright 2023 Code Intelligence GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,6 @@
package com.example;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.Jazzer;
import org.apache.commons.text.StringSubstitutor;

public class CommonsTextFuzzer {
Expand Down
49 changes: 47 additions & 2 deletions maven_install.json
@@ -1,7 +1,7 @@
{
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
"__INPUT_ARTIFACTS_HASH": 25727711,
"__RESOLVED_ARTIFACTS_HASH": 1912979319,
"__INPUT_ARTIFACTS_HASH": 1679182184,
"__RESOLVED_ARTIFACTS_HASH": -1745128755,
"conflict_resolution": {
"junit:junit:4.12": "junit:junit:4.13.2"
},
Expand Down Expand Up @@ -210,12 +210,24 @@
},
"version": "1.0-alpha2"
},
"org.apache.commons:commons-lang3": {
"shasums": {
"jar": "4ee380259c068d1dbe9e84ab52186f2acd65de067ec09beff731fca1697fdb16"
},
"version": "3.11"
},
"org.apache.commons:commons-math3": {
"shasums": {
"jar": "6268a9a0ea3e769fc493a21446664c0ef668e48c93d126791f6f3f757978fee2"
},
"version": "3.2"
},
"org.apache.commons:commons-text": {
"shasums": {
"jar": "0812f284ac5dd0d617461d9a2ab6ac6811137f25122dfffd4788a4871e732d00"
},
"version": "1.9"
},
"org.apache.logging.log4j:log4j-api": {
"shasums": {
"jar": "8caf58db006c609949a0068110395a33067a2bad707c3da35e959c0473f9a916"
Expand Down Expand Up @@ -596,6 +608,9 @@
"junit:junit": [
"org.hamcrest:hamcrest-core"
],
"org.apache.commons:commons-text": [
"org.apache.commons:commons-lang3"
],
"org.apache.logging.log4j:log4j-core": [
"org.apache.logging.log4j:log4j-api"
],
Expand Down Expand Up @@ -1199,6 +1214,25 @@
"org.apache.commons.imaging.internal",
"org.apache.commons.imaging.palette"
],
"org.apache.commons:commons-lang3": [
"org.apache.commons.lang3",
"org.apache.commons.lang3.arch",
"org.apache.commons.lang3.builder",
"org.apache.commons.lang3.compare",
"org.apache.commons.lang3.concurrent",
"org.apache.commons.lang3.concurrent.locks",
"org.apache.commons.lang3.event",
"org.apache.commons.lang3.exception",
"org.apache.commons.lang3.function",
"org.apache.commons.lang3.math",
"org.apache.commons.lang3.mutable",
"org.apache.commons.lang3.reflect",
"org.apache.commons.lang3.stream",
"org.apache.commons.lang3.text",
"org.apache.commons.lang3.text.translate",
"org.apache.commons.lang3.time",
"org.apache.commons.lang3.tuple"
],
"org.apache.commons:commons-math3": [
"org.apache.commons.math3",
"org.apache.commons.math3.analysis",
Expand Down Expand Up @@ -1262,6 +1296,15 @@
"org.apache.commons.math3.transform",
"org.apache.commons.math3.util"
],
"org.apache.commons:commons-text": [
"org.apache.commons.text",
"org.apache.commons.text.diff",
"org.apache.commons.text.io",
"org.apache.commons.text.lookup",
"org.apache.commons.text.matcher",
"org.apache.commons.text.similarity",
"org.apache.commons.text.translate"
],
"org.apache.logging.log4j:log4j-api": [
"org.apache.logging.log4j",
"org.apache.logging.log4j.internal",
Expand Down Expand Up @@ -2029,7 +2072,9 @@
"net.bytebuddy:byte-buddy-agent",
"net.sf.jopt-simple:jopt-simple",
"org.apache.commons:commons-imaging",
"org.apache.commons:commons-lang3",
"org.apache.commons:commons-math3",
"org.apache.commons:commons-text",
"org.apache.logging.log4j:log4j-api",
"org.apache.logging.log4j:log4j-core",
"org.apache.xmlgraphics:batik-anim",
Expand Down
Expand Up @@ -52,8 +52,8 @@ kt_jvm_library(
visibility = ["//sanitizers:__pkg__"],
runtime_deps = [
":regex_roadblocks",
":server_side_request_forgery",
":script_engine_injection",
":server_side_request_forgery",
":sql_injection",
],
deps = [
Expand Down
@@ -1,4 +1,4 @@
// Copyright 2022 Code Intelligence GmbH
// Copyright 2023 Code Intelligence GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -14,111 +14,95 @@

package com.code_intelligence.jazzer.sanitizers;

import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.toSet;

import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical;
import com.code_intelligence.jazzer.api.HookType;
import com.code_intelligence.jazzer.api.Jazzer;
import com.code_intelligence.jazzer.api.MethodHook;
import com.code_intelligence.jazzer.api.MethodHooks;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.invoke.MethodHandle;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
import javax.script.ScriptEngineManager;

/**
* Detects Script Engine injection
* Detects Script Engine injections.
*
* <p>
* The hooks in this class attempt to detect user input flowing into
* {@link javax.script.ScriptEngine.eval} that might lead to
* remote code executions depending on the scripting engine's capabilities.
* Before JDK 15, the Nashorn Engine
* was registered by default with ScriptEngineManager under several aliases,
* including "js". Nashorn allows
* {@link javax.script.ScriptEngine#eval(String)} and the like that might lead
* to remote code executions depending on the scripting engine's capabilities.
* Before JDK 15, the Nashorn Engine was registered by default with
* ScriptEngineManager under several aliases, including "js". Nashorn allows
* access to JVM classes, for example {@link java.lang.Runtime} allowing the
* execution of arbitrary OS commands.
* Several other scripting engines can be embedded to the JVM (they must follow
* the <a href="https://www.jcp.org/en/jsr/detail?id=223">JSR-223</a>
* execution of arbitrary OS commands. Several other scripting engines can be
* embedded to the JVM (they must follow the
* <a href="https://www.jcp.org/en/jsr/detail?id=223">JSR-223 </a>
* specification).
* </p>
**/
@SuppressWarnings("unused")
public final class ScriptEngineInjection {
private static final String ENGINE = "js";
private static final String PAYLOAD = "1+1";

private static char[] guideMarkableReaderTowardsEquality(Reader reader, String target, int id)
throws IOException {
final int size = target.length();
char[] current = new char[size];
int n = 0;

if (!reader.markSupported()) {
throw new IllegalStateException("Reader does not support mark - not possible to fuzz");
}

reader.mark(size);

while (n < size) {
int count = reader.read(current, n, size - n);
if (count < 0)
break;
n += count;
}
reader.reset();

Jazzer.guideTowardsEquality(new String(current), target, id);

return current;
}

@MethodHook(type = HookType.REPLACE, targetClassName = "javax.script.ScriptEngineManager",
targetMethod = "registerEngineName")
public static Object
ensureScriptEngine(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
throws Throwable {
return method.invokeWithArguments(Stream
.concat(Stream.of(thisObject),
Stream.concat(Stream.of((Object) ENGINE),
Arrays.stream(arguments, 1, arguments.length)))
.toArray());
}

@MethodHook(type = HookType.REPLACE, targetClassName = "javax.script.ScriptEngineManager",
targetMethod = "getEngineByName",
targetMethodDescriptor = "(Ljava/lang/String;)Ljavax/script/ScriptEngine;")
public static Object
hookEngineName(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
throws Throwable {
String engine = (String) arguments[0];
Jazzer.guideTowardsEquality(engine, ENGINE, hookId);
return method.invokeWithArguments(
Stream.concat(Stream.of(thisObject), Arrays.stream(arguments)).toArray());
}
private static final String PAYLOAD = "\"jaz\"+\"zer\"";

/**
* String variants of eval can be intercepted by before hooks, as the script
* content can directly be checked for the presence of the payload.
*/
@MethodHook(type = HookType.BEFORE, targetClassName = "javax.script.ScriptEngine",
targetMethod = "eval", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;")
@MethodHook(type = HookType.BEFORE, targetClassName = "javax.script.ScriptEngine",
targetMethod = "eval", targetMethodDescriptor = "(Ljava/io/Reader;)Ljava/lang/Object;")
targetMethod = "eval",
targetMethodDescriptor = "(Ljava/lang/String;Ljavax/script/ScriptContext;)Ljava/lang/Object;")
@MethodHook(type = HookType.BEFORE, targetClassName = "javax.script.ScriptEngine",
targetMethod = "eval",
targetMethodDescriptor = "(Ljava/lang/String;Ljavax/script/Bindings;)Ljava/lang/Object;")
public static void
checkScriptEngineExecuteString(
MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
checkScriptContent((String) arguments[0], hookId);
}

/**
* Reader variants of eval must be intercepted by replace hooks, as their
* contents are converted to strings, for the payload check, and back to readers
* for the actual method invocation.
*/
@MethodHook(type = HookType.REPLACE, targetClassName = "javax.script.ScriptEngine",
targetMethod = "eval", targetMethodDescriptor = "(Ljava/io/Reader;)Ljava/lang/Object;")
@MethodHook(type = HookType.REPLACE, targetClassName = "javax.script.ScriptEngine",
targetMethod = "eval",
targetMethodDescriptor = "(Ljava/io/Reader;Ljavax/script/ScriptContext;)Ljava/lang/Object;")
@MethodHook(type = HookType.REPLACE, targetClassName = "javax.script.ScriptEngine",
targetMethod = "eval",
targetMethodDescriptor = "(Ljava/io/Reader;Ljavax/script/Bindings;)Ljava/lang/Object;")
public static Object
checkScriptEngineExecute(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
throws Throwable {
String script = null;
if (arguments[0] != null) {
String content = readAll((Reader) arguments[0]);
checkScriptContent(content, hookId);
arguments[0] = new StringReader(content);
}
return method.invokeWithArguments(thisObject, arguments);
}

if (arguments[0] instanceof String) {
script = (String) arguments[0];
Jazzer.guideTowardsEquality(script, PAYLOAD, hookId);
} else {
script =
new String(guideMarkableReaderTowardsEquality((Reader) arguments[0], PAYLOAD, hookId));
private static void checkScriptContent(String content, int hookId) {
if (content != null) {
if (content.contains(PAYLOAD)) {
Jazzer.reportFindingFromHook(new FuzzerSecurityIssueCritical(
"Script Engine Injection: Insecure user input was used in script engine invocation.\n"
+ "Depending on the script engine's capabilities this could lead to sandbox escape and remote code execution."));
} else {
Jazzer.guideTowardsContainment(content, PAYLOAD, hookId);
}
}
}

if (script.equals(PAYLOAD)) {
Jazzer.reportFindingFromHook(new FuzzerSecurityIssueCritical("Possible script execution"));
private static String readAll(Reader reader) throws IOException {
StringBuilder content = new StringBuilder();
char[] buffer = new char[4096];
int numChars;
while ((numChars = reader.read(buffer)) >= 0) {
content.append(buffer, 0, numChars);
}
return content.toString();
}
}

0 comments on commit 7dcfa35

Please sign in to comment.