blob: e9500d6d0564ac6212a42fb7de5dc559e766eb1e [file] [log] [blame]
// Copyright 2022 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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.lang.invoke.MethodHandle;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
import javax.script.ScriptEngineManager;
/**
* Detects Script Engine injection
*
* <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
* 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>
* specification).
* </p>
**/
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());
}
@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;")
public static void
checkScriptEngineExecute(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
throws Throwable {
String script = null;
if (arguments[0] instanceof String) {
script = (String) arguments[0];
Jazzer.guideTowardsEquality(script, PAYLOAD, hookId);
} else {
script =
new String(guideMarkableReaderTowardsEquality((Reader) arguments[0], PAYLOAD, hookId));
}
if (script.equals(PAYLOAD)) {
Jazzer.reportFindingFromHook(new FuzzerSecurityIssueCritical("Possible script execution"));
}
}
}