aboutsummaryrefslogtreecommitdiff
path: root/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java
diff options
context:
space:
mode:
Diffstat (limited to 'sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java')
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java127
1 files changed, 127 insertions, 0 deletions
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java
new file mode 100644
index 00000000..3ff48e3c
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java
@@ -0,0 +1,127 @@
+/*
+ * 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.code_intelligence.jazzer.sanitizers;
+
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.Jazzer;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
+
+public class ServerSideRequestForgery {
+ // Set via reflection by Jazzer's BugDetectors API.
+ public static final AtomicReference<BiPredicate<String, Integer>> connectionPermitted =
+ new AtomicReference<>((host, port) -> false);
+
+ /**
+ * {@link java.net.Socket} is used in many JDK classes to open network connections. Internally it
+ * delegates to {@link java.net.SocketImpl}, hence, for most situations it's sufficient to hook
+ * the call site {@link java.net.Socket} itself. As {@link java.net.SocketImpl} is an abstract
+ * class all call sites invoking "connect" on concrete implementations get hooked. As JKD internal
+ * classes are normally ignored, they have to be marked for hooking explicitly. In this case, all
+ * internal classes calling "connect" on {@link java.net.SocketImpl} should be listed below.
+ * Internal classes using {@link java.net.SocketImpl#connect(String, int)}:
+ * <ul>
+ * <li>java.net.Socket (hook required)
+ * <li>java.net.AbstractPlainSocketImpl (no direct usage, no hook required)
+ * <li>java.net.PlainSocketImpl (no direct usage, no hook required)
+ * <li>java.net.HttpConnectSocketImpl (only used in Socket, which is already listed)
+ * <li>java.net.SocksSocketImpl (used in Socket, but also invoking super.connect directly,
+ * hook required)
+ * <li>java.net.ServerSocket (security check, no hook required)
+ * </ul>
+ */
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.net.SocketImpl",
+ targetMethod = "connect",
+ additionalClassesToHook =
+ {
+ "java.net.Socket",
+ "java.net.SocksSocketImpl",
+ })
+ public static void
+ checkSsrfSocket(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ checkSsrf(arguments);
+ }
+
+ /**
+ * {@link java.nio.channels.SocketChannel} is used in many JDK classes to open (non-blocking)
+ * network connections, e.g. {@link java.net.http.HttpClient} uses it internally. The actual
+ * connection is established in the abstract "connect" method. Hooking that also hooks invocations
+ * of all concrete implementations, from which only one exists in {@link
+ * sun.nio.ch.SocketChannelImpl}. "connect" is only called in {@link
+ * java.nio.channels.SocketChannel} itself and the two mentioned classes below.
+ */
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.nio.channels.SocketChannel",
+ targetMethod = "connect",
+ additionalClassesToHook =
+ {
+ "sun.nio.ch.SocketAdaptor",
+ "jdk.internal.net.http.PlainHttpConnection",
+ })
+ public static void
+ checkSsrfHttpConnection(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ checkSsrf(arguments);
+ }
+
+ private static void checkSsrf(Object[] arguments) {
+ if (arguments.length == 0) {
+ return;
+ }
+
+ String host;
+ int port;
+ if (arguments[0] instanceof InetSocketAddress) {
+ // Only implementation of java.net.SocketAddress.
+ InetSocketAddress address = (InetSocketAddress) arguments[0];
+ host = address.getHostName();
+ port = address.getPort();
+ } else if (arguments.length >= 2 && arguments[1] instanceof Integer) {
+ if (arguments[0] instanceof InetAddress) {
+ host = ((InetAddress) arguments[0]).getHostName();
+ } else if (arguments[0] instanceof String) {
+ host = (String) arguments[0];
+ } else {
+ return;
+ }
+ port = (int) arguments[1];
+ } else {
+ return;
+ }
+
+ if (port < 0 || port > 65535) {
+ return;
+ }
+
+ if (!connectionPermitted.get().test(host, port)) {
+ Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium(String.format(
+ "Server Side Request Forgery (SSRF)\n"
+ + "Attempted connection to: %s:%d\n"
+ + "Requests to destinations based on untrusted data could lead to exfiltration of "
+ + "sensitive data or exposure of internal services.\n\n"
+ + "If the fuzz test is expected to perform network connections, call "
+ + "com.code_intelligence.jazzer.api.BugDetectors#allowNetworkConnections at the "
+ + "beginning of your fuzz test and optionally provide a predicate matching the "
+ + "expected hosts.",
+ host, port)));
+ }
+ }
+}