diff options
author | gdemarcsek <gyorgy.demarcsek@protonmail.ch> | 2022-11-21 22:43:23 +0100 |
---|---|---|
committer | Norbert Schneider <mail@bertschneider.de> | 2023-06-13 08:57:04 +0200 |
commit | 1bf61b4416bc96b0a0bb4c6c941d5b9b88d12c4e (patch) | |
tree | 1d1ef98ea758d171ae61d3aa8e5d91a44656fadb | |
parent | d3d742bf8e1df8b8b60a86dc5834bbb2fe6ff5fe (diff) | |
download | jazzer-api-1bf61b4416bc96b0a0bb4c6c941d5b9b88d12c4e.tar.gz |
add script engine injection sanitizer
4 files changed, 240 insertions, 0 deletions
diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index 280ccfde..f4894f64 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -24,6 +24,7 @@ _sanitizer_class_names = [ "ReflectiveCall", "RegexInjection", "RegexRoadblocks", + "ScriptEngineInjection", "ServerSideRequestForgery", "SqlInjection", "XPathInjection", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index 21217db0..7f416dfd 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -28,6 +28,14 @@ java_library( ], ) +java_library( + name = "script_engine_injection", + srcs = ["ScriptEngineInjection.java"], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api:hooks", + ], +) + kt_jvm_library( name = "sanitizers", srcs = [ @@ -45,6 +53,7 @@ kt_jvm_library( runtime_deps = [ ":regex_roadblocks", ":server_side_request_forgery", + ":script_engine_injection", ":sql_injection", ], deps = [ diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java new file mode 100644 index 00000000..4236287c --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java @@ -0,0 +1,112 @@ +// 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.FuzzerSecurityIssueHigh; +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.lang.invoke.MethodHandle; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Stream; +import java.io.IOException; +import java.io.Reader; + +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 exmaple {@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") + 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") + 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(String.format("Possible script execution"))); + } + } +} diff --git a/sanitizers/src/test/java/com/example/ScriptEngineInjection.java b/sanitizers/src/test/java/com/example/ScriptEngineInjection.java new file mode 100644 index 00000000..cf9bc4f6 --- /dev/null +++ b/sanitizers/src/test/java/com/example/ScriptEngineInjection.java @@ -0,0 +1,118 @@ +// 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical; + +import java.io.Reader; +import java.io.StringReader; + +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +class DummyScriptEngine implements ScriptEngine { + @Override + public Bindings createBindings() { + return null; + } + + @Override + public Object eval(String script) throws ScriptException { + return null; + } + + @Override + public Object eval(Reader reader) throws ScriptException { + return null; + } + + @Override + public Object eval(String script, ScriptContext context) throws ScriptException { + return null; + } + + @Override + public Object eval(Reader reader, ScriptContext context) throws ScriptException { + return null; + } + + @Override + public Object eval(String script, Bindings n) throws ScriptException { + return null; + } + + @Override + public Object eval(Reader reader, Bindings n) throws ScriptException { + return null; + } + + @Override + public Object get(String key) { + return null; + } + + @Override + public Bindings getBindings(int scope) { + return null; + } + + @Override + public ScriptContext getContext() { + return null; + } + + @Override + public ScriptEngineFactory getFactory() { + return null; + } + + @Override + public void put(String key, Object value) { + } + + @Override + public void setBindings(Bindings bindings, int scope) { + } + + @Override + public void setContext(ScriptContext context) { + } + + public DummyScriptEngine() { + } +} + +public class ScriptEngineInjection { + static ScriptEngine engine = new DummyScriptEngine(); + + static void insecureScriptEval(String input) throws Exception { + engine.eval(new StringReader(input)); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + try { + insecureScriptEval(data.consumeRemainingAsAsciiString()); + } catch (Throwable t) { + if (t instanceof FuzzerSecurityIssueCritical) { + throw t; + } + } + } +} |