diff options
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.java | 127 |
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))); + } + } +} |