blob: 3ff48e3cc3635e56e94b97afef987db3468ff2fd [file] [log] [blame]
/*
* 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)));
}
}
}