diff --git a/Makefile b/Makefile index 8cb21666..a69229d6 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,10 @@ build: clean check_binaries ./gradlew agent_api:shadowJar cp agent_api/build/libs/agent*-all.jar dist/agent_api.jar + + ./gradlew agent_bootstrap:shadowJar + cp agent_bootstrap/build/libs/agent*-all.jar dist/agent_bootstrap.jar + mock_init: docker kill mock_core && docker rm mock_core cd end2end/server && docker build -t mock_core . diff --git a/agent/build.gradle b/agent/build.gradle index d6656bf9..bf4326fb 100644 --- a/agent/build.gradle +++ b/agent/build.gradle @@ -5,6 +5,7 @@ plugins { dependencies { implementation project(':agent_api') + compileOnly project(':agent_bootstrap') implementation 'net.bytebuddy:byte-buddy:1.15.11' // Compile only for interface types : compileOnly 'jakarta.servlet:jakarta.servlet-api:6.1.0' // spring 3 -> jakarta diff --git a/agent/src/main/java/dev/aikido/agent/Agent.java b/agent/src/main/java/dev/aikido/agent/Agent.java index 89b859ea..a518a174 100644 --- a/agent/src/main/java/dev/aikido/agent/Agent.java +++ b/agent/src/main/java/dev/aikido/agent/Agent.java @@ -6,11 +6,19 @@ import dev.aikido.agent_api.helpers.logging.LogManager; import dev.aikido.agent_api.helpers.logging.Logger; import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; import java.io.File; +import java.io.IOException; import java.lang.instrument.Instrumentation; +import java.util.jar.JarFile; import static dev.aikido.agent.ByteBuddyInitializer.createAgentBuilder; import static dev.aikido.agent.DaemonStarter.startDaemon; @@ -19,17 +27,31 @@ public class Agent { private static final Logger logger = LogManager.getLogger(Agent.class); + public static void premain(String agentArgs, Instrumentation inst) { // Check for 'AIKIDO_DISABLE' : if (new BooleanEnv("AIKIDO_DISABLE", /*default value*/ false).getValue()) { return; // AIKIDO_DISABLE is true, so we will not be wrapping anything. } logger.info("Zen by Aikido v%s starting.", Config.pkgVersion); - setAikidoSysProperties(); + try { + setAikidoSysProperties(inst); + } catch (Exception e) { + logger.error(e.getMessage()); + } + + // Modify bootstrap class path (includes the core Java classes), so we can have some shared + // code for core java classes when wrapping. + try { + inst.appendToBootstrapClassLoaderSearch(new JarFile(getPathToAikidoDirectory() + "/agent_bootstrap.jar")); + } catch (IOException e) { + logger.error(e.getMessage()); + } // Test loading of zen binaries : loadLibrary(); + ElementMatcher.Junction wrapperTypeDescriptors = ElementMatchers.none(); for(Wrapper wrapper: WRAPPERS) { wrapperTypeDescriptors = wrapperTypeDescriptors.or(wrapper.getTypeMatcher()); @@ -56,11 +78,14 @@ public static AgentBuilder.Transformer get() { return adviceAgentBuilder; } } - private static void setAikidoSysProperties() { + private static String getPathToAikidoDirectory() { String pathToAgentJar = Agent.class.getProtectionDomain().getCodeSource().getLocation().getPath(); - String pathToAikidoDirectory = new File(pathToAgentJar).getParent(); - String jarPath = "file:" + pathToAikidoDirectory + "/agent_api.jar"; - System.setProperty("AIK_agent_dir", pathToAikidoDirectory); + return new File(pathToAgentJar).getParent(); + } + private static void setAikidoSysProperties(Instrumentation inst) throws IOException { + + String jarPath = "file:" + getPathToAikidoDirectory() + "/agent_api.jar"; + System.setProperty("AIK_agent_dir", getPathToAikidoDirectory()); System.setProperty("AIK_agent_api_jar", jarPath); } -} \ No newline at end of file +} diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientSendWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientSendWrapper.java index 986e6341..4a1655c7 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientSendWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientSendWrapper.java @@ -1,21 +1,16 @@ package dev.aikido.agent.wrappers; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; -import java.lang.reflect.Method; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; -import java.net.URLClassLoader; import java.net.http.HttpClient; import java.net.http.HttpRequest; -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; - public class HttpClientSendWrapper implements Wrapper { public String getName() { // Wrap send(HttpRequest req, ...) and sendAsync(HttpRequest req, ...) on HttpClient instance @@ -32,37 +27,15 @@ public ElementMatcher getTypeMatcher() { return ElementMatchers.isSubTypeOf(HttpClient.class); } public static class SendFunctionAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file @Advice.OnMethodEnter(suppress = Throwable.class) public static void before( @Advice.Argument(0) HttpRequest httpRequest - ) throws Exception { + ) throws Throwable { if (httpRequest == null || httpRequest.uri() == null) { return; } - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = { new URL(jarFilePath) }; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) {} - if (classLoader == null) { - return; - } - - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.URLCollector"); - - // Run report with "argument" - for (Method method2: clazz.getMethods()) { - if(method2.getName().equals("report")) { - method2.invoke(null, httpRequest.uri().toURL()); - break; - } - } - classLoader.close(); // Close the class loader + URL url = httpRequest.uri().toURL(); + AikidoBootstrapClass.invoke(AikidoBootstrapClass.URL_COLLECTOR_REPORT, url); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientWrapper.java index dc93f3f8..d184b616 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/HttpClientWrapper.java @@ -1,18 +1,16 @@ package dev.aikido.agent.wrappers; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; -import java.lang.reflect.Method; import java.net.*; import java.net.http.HttpClient; import java.net.http.HttpRequest; -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; - public class HttpClientWrapper implements Wrapper { public String getName() { // Wrap getResponseCode function which executes HTTP requests @@ -28,35 +26,14 @@ public ElementMatcher getTypeMatcher() { return ElementMatchers.isSubTypeOf(HttpClient.class); } public static class HttpClientAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file, specified with the AIKIDO_DIRECTORY env variable @Advice.OnMethodEnter(suppress = Throwable.class) public static void before( - @Advice.This(typing = DYNAMIC, optional = true) Object target, @Advice.Argument(0) Object uriObject, @Advice.Argument(2) Object httpRequestObject - ) throws Exception { - URI uri = (URI) uriObject; - HttpRequest httpRequest = (HttpRequest) httpRequestObject; - - // Call to collector : - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URL[] urls = { new URL(jarFilePath) }; - URLClassLoader classLoader = new URLClassLoader(urls); - - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.RedirectCollector"); - - // Run report func with its arguments - for (Method method2: clazz.getMethods()) { - if(method2.getName().equals("report")) { - URL originUrl = httpRequest.uri().toURL(); - method2.invoke(null, originUrl, uri.toURL()); - break; - } - } - classLoader.close(); // Close the class loader + ) throws Throwable { + URL origin = ((HttpRequest) httpRequestObject).uri().toURL(); + URL dest = ((URI) uriObject).toURL(); + AikidoBootstrapClass.invoke(AikidoBootstrapClass.REDIRECT_COLLECTOR_REPORT, origin, dest); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/HttpConnectionRedirectWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/HttpConnectionRedirectWrapper.java index 89422651..dd52a6ec 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/HttpConnectionRedirectWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/HttpConnectionRedirectWrapper.java @@ -1,17 +1,13 @@ package dev.aikido.agent.wrappers; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.*; -import java.net.http.HttpClient; -import java.util.List; -import java.util.Map; import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; @@ -32,38 +28,16 @@ public ElementMatcher getTypeMatcher() { return ElementMatchers.isSubTypeOf(HttpURLConnection.class); } public static class FollowRedirect0Advice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file, specified with the AIKIDO_DIRECTORY env variable @Advice.OnMethodEnter(suppress = Throwable.class) public static void before( @Advice.This(typing = DYNAMIC, optional = true) HttpURLConnection target, @Advice.Argument(2) URL destUrl - ) throws Exception { + ) throws Throwable { URL origin = target.getURL(); if (origin == null || destUrl == null) { return; } - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = { new URL(jarFilePath) }; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) {} - if (classLoader == null) { - return; - } - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.RedirectCollector"); - - // Run report with "argument" - for (Method method2: clazz.getMethods()) { - if(method2.getName().equals("report")) { - method2.invoke(null, origin, destUrl); - break; - } - } - classLoader.close(); // Close the class loader + AikidoBootstrapClass.invoke(AikidoBootstrapClass.REDIRECT_COLLECTOR_REPORT, origin, destUrl); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/HttpURLConnectionWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/HttpURLConnectionWrapper.java index fde7530f..f66af3f5 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/HttpURLConnectionWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/HttpURLConnectionWrapper.java @@ -1,11 +1,11 @@ package dev.aikido.agent.wrappers; + +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -import net.bytebuddy.matcher.ElementMatchers; -import java.lang.reflect.Method; import java.net.*; import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; @@ -18,46 +18,20 @@ public String getName() { return ConstructorAdvice.class.getName(); } public ElementMatcher getMatcher() { - return isConstructor().and(isDeclaredBy(is(HttpURLConnection.class))); + return isDeclaredBy(URL.class).and(named("openConnection")); } @Override public ElementMatcher getTypeMatcher() { - return ElementMatchers.is(HttpURLConnection.class); + return is(URL.class); } public static class ConstructorAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file @Advice.OnMethodExit(suppress = Throwable.class) public static void before( - @Advice.This(typing = DYNAMIC) Object target - ) throws Exception { - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = { new URL(jarFilePath) }; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) {} - if (classLoader == null) { - return; - } - URL url = ((HttpURLConnection) target).getURL(); - if (target == null || url == null) { - return; - } - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.URLCollector"); - - // Run report with "argument" - for (Method method2: clazz.getMethods()) { - if(method2.getName().equals("report")) { - method2.invoke(null, url); - break; - } - } - classLoader.close(); // Close the class loader + @Advice.This(typing = DYNAMIC) URL url + ) throws Throwable { + AikidoBootstrapClass.invoke(AikidoBootstrapClass.URL_COLLECTOR_REPORT, url); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/InetAddressWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/InetAddressWrapper.java index e95caf19..0ea4fbf2 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/InetAddressWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/InetAddressWrapper.java @@ -1,19 +1,12 @@ package dev.aikido.agent.wrappers; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; -import java.lang.reflect.Executable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; - -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; public class InetAddressWrapper implements Wrapper { public String getName() { @@ -29,44 +22,12 @@ public ElementMatcher getTypeMatcher() { return ElementMatchers.isSubTypeOf(InetAddress.class); } public static class InetAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file @Advice.OnMethodExit public static void after( @Advice.Argument(0) String hostname, @Advice.Return InetAddress[] inetAddresses ) throws Throwable { - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = { new URL(jarFilePath) }; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) {} - if (classLoader == null) { - return; - } - - try { - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.DNSRecordCollector"); - - // Run report with "argument" - for (Method method2: clazz.getMethods()) { - if(method2.getName().equals("report")) { - method2.invoke(null, hostname, inetAddresses); - break; - } - } - classLoader.close(); // Close the class loader - } catch (InvocationTargetException invocationTargetException) { - if(invocationTargetException.getCause().toString().startsWith("dev.aikido.agent_api.vulnerabilities")) { - throw invocationTargetException.getCause(); - } - // Ignore non-aikido throwables. - } catch(Throwable e) { - System.out.println("AIKIDO: " + e.getMessage()); - } + AikidoBootstrapClass.invoke(AikidoBootstrapClass.DNS_RECORD_COLLECTOR_REPORT, hostname, inetAddresses); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/PathWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/PathWrapper.java index 8bb68fb5..4c8fb82d 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/PathWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/PathWrapper.java @@ -1,4 +1,5 @@ package dev.aikido.agent.wrappers; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -6,15 +7,8 @@ import net.bytebuddy.matcher.ElementMatchers; import java.lang.reflect.Executable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; import java.nio.file.Path; -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.of; import static net.bytebuddy.matcher.ElementMatchers.*; /** @@ -40,41 +34,13 @@ public ElementMatcher getTypeMatcher() { } public static class PathAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file @Advice.OnMethodEnter public static void before( @Advice.Origin Executable method, @Advice.Argument(value = 0, optional = true) Object argument ) throws Throwable { - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = { new URL(jarFilePath) }; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) {} - if (classLoader == null) { - return; - } - - try { - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.FileCollector"); - - // Run report with "argument" - Method reportMethod = clazz.getMethod("report", Object.class, String.class); - String op = "java.nio.file.Path." + method.getName(); - reportMethod.invoke(null, argument, op); - } catch (InvocationTargetException invocationTargetException) { - if(invocationTargetException.getCause().toString().startsWith("dev.aikido.agent_api.vulnerabilities")) { - throw invocationTargetException.getCause(); - } - // Ignore non-aikido throwables. - } catch(Throwable e) { - System.out.println("AIKIDO: " + e.getMessage()); - } - classLoader.close(); // Close the class loader + String op = "java.nio.file.Path." + method.getName(); + AikidoBootstrapClass.invoke(AikidoBootstrapClass.FILE_COLLECTOR_REPORT, argument, op); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/PathsWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/PathsWrapper.java index 634a1740..ca92dd0c 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/PathsWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/PathsWrapper.java @@ -1,17 +1,10 @@ package dev.aikido.agent.wrappers; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -import net.bytebuddy.matcher.ElementMatchers; -import java.lang.reflect.Executable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; import java.nio.file.Paths; import static net.bytebuddy.matcher.ElementMatchers.*; @@ -36,45 +29,18 @@ public ElementMatcher getTypeMatcher() { } public static class GetFunctionAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file @Advice.OnMethodEnter public static void before( @Advice.Argument(0) String argument1, @Advice.Argument(value = 1, optional = true) String[] argument2 ) throws Throwable { - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = { new URL(jarFilePath) }; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) {} - if (classLoader == null) { - return; + String op = "java.nio.file.Paths.get"; + if (argument1 != null) { + AikidoBootstrapClass.invoke(AikidoBootstrapClass.FILE_COLLECTOR_REPORT, argument1, op); } - - try { - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.FileCollector"); - - // Run report with "argument" - Method reportMethod = clazz.getMethod("report", Object.class, String.class); - if (argument1 != null) { - reportMethod.invoke(null, argument1, "java.nio.file.Paths.get"); - } - if (argument2 != null) { - reportMethod.invoke(null, argument2, "java.nio.file.Paths.get"); - } - } catch (InvocationTargetException invocationTargetException) { - if(invocationTargetException.getCause().toString().startsWith("dev.aikido.agent_api.vulnerabilities")) { - throw invocationTargetException.getCause(); - } - // Ignore non-aikido throwables. - } catch(Throwable e) { - System.out.println("AIKIDO: " + e.getMessage()); + if (argument2 != null) { + AikidoBootstrapClass.invoke(AikidoBootstrapClass.FILE_COLLECTOR_REPORT, argument2, op); } - classLoader.close(); // Close the class loader } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/RuntimeExecWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/RuntimeExecWrapper.java index 6032ae32..f7237c99 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/RuntimeExecWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/RuntimeExecWrapper.java @@ -1,21 +1,12 @@ package dev.aikido.agent.wrappers; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; -import java.io.IOException; -import java.lang.reflect.Executable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; - -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; import static net.bytebuddy.matcher.ElementMatchers.is; -import static net.bytebuddy.matcher.ElementMatchers.nameContains; public class RuntimeExecWrapper implements Wrapper { public String getName() { @@ -34,48 +25,11 @@ public ElementMatcher getTypeMatcher() { } public static class CommandExecAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file. @Advice.OnMethodEnter public static void before( - @Advice.This(typing = DYNAMIC, optional = true) Object target, - @Advice.Origin Executable method, @Advice.Argument(0) Object argument ) throws Throwable { - if (!(argument instanceof String)) { - return; - } - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = { new URL(jarFilePath) }; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) {} - if (classLoader == null) { - return; - } - - try { - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.CommandCollector"); - - // Run report with "argument" - for (Method method2: clazz.getMethods()) { - if(method2.getName().equals("report")) { - method2.invoke(null, argument); - break; - } - } - classLoader.close(); // Close the class loader - } catch (InvocationTargetException invocationTargetException) { - if(invocationTargetException.getCause().toString().startsWith("dev.aikido.agent_api.vulnerabilities")) { - throw invocationTargetException.getCause(); - } - // Ignore non-aikido throwables. - } catch(Throwable e) { - System.out.println("AIKIDO: " + e.getMessage()); - } + AikidoBootstrapClass.invoke(AikidoBootstrapClass.COMMAND_COLLECTOR_REPORT, argument); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorMultiArgumentWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorMultiArgumentWrapper.java index d6ebdeb8..ea1919ef 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorMultiArgumentWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorMultiArgumentWrapper.java @@ -1,5 +1,6 @@ package dev.aikido.agent.wrappers.file; import dev.aikido.agent.wrappers.Wrapper; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -7,11 +8,6 @@ import net.bytebuddy.matcher.ElementMatchers; import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; import static net.bytebuddy.matcher.ElementMatchers.*; @@ -36,9 +32,6 @@ public ElementMatcher getTypeMatcher() { } public static class FileConstructorMultiArgumentAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file @Advice.OnMethodEnter public static void before( @Advice.Argument(0) String parent, @@ -49,43 +42,13 @@ public static void before( if (prop != null && prop.equals("1")) { return; } - if (Thread.currentThread().getClass().toString() - .equals("class dev.aikido.agent_api.background.BackgroundProcess")) { - return; // Do not wrap File calls in background process. - } } catch (Throwable e) { return; } - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = {new URL(jarFilePath)}; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) { - } - if (classLoader == null) { - return; - } - try { - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.FileCollector"); - // Run report with "argument" - Method reportMethod = clazz.getMethod("report", Object.class, String.class); - - // Report both parent and child paths : - reportMethod.invoke(null, parent, "java.io.File(String, String)"); - reportMethod.invoke(null, child, "java.io.File(String, String)"); - - classLoader.close(); // Close the class loader - } catch (InvocationTargetException invocationTargetException) { - if (invocationTargetException.getCause().toString().startsWith("dev.aikido.agent_api.vulnerabilities")) { - throw invocationTargetException.getCause(); - } - // Ignore non-aikido throwables. - } catch (Throwable e) { - System.out.println("AIKIDO: " + e.getMessage()); - } + String operation = "java.io.File(String, String)"; + AikidoBootstrapClass.invoke(AikidoBootstrapClass.FILE_COLLECTOR_REPORT, parent, operation); + AikidoBootstrapClass.invoke(AikidoBootstrapClass.FILE_COLLECTOR_REPORT, child, operation); } } } diff --git a/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorSingleArgumentWrapper.java b/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorSingleArgumentWrapper.java index 25c9688d..39be6508 100644 --- a/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorSingleArgumentWrapper.java +++ b/agent/src/main/java/dev/aikido/agent/wrappers/file/FileConstructorSingleArgumentWrapper.java @@ -1,5 +1,6 @@ package dev.aikido.agent.wrappers.file; import dev.aikido.agent.wrappers.Wrapper; +import dev.aikido.agent_bootstrap.AikidoBootstrapClass; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -7,12 +8,7 @@ import net.bytebuddy.matcher.ElementMatchers; import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; import java.net.URI; -import java.net.URL; -import java.net.URLClassLoader; import static net.bytebuddy.matcher.ElementMatchers.*; @@ -38,9 +34,6 @@ public ElementMatcher getTypeMatcher() { } public static class FileConstructorSingleArgumentAdvice { - // Since we have to wrap a native Java Class stuff gets more complicated - // The classpath is not the same anymore, and we can't import our modules directly. - // To bypass this issue we load collectors from a .jar file @Advice.OnMethodEnter public static void before( @Advice.Argument(0) Object argument @@ -50,40 +43,12 @@ public static void before( if (prop != null && prop.equals("1")) { return; } - if (Thread.currentThread().getClass().toString() - .equals("class dev.aikido.agent_api.background.BackgroundProcess")) { - return; // Do not wrap File calls in background process. - } } catch (Throwable e) { return; } - String jarFilePath = System.getProperty("AIK_agent_api_jar"); - URLClassLoader classLoader = null; - try { - URL[] urls = {new URL(jarFilePath)}; - classLoader = new URLClassLoader(urls); - } catch (MalformedURLException ignored) { - } - if (classLoader == null) { - return; - } - try { - // Load the class from the JAR - Class clazz = classLoader.loadClass("dev.aikido.agent_api.collectors.FileCollector"); - - // Run report with "argument" - Method reportMethod = clazz.getMethod("report", Object.class, String.class); - reportMethod.invoke(null, argument, "java.io.File"); - classLoader.close(); // Close the class loader - } catch (InvocationTargetException invocationTargetException) { - if (invocationTargetException.getCause().toString().startsWith("dev.aikido.agent_api.vulnerabilities")) { - throw invocationTargetException.getCause(); - } - // Ignore non-aikido throwables. - } catch (Throwable e) { - System.out.println("AIKIDO: " + e.getMessage()); - } + String op = "java.io.File"; + AikidoBootstrapClass.invoke(AikidoBootstrapClass.FILE_COLLECTOR_REPORT, argument, op); } } } diff --git a/agent_api/src/test/java/wrappers/HttpURLConnectionTest.java b/agent_api/src/test/java/wrappers/HttpURLConnectionTest.java index ae85b395..8f474747 100644 --- a/agent_api/src/test/java/wrappers/HttpURLConnectionTest.java +++ b/agent_api/src/test/java/wrappers/HttpURLConnectionTest.java @@ -10,6 +10,7 @@ import org.springframework.web.client.ResourceAccessException; import utils.EmptySampleContextObject; +import javax.net.ssl.HttpsURLConnection; import java.io.IOException; import java.net.ConnectException; import java.net.HttpURLConnection; @@ -77,6 +78,15 @@ public void testNewUrlConnectionHttps() throws Exception { assertEquals(1, getHits("aikido.dev", 443)); } + @Test + public void testNewUrlConnectionHttps2() throws Exception { + setContextAndLifecycle("https://aikido.dev"); + assertEquals(0, getHits("aikido.dev", 443)); + + fetchResponseHttps("https://aikido.dev"); + assertEquals(1, getHits("aikido.dev", 443)); + } + @Test public void testNewUrlConnectionFaultyProtocol() throws Exception { setContextAndLifecycle("ftp://localhost:8080"); @@ -118,6 +128,7 @@ public void testSSRFLocalhostValid() throws Exception { assertEquals(3, getHits("localhost", 5000)); } + @Test public void testSSRFWithoutPort() throws Exception { setContextAndLifecycle("http://localhost:80"); @@ -148,6 +159,14 @@ private void fetchResponse(String urlString) throws IOException { connection.getResponseCode(); } + private void fetchResponseHttps(String urlString) throws IOException { + URL url = new URL(urlString); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setInstanceFollowRedirects(true); + connection.getResponseCode(); + } + private int getHits(String hostname, int port) { for (Hostnames.HostnameEntry entry : Objects.requireNonNull(HostnamesStore.getHostnamesAsList())) { if (entry.getHostname().equals(hostname) && entry.getPort() == port) { diff --git a/agent_bootstrap/build.gradle b/agent_bootstrap/build.gradle new file mode 100644 index 00000000..302024f5 --- /dev/null +++ b/agent_bootstrap/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '7.1.0' +} + +dependencies { +} + +shadowJar { + mergeServiceFiles() + manifest { + } +} diff --git a/agent_bootstrap/src/main/java/dev/aikido/agent_bootstrap/AikidoBootstrapClass.java b/agent_bootstrap/src/main/java/dev/aikido/agent_bootstrap/AikidoBootstrapClass.java new file mode 100644 index 00000000..8cdee8d0 --- /dev/null +++ b/agent_bootstrap/src/main/java/dev/aikido/agent_bootstrap/AikidoBootstrapClass.java @@ -0,0 +1,70 @@ +package dev.aikido.agent_bootstrap; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +public final class AikidoBootstrapClass { + // Define some constants for the collectors we use: + public final static Method URL_COLLECTOR_REPORT = + load("URLCollector", "report", URL.class); + + public final static Method FILE_COLLECTOR_REPORT = + load("FileCollector", "report", Object.class, String.class); + + public final static Method REDIRECT_COLLECTOR_REPORT = + load("RedirectCollector", "report", URL.class, URL.class); + + public final static Method DNS_RECORD_COLLECTOR_REPORT = + load("DNSRecordCollector", "report", String.class, InetAddress[].class); + + public final static Method COMMAND_COLLECTOR_REPORT = + load("CommandCollector", "report", Object.class); + + // Since we have to wrap a native Java Class stuff gets more complicated + // The classpath is not the same anymore, and we can't import our modules directly. + // To bypass this issue we load collectors from a .jar file + public static Method load(String className, String expectedMethodName, Class... parameterTypes) { + try { + Class requestedClass = loadClass("dev.aikido.agent_api.collectors." + className); + return requestedClass.getMethod(expectedMethodName, parameterTypes); + } catch (Throwable e) { + System.out.println("AIKIDO: " + e.getMessage()); + } + return null; + } + + public static Object invoke(Method method, Object... arguments) throws Throwable { + try { + // Do not wrap calls that happen in the background process. + if (Thread.currentThread().getClass().toString() + .equals("class dev.aikido.agent_api.background.BackgroundProcess")) { + return null; + } + + return method.invoke(null, arguments); + } catch (InvocationTargetException invocationTargetException) { + if (invocationTargetException.getCause().toString().startsWith("dev.aikido.agent_api.vulnerabilities")) { + throw invocationTargetException.getCause(); + } + // Ignore non-aikido exceptions + System.out.println("AIKIDO: " + invocationTargetException.getTargetException().getMessage()); + } catch (Throwable e) { + System.out.println("AIKIDO: " + e.getMessage()); + } + return null; + } + + /** + * Load class path from the aikido agent api jar file + */ + private static Class loadClass(String classPath) throws MalformedURLException, ClassNotFoundException { + String jarFilePath = System.getProperty("AIK_agent_api_jar"); + URL[] urls = {new URL(jarFilePath)}; + URLClassLoader classLoader = new URLClassLoader(urls); + return classLoader.loadClass(classPath); + } +} diff --git a/settings.gradle b/settings.gradle index b15843f6..021ece11 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ rootProject.name = 'firewall-java' -include 'agent', 'agent_api' \ No newline at end of file +include 'agent', 'agent_api', 'agent_bootstrap'