diff options
Diffstat (limited to 'sanitizers/src/test/java')
17 files changed, 740 insertions, 72 deletions
diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 5d2e1ca5..ea0a7f82 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -6,7 +6,10 @@ java_fuzz_target_test( srcs = [ "ObjectInputStreamDeserialization.java", ], - expected_findings = ["java.lang.ExceptionInInitializerError"], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + "java.lang.ExceptionInInitializerError", + ], target_class = "com.example.ObjectInputStreamDeserialization", ) @@ -15,7 +18,10 @@ java_fuzz_target_test( srcs = [ "ReflectiveCall.java", ], - expected_findings = ["java.lang.ExceptionInInitializerError"], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + "java.lang.ExceptionInInitializerError", + ], target_class = "com.example.ReflectiveCall", ) @@ -24,25 +30,30 @@ java_fuzz_target_test( srcs = [ "LibraryLoad.java", ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + ], target_class = "com.example.LibraryLoad", # loading of native libraries is very slow on macos, # especially using Java 17 target_compatible_with = SKIP_ON_MACOS, + # The reproducer doesn't contain the sanitizer and thus runs into an ordinary ignored + # UnsatisfiedLinkError. + verify_crash_reproducer = False, ) java_fuzz_target_test( name = "ExpressionLanguageInjection", srcs = [ "ExpressionLanguageInjection.java", - "InsecureEmailValidator.java", ], + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh"], target_class = "com.example.ExpressionLanguageInjection", + # The reproducer can't find jaz.Zer and thus doesn't crash. + verify_crash_reproducer = False, deps = [ - "@maven//:javax_el_javax_el_api", + "//sanitizers/src/test/java/com/example/el:ExpressionLanguageExample", "@maven//:javax_validation_validation_api", - "@maven//:javax_xml_bind_jaxb_api", - "@maven//:org_glassfish_javax_el", - "@maven//:org_hibernate_hibernate_validator", ], ) @@ -51,7 +62,9 @@ java_fuzz_target_test( srcs = [ "OsCommandInjectionProcessBuilder.java", ], + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical"], target_class = "com.example.OsCommandInjectionProcessBuilder", + verify_crash_reproducer = False, ) java_fuzz_target_test( @@ -59,17 +72,22 @@ java_fuzz_target_test( srcs = [ "OsCommandInjectionRuntimeExec.java", ], + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical"], target_class = "com.example.OsCommandInjectionRuntimeExec", + verify_crash_reproducer = False, ) java_fuzz_target_test( name = "LdapSearchInjection", srcs = [ "LdapSearchInjection.java", - "ldap/MockInitialContextFactory.java", "ldap/MockLdapContext.java", ], - expected_findings = ["javax.naming.directory.InvalidSearchFilterException"], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", + # The crashing input encoded by the replayer does not have valid syntax, but no hook. + "javax.naming.directory.InvalidSearchFilterException", + ], target_class = "com.example.LdapSearchInjection", deps = [ "@maven//:com_unboundid_unboundid_ldapsdk", @@ -80,10 +98,13 @@ java_fuzz_target_test( name = "LdapDnInjection", srcs = [ "LdapDnInjection.java", - "ldap/MockInitialContextFactory.java", "ldap/MockLdapContext.java", ], - expected_findings = ["javax.naming.NamingException"], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", + # The crashing input encoded by the reproducer does not have valid syntax, but no hook. + "javax.naming.NamingException", + ], target_class = "com.example.LdapDnInjection", deps = [ "@maven//:com_unboundid_unboundid_ldapsdk", @@ -93,7 +114,9 @@ java_fuzz_target_test( java_fuzz_target_test( name = "RegexInsecureQuoteInjection", srcs = ["RegexInsecureQuoteInjection.java"], + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], target_class = "com.example.RegexInsecureQuoteInjection", + verify_crash_reproducer = False, ) java_fuzz_target_test( @@ -101,7 +124,9 @@ java_fuzz_target_test( srcs = [ "RegexCanonEqInjection.java", ], + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], target_class = "com.example.RegexCanonEqInjection", + verify_crash_reproducer = False, ) java_fuzz_target_test( @@ -109,19 +134,40 @@ java_fuzz_target_test( srcs = [ "ClassLoaderLoadClass.java", ], - expected_findings = ["java.lang.ExceptionInInitializerError"], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + # Reproducer does not find the honeypot library and doesn't have the hook. + "java.lang.ExceptionInInitializerError", + ], target_class = "com.example.ClassLoaderLoadClass", ) java_fuzz_target_test( name = "RegexRoadblocks", srcs = ["RegexRoadblocks.java"], + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], fuzzer_args = [ # Limit the number of runs to verify that the regex roadblocks are # cleared quickly. "-runs=22000", ], target_class = "com.example.RegexRoadblocks", + verify_crash_reproducer = False, +) + +# Catching StackOverflowErrors doesn't work reliably across all systems and JDK versions. +# It may lead to a native crash before we can handle the exception in Java, therefore the +# test is set to manual execution. +java_fuzz_target_test( + name = "StackOverflowRegexInjection", + srcs = ["StackOverflowRegexInjection.java"], + allowed_findings = ["java.util.regex.PatternSyntaxException"], + fuzzer_args = [ + "-runs=1", + ], + tags = ["manual"], + target_class = "com.example.StackOverflowRegexInjection", + verify_crash_reproducer = False, ) java_fuzz_target_test( @@ -129,7 +175,7 @@ java_fuzz_target_test( srcs = [ "SqlInjection.java", ], - expected_findings = [ + allowed_findings = [ "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", "org.h2.jdbc.JdbcSQLSyntaxErrorException", ], @@ -138,3 +184,90 @@ java_fuzz_target_test( "@maven//:com_h2database_h2", ], ) + +java_test( + name = "DisabledHooksTest", + size = "small", + srcs = [ + "DisabledHooksTest.java", + ], + test_class = "com.example.DisabledHooksTest", + deps = [ + "//src/main/java/com/code_intelligence/jazzer/api", + "//src/main/java/com/code_intelligence/jazzer/api:hooks", + ], +) + +java_fuzz_target_test( + name = "XPathInjection", + srcs = [ + "XPathInjection.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + ], + target_class = "com.example.XPathInjection", + # Fuzz target catches the syntax exception triggered by the reproducer without the sanitizer. + verify_crash_reproducer = False, +) + +java_fuzz_target_test( + name = "SsrfSocketConnect", + srcs = [ + "SsrfSocketConnect.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium", + ], + target_class = "com.example.SsrfSocketConnect", + verify_crash_reproducer = False, +) + +java_fuzz_target_test( + name = "SsrfSocketConnectToHost", + srcs = [ + "SsrfSocketConnectToHost.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium", + ], + target_class = "com.example.SsrfSocketConnectToHost", + verify_crash_reproducer = False, +) + +java_fuzz_target_test( + name = "SsrfUrlConnection", + srcs = [ + "SsrfUrlConnection.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium", + ], + target_class = "com.example.SsrfUrlConnection", + verify_crash_reproducer = False, +) + +java_fuzz_target_test( + name = "SsrfHttpClient", + srcs = [ + "SsrfHttpClient.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium", + ], + tags = ["no-jdk8"], + target_class = "com.example.SsrfHttpClient", + verify_crash_reproducer = False, +) + +java_fuzz_target_test( + name = "ScriptEngineInjection", + srcs = [ + "ScriptEngineInjection.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", + ], + target_class = "com.example.ScriptEngineInjection", + verify_crash_reproducer = False, +) diff --git a/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java b/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java index c3fa47ac..207f29cd 100644 --- a/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java +++ b/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java @@ -22,9 +22,11 @@ public class ClassLoaderLoadClass { String input = data.consumeRemainingAsAsciiString(); try { // create an instance to trigger class initialization - ClassLoaderLoadClass.class.getClassLoader().loadClass(input).getConstructor().newInstance(); - } catch (ClassNotFoundException | InvocationTargetException | InstantiationException - | IllegalAccessException | NoSuchMethodException ignored) { + ClassLoaderLoadClass.class.getClassLoader().loadClass(input).newInstance(); + // TODO(khaled): this fails to reproduce the finding. It seems that this is related to not + // throwing a hard-to-catch error when not running in the fuzzing mode. + // ClassLoaderLoadClass.class.getClassLoader().loadClass(input).getConstructor().newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ignored) { } } } diff --git a/sanitizers/src/test/java/com/example/DisabledHooksTest.java b/sanitizers/src/test/java/com/example/DisabledHooksTest.java new file mode 100644 index 00000000..763cd637 --- /dev/null +++ b/sanitizers/src/test/java/com/example/DisabledHooksTest.java @@ -0,0 +1,110 @@ +// 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.FuzzerSecurityIssueHigh; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.util.Base64; +import org.junit.After; +import org.junit.Test; + +public class DisabledHooksTest { + public static void triggerReflectiveCallSanitizer() { + try { + Class.forName("jaz.Zer").newInstance(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException ignored) { + } + } + + public static void triggerExpressionLanguageInjectionSanitizer() throws Throwable { + try { + Class.forName("jaz.Zer").getMethod("el").invoke(null); + } catch (InvocationTargetException e) { + throw e.getCause(); + } catch (IllegalAccessException | ClassNotFoundException | NoSuchMethodException ignore) { + } + } + + public static void triggerDeserializationSanitizer() { + byte[] data = + Base64.getDecoder().decode("rO0ABXNyAAdqYXouWmVyAAAAAAAAACoCAAFCAAlzYW5pdGl6ZXJ4cAEK"); + try { + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); + System.out.println(ois.readObject()); + } catch (IOException | ClassNotFoundException ignore) { + } + } + + @After + public void resetDisabledHooksProperty() { + System.clearProperty("jazzer.disabled_hooks"); + } + + @Test(expected = FuzzerSecurityIssueHigh.class) + public void enableReflectiveCallSanitizer() { + triggerReflectiveCallSanitizer(); + } + + @Test(expected = FuzzerSecurityIssueHigh.class) + public void enableDeserializationSanitizer() { + triggerDeserializationSanitizer(); + } + + @Test(expected = FuzzerSecurityIssueHigh.class) + public void enableExpressionLanguageInjectionSanitizer() throws Throwable { + triggerExpressionLanguageInjectionSanitizer(); + } + + @Test + public void disableReflectiveCallSanitizer() { + System.setProperty( + "jazzer.disabled_hooks", "com.code_intelligence.jazzer.sanitizers.ReflectiveCall"); + triggerReflectiveCallSanitizer(); + } + + @Test + public void disableDeserializationSanitizer() { + System.setProperty( + "jazzer.disabled_hooks", "com.code_intelligence.jazzer.sanitizers.Deserialization"); + triggerDeserializationSanitizer(); + } + + @Test + public void disableExpressionLanguageSanitizer() throws Throwable { + System.setProperty("jazzer.disabled_hooks", + "com.code_intelligence.jazzer.sanitizers.ExpressionLanguageInjection"); + triggerExpressionLanguageInjectionSanitizer(); + } + + @Test(expected = FuzzerSecurityIssueHigh.class) + public void disableReflectiveCallAndEnableDeserialization() { + System.setProperty( + "jazzer.disabled_hooks", "com.code_intelligence.jazzer.sanitizers.ReflectiveCall"); + triggerReflectiveCallSanitizer(); + triggerDeserializationSanitizer(); + } + + @Test + public void disableAllSanitizers() throws Throwable { + System.setProperty("jazzer.disabled_hooks", + "com.code_intelligence.jazzer.sanitizers.ReflectiveCall," + + "com.code_intelligence.jazzer.sanitizers.Deserialization," + + "com.code_intelligence.jazzer.sanitizers.ExpressionLanguageInjection"); + triggerReflectiveCallSanitizer(); + triggerExpressionLanguageInjectionSanitizer(); + triggerDeserializationSanitizer(); + } +} diff --git a/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java index e26a9117..7d0192ab 100644 --- a/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java +++ b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java @@ -15,33 +15,20 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import javax.validation.*; - -class UserData { - public UserData(String email) { - this.email = email; - } - - @ValidEmailConstraint private String email; -} - -@Constraint(validatedBy = InsecureEmailValidator.class) -@Target({ElementType.METHOD, ElementType.FIELD}) -@Retention(RetentionPolicy.RUNTIME) -@interface ValidEmailConstraint { - String message() default "Invalid email address"; - Class<?>[] groups() default {}; - Class<? extends Payload>[] payload() default {}; -} +import com.example.el.UserData; +import java.util.logging.Level; +import java.util.logging.LogManager; +import javax.validation.Validation; +import javax.validation.Validator; public class ExpressionLanguageInjection { final private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + public static void fuzzerInitialize() { + LogManager.getLogManager().getLogger("").setLevel(Level.SEVERE); + } + public static void fuzzerTestOneInput(FuzzedDataProvider data) { UserData uncheckedUserData = new UserData(data.consumeRemainingAsString()); validator.validate(uncheckedUserData); diff --git a/sanitizers/src/test/java/com/example/LdapDnInjection.java b/sanitizers/src/test/java/com/example/LdapDnInjection.java index 911db1dc..2fdf4a0c 100644 --- a/sanitizers/src/test/java/com/example/LdapDnInjection.java +++ b/sanitizers/src/test/java/com/example/LdapDnInjection.java @@ -15,20 +15,13 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.util.Hashtable; -import javax.naming.Context; -import javax.naming.NamingException; -import javax.naming.directory.InitialDirContext; +import com.example.ldap.MockLdapContext; +import javax.naming.directory.DirContext; import javax.naming.directory.SearchControls; +@SuppressWarnings("BanJNDI") public class LdapDnInjection { - private static InitialDirContext ctx; - - public static void fuzzerInitialize() throws NamingException { - Hashtable<String, String> env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.example.ldap.MockInitialContextFactory"); - ctx = new InitialDirContext(env); - } + private static final DirContext ctx = new MockLdapContext(); public static void fuzzerTestOneInput(FuzzedDataProvider fuzzedDataProvider) throws Exception { // Externally provided DN input needs to be escaped properly diff --git a/sanitizers/src/test/java/com/example/LdapSearchInjection.java b/sanitizers/src/test/java/com/example/LdapSearchInjection.java index b3dfee74..4ac84931 100644 --- a/sanitizers/src/test/java/com/example/LdapSearchInjection.java +++ b/sanitizers/src/test/java/com/example/LdapSearchInjection.java @@ -15,20 +15,13 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.util.Hashtable; -import javax.naming.Context; -import javax.naming.NamingException; +import com.example.ldap.MockLdapContext; import javax.naming.directory.SearchControls; -import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapContext; +@SuppressWarnings("BanJNDI") public class LdapSearchInjection { - private static InitialLdapContext ctx; - - public static void fuzzerInitialize() throws NamingException { - Hashtable<String, String> env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.example.ldap.MockInitialContextFactory"); - ctx = new InitialLdapContext(env, null); - } + private static final LdapContext ctx = new MockLdapContext(); public static void fuzzerTestOneInput(FuzzedDataProvider fuzzedDataProvider) throws Exception { // Externally provided LDAP query input needs to be escaped properly diff --git a/sanitizers/src/test/java/com/example/ReflectiveCall.java b/sanitizers/src/test/java/com/example/ReflectiveCall.java index e6b62b45..d7b3e46c 100644 --- a/sanitizers/src/test/java/com/example/ReflectiveCall.java +++ b/sanitizers/src/test/java/com/example/ReflectiveCall.java @@ -22,8 +22,8 @@ public class ReflectiveCall { if (input.startsWith("@")) { String className = input.substring(1); try { - Class.forName(className); - } catch (ClassNotFoundException ignored) { + Class.forName(className).newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ignored) { } } } 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..631b7ab8 --- /dev/null +++ b/sanitizers/src/test/java/com/example/ScriptEngineInjection.java @@ -0,0 +1,171 @@ +// 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. +// 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 java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.util.List; +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; + +public class ScriptEngineInjection { + private final static ScriptEngine engine = new DummyScriptEngine(); + private final static ScriptContext context = new DummyScriptContext(); + + private static void insecureScriptEval(String input) throws Exception { + engine.eval(new StringReader(input), context); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + try { + insecureScriptEval(data.consumeRemainingAsAsciiString()); + } catch (Exception ignored) { + } + } + + private static class DummyScriptEngine implements ScriptEngine { + @Override + public Bindings createBindings() { + return null; + } + + @Override + public Object eval(String script) { + return null; + } + + @Override + public Object eval(Reader reader) { + return null; + } + + @Override + public Object eval(String script, ScriptContext context) { + return null; + } + + @Override + public Object eval(Reader reader, ScriptContext context) { + return null; + } + + @Override + public Object eval(String script, Bindings n) { + return null; + } + + @Override + public Object eval(Reader reader, Bindings n) { + 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() {} + } + + private static class DummyScriptContext implements ScriptContext { + @Override + public void setBindings(Bindings bindings, int scope) {} + + @Override + public Bindings getBindings(int scope) { + return null; + } + + @Override + public void setAttribute(String name, Object value, int scope) {} + + @Override + public Object getAttribute(String name, int scope) { + return null; + } + + @Override + public Object removeAttribute(String name, int scope) { + return null; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public int getAttributesScope(String name) { + return 0; + } + + @Override + public Writer getWriter() { + return null; + } + + @Override + public Writer getErrorWriter() { + return null; + } + + @Override + public void setWriter(Writer writer) {} + + @Override + public void setErrorWriter(Writer writer) {} + + @Override + public Reader getReader() { + return null; + } + + @Override + public void setReader(Reader reader) {} + + @Override + public List<Integer> getScopes() { + return null; + } + } +} diff --git a/sanitizers/src/test/java/com/example/SsrfHttpClient.java b/sanitizers/src/test/java/com/example/SsrfHttpClient.java new file mode 100644 index 00000000..6da561a9 --- /dev/null +++ b/sanitizers/src/test/java/com/example/SsrfHttpClient.java @@ -0,0 +1,39 @@ +// 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. +// 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 java.io.IOException; +import java.net.*; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class SsrfHttpClient { + private static final HttpClient CLIENT = + HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(); + + public static void fuzzerTestOneInput(FuzzedDataProvider data) + throws IOException, InterruptedException { + String hostname = data.consumeString(15); + URI uri; + try { + uri = URI.create("https://" + hostname); + HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build(); + CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IllegalArgumentException ignored) { + } + } +} diff --git a/sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java b/sanitizers/src/test/java/com/example/SsrfSocketConnect.java index b674f5c5..f1d7a59b 100644 --- a/sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java +++ b/sanitizers/src/test/java/com/example/SsrfSocketConnect.java @@ -1,4 +1,4 @@ -// Copyright 2021 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. @@ -12,15 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.example.ldap; +package com.example; -import java.util.Hashtable; -import javax.naming.Context; -import javax.naming.NamingException; -import javax.naming.spi.InitialContextFactory; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.net.Socket; -public class MockInitialContextFactory implements InitialContextFactory { - public Context getInitialContext(Hashtable environment) { - return new MockLdapContext(); +public class SsrfSocketConnect { + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + String hostname = data.consumeString(15); + try (Socket s = new Socket(hostname, 80)) { + s.getInetAddress(); + } } } diff --git a/sanitizers/src/test/java/com/example/SsrfSocketConnectToHost.java b/sanitizers/src/test/java/com/example/SsrfSocketConnectToHost.java new file mode 100644 index 00000000..3e60e503 --- /dev/null +++ b/sanitizers/src/test/java/com/example/SsrfSocketConnectToHost.java @@ -0,0 +1,46 @@ +// 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. +// 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.BugDetectors; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.api.Jazzer; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +public class SsrfSocketConnectToHost { + // We don't actually care about establishing a connection and thus choose the lowest possible + // timeout. + private static final int CONNECTION_TIMEOUT_MS = 1; + + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + String host = data.consumeAsciiString(15); + int port = data.consumeInt(1, 65535); + + try (AutoCloseable ignored = BugDetectors.allowNetworkConnections()) { + // Verify that policies nest properly. + try (AutoCloseable ignored1 = BugDetectors.allowNetworkConnections( + (String h, Integer p) -> h.equals("localhost"))) { + try (AutoCloseable ignored2 = BugDetectors.allowNetworkConnections()) { + } + try (Socket s = new Socket()) { + s.connect(new InetSocketAddress(host, port), CONNECTION_TIMEOUT_MS); + } catch (IOException ignored3) { + } + } + } + } +} diff --git a/sanitizers/src/test/java/com/example/SsrfUrlConnection.java b/sanitizers/src/test/java/com/example/SsrfUrlConnection.java new file mode 100644 index 00000000..8ea940a1 --- /dev/null +++ b/sanitizers/src/test/java/com/example/SsrfUrlConnection.java @@ -0,0 +1,33 @@ +// 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. +// 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 java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class SsrfUrlConnection { + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + String hostname = data.consumeString(15); + try { + URL url = new URL("https://" + hostname); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.getInputStream(); + } catch (IOException | IllegalArgumentException ignored) { + } + } +} diff --git a/sanitizers/src/test/java/com/example/StackOverflowRegexInjection.java b/sanitizers/src/test/java/com/example/StackOverflowRegexInjection.java new file mode 100644 index 00000000..92dfcf38 --- /dev/null +++ b/sanitizers/src/test/java/com/example/StackOverflowRegexInjection.java @@ -0,0 +1,51 @@ +// 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 java.util.regex.Pattern; + +/** + * Compiling a regex pattern can lead to stack overflows and thus is caught + * in the constructor of {@link java.util.regex.Pattern} and rethrown as a + * {@link java.util.regex.PatternSyntaxException}. + * The {@link com.code_intelligence.jazzer.sanitizers.RegexInjection} sanitizer + * uses this exception to detect injections and would incorrectly report a + * finding. Exceptions caused by stack overflows should not be handled in the + * hook as it's very unlikely that the fuzzer generates a pattern causing a + * stack overflow before it generates an invalid one. + */ +@SuppressWarnings({"ReplaceOnLiteralHasNoEffect", "ResultOfMethodCallIgnored"}) +public class StackOverflowRegexInjection { + public static void fuzzerTestOneInput(FuzzedDataProvider ignored) { + // load regex classes by using them beforehand, + // otherwise initialization would cause other issues. + Pattern.compile("\n").matcher("some string").replaceAll("\\\\n"); + + generatePatternSyntaxException(); + } + + @SuppressWarnings("InfiniteRecursion") + private static void generatePatternSyntaxException() { + // try-catch on every level to not unwind the stack + try { + // generate stack overflow + generatePatternSyntaxException(); + } catch (StackOverflowError e) { + // invoke regex injection hook + "some sting".replaceAll("\n", "\\\\n"); + } + } +} diff --git a/sanitizers/src/test/java/com/example/XPathInjection.java b/sanitizers/src/test/java/com/example/XPathInjection.java new file mode 100644 index 00000000..e8fe22a0 --- /dev/null +++ b/sanitizers/src/test/java/com/example/XPathInjection.java @@ -0,0 +1,53 @@ +// 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 java.io.*; +import javax.xml.parsers.*; +import javax.xml.xpath.*; +import org.w3c.dom.Document; +import org.xml.sax.*; + +public class XPathInjection { + static Document doc = null; + static XPath xpath = null; + + public static void fuzzerInitialize() throws Exception { + String xmlFile = "<user name=\"user\" pass=\"pass\"></user>"; + + DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); + domFactory.setNamespaceAware(true); + DocumentBuilder builder = domFactory.newDocumentBuilder(); + doc = builder.parse(new InputSource(new StringReader(xmlFile))); + + XPathFactory xpathFactory = XPathFactory.newInstance(); + xpath = xpathFactory.newXPath(); + } + + public static void unsafeEval(String user, String pass) { + if (user != null && pass != null) { + String expression = "/user[@name='" + user + "' and @pass='" + pass + "']"; + try { + xpath.evaluate(expression, doc, XPathConstants.BOOLEAN); + } catch (XPathExpressionException e) { + } + } + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + unsafeEval(data.consumeString(20), data.consumeRemainingAsString()); + } +} diff --git a/sanitizers/src/test/java/com/example/el/BUILD.bazel b/sanitizers/src/test/java/com/example/el/BUILD.bazel new file mode 100644 index 00000000..bf12a48b --- /dev/null +++ b/sanitizers/src/test/java/com/example/el/BUILD.bazel @@ -0,0 +1,15 @@ +java_library( + name = "ExpressionLanguageExample", + srcs = [ + "InsecureEmailValidator.java", + "UserData.java", + ], + visibility = ["//sanitizers/src/test/java/com/example:__pkg__"], + deps = [ + "@maven//:javax_el_javax_el_api", + "@maven//:javax_validation_validation_api", + "@maven//:javax_xml_bind_jaxb_api", + "@maven//:org_glassfish_javax_el", + "@maven//:org_hibernate_hibernate_validator", + ], +) diff --git a/sanitizers/src/test/java/com/example/InsecureEmailValidator.java b/sanitizers/src/test/java/com/example/el/InsecureEmailValidator.java index d61e888d..e10b082e 100644 --- a/sanitizers/src/test/java/com/example/InsecureEmailValidator.java +++ b/sanitizers/src/test/java/com/example/el/InsecureEmailValidator.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.example; +package com.example.el; import static java.lang.String.format; diff --git a/sanitizers/src/test/java/com/example/el/UserData.java b/sanitizers/src/test/java/com/example/el/UserData.java new file mode 100644 index 00000000..305e78ee --- /dev/null +++ b/sanitizers/src/test/java/com/example/el/UserData.java @@ -0,0 +1,41 @@ +/* + * 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. + * 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.el; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +public class UserData { + public UserData(String email) { + this.email = email; + } + + @ValidEmailConstraint private String email; +} + +@Constraint(validatedBy = InsecureEmailValidator.class) +@Target({ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@interface ValidEmailConstraint { + String message() default "Invalid email address"; + Class<?>[] groups() default {}; + Class<? extends Payload>[] payload() default {}; +} |