diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index c1c4e2f4c9..dcc488d403 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -9,6 +9,7 @@ apply plugin: 'com.jfrog.artifactory' description 'Corda standalone node' evaluationDependsOn(':node') +evaluationDependsOn(':tools:aegis4j') configurations { runtimeArtifacts.extendsFrom runtimeClasspath @@ -37,23 +38,27 @@ capsule { } def nodeProject = project(':node') +def aegisProject = project(':tools:aegis4j') task buildCordaJAR(type: FatCapsule, dependsOn: [ nodeProject.tasks.named('jar'), + aegisProject.tasks.named('shadowJar'), project(':core-deterministic').tasks.named('assemble'), project(':serialization-deterministic').tasks.named('assemble') - ]) { +]) { applicationClass 'net.corda.node.Corda' archiveBaseName = 'corda' archiveVersion = corda_release_version archiveClassifier = jdkClassifier archiveName = archiveFileName.get() applicationSource = files( - nodeProject.configurations.runtimeClasspath, - nodeProject.tasks.jar, - nodeProject.buildDir.toString() + '/resources/main/corda-reference.conf', - "$rootDir/config/dev/log4j2.xml", - 'NOTICE' // Copy CDDL notice + nodeProject.configurations.runtimeClasspath, + nodeProject.tasks.jar, + aegisProject.tasks.named('shadowJar'), + nodeProject.buildDir.toString() + '/resources/main/corda-reference.conf', + nodeProject.buildDir.toString() + '/resources/main/mods.properties', + "$rootDir/config/dev/log4j2.xml", + 'NOTICE' // Copy CDDL notice ) from configurations.capsuleRuntime.files.collect { zipTree(it) } with jar @@ -99,6 +104,7 @@ task buildCordaJAR(type: FatCapsule, dependsOn: [ def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.corda.djvm**;djvm**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)" def quasarClassLoaderExclusion = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)" javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] + javaAgents += "aegis4j-1.2.jar=path=mods.properties" systemProperties['visualvm.display.name'] = 'Corda' if (JavaVersion.current() == JavaVersion.VERSION_1_8) { minJavaVersion = '1.8.0' diff --git a/node/src/main/resources/mods.properties b/node/src/main/resources/mods.properties new file mode 100644 index 0000000000..7c9d6bd00b --- /dev/null +++ b/node/src/main/resources/mods.properties @@ -0,0 +1,56 @@ +# format: ..= +# JNDI +# ---- +# Patches the 3 protected methods which InitialContext uses internally to get all Context instances, +# so that these methods always throw a NoInitialContextException (NICE). As a result, no JNDI lookups +# are possible. +JNDI.javax.naming.InitialContext.getURLOrDefaultInitCtx=throw new javax.naming.NoInitialContextException("JNDI context creation blocked by aegis4j"); +JNDI.javax.naming.InitialContext.getDefaultInitCtx=throw new javax.naming.NoInitialContextException("JNDI context creation blocked by aegis4j"); +# RMI +# --- +# Outside of traditional JEE application servers, few people use the RMI libraries anymore. +RMI.java.rmi.registry.LocateRegistry.getRegistry=throw new java.rmi.StubNotFoundException("RMI registry creation blocked by aegis4j"); +RMI.java.rmi.registry.LocateRegistry.createRegistry=throw new java.rmi.StubNotFoundException("RMI registry creation blocked by aegis4j"); +RMI.java.rmi.server.RemoteObject.RemoteObject=throw new java.lang.RuntimeException("RMI remote object creation blocked by aegis4j"); +# Process Execution +# ----------------- +# Many remote code execution exploits culminate in local process execution, but it's relatively rare to +# need to use these methods for legitimate purposes. +#PROCESS.java.lang.Runtime.exec=throw new java.io.IOException("Process execution blocked by aegis4j"); +#PROCESS.java.lang.ProcessBuilder.start=throw new java.io.IOException("Process execution blocked by aegis4j"); +#PROCESS.java.lang.ProcessBuilder.startPipeline=throw new java.io.IOException("Process execution blocked by aegis4j"); +# JDK HTTP Server +# --------------- +# The JDK HTTP server is intended for quick testing, especially for platform beginners. It is rarely (if +# ever) used in production, so we can eliminate this little bit of attack surface. +HTTPSERVER.com.sun.net.httpserver.HttpServer.HttpServer=throw new java.lang.RuntimeException("HTTP server creation blocked by aegis4j"); +HTTPSERVER.com.sun.net.httpserver.HttpsServer.HttpsServer=throw new java.lang.RuntimeException("HTTPS server creation blocked by aegis4j"); +HTTPSERVER.com.sun.net.httpserver.spi.HttpServerProvider.HttpServerProvider=throw new java.lang.RuntimeException("HTTP server provider creation blocked by aegis4j"); +HTTPSERVER.com.sun.net.httpserver.spi.HttpServerProvider.provider=throw new java.lang.RuntimeException("HTTP server provider lookup blocked by aegis4j"); +# Java Serialization +# ------------------ +# Probably a bit more commonly used than most of the other features on this list, but a huge security +# liability for applications that don't use it. Best to live without it, if at all possible. +#SERIALIZATION.java.io.ObjectInputStream.ObjectInputStream=throw new java.lang.RuntimeException("Java deserialization blocked by aegis4j"); +#SERIALIZATION.java.io.ObjectOutputStream.ObjectOutputStream=throw new java.lang.RuntimeException("Java serialization blocked by aegis4j"); +# Unsafe +# ------ +# Quite commonly used in the olden days, but many applications should be able to run without it these days. +#UNSAFE.sun.misc.Unsafe.*=throw new java.lang.RuntimeException("Unsafe blocked by aegis4j"); +# Scripting +# --------- +# Nashorn was removed from the platform in JDK 15. There are other JSR 223 script engines out there, +# but they're probably not common enough to leave this capability enabled by default. +#SCRIPTING.javax.script.ScriptEngineManager.ScriptEngineManager=throw new java.lang.RuntimeException("Scripting blocked by aegis4j"); +#SCRIPTING.javax.script.AbstractScriptEngine.AbstractScriptEngine=throw new java.lang.RuntimeException("Scripting blocked by aegis4j"); +#SCRIPTING.javax.script.SimpleScriptContext.SimpleScriptContext=throw new java.lang.RuntimeException("Scripting blocked by aegis4j"); +#SCRIPTING.javax.script.CompiledScript.CompiledScript=throw new java.lang.RuntimeException("Scripting blocked by aegis4j"); +# JShell +# ------ +# Introduced in JDK 9, the Java Shell is intended for rapid prototyping and testing. It is not usually used in production. +#JSHELL.jdk.jshell.JShell.JShell=throw new java.lang.RuntimeException("JShell blocked by aegis4j"); +#JSHELL.jdk.jshell.JShell.create=throw new java.lang.RuntimeException("JShell blocked by aegis4j"); +#JSHELL.jdk.jshell.JShell.builder=throw new java.lang.RuntimeException("JShell blocked by aegis4j"); +#JSHELL.jdk.jshell.tool.JavaShellToolBuilder.builder=throw new java.lang.RuntimeException("JShell blocked by aegis4j"); +#JSHELL.jdk.jshell.Snippet.Snippet=throw new java.lang.RuntimeException("JShell blocked by aegis4j"); +#JSHELL.jdk.jshell.TaskFactory.parse=throw new java.lang.RuntimeException("JShell blocked by aegis4j"); diff --git a/tools/aegis4j/build.gradle b/tools/aegis4j/build.gradle index cc84995ae7..464c8bd176 100644 --- a/tools/aegis4j/build.gradle +++ b/tools/aegis4j/build.gradle @@ -9,7 +9,7 @@ plugins { group = 'net.gredler' archivesBaseName = 'aegis4j' -version = '1.1' +version = '1.2' repositories { mavenCentral() diff --git a/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java b/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java index 13be5e35f0..eb38975368 100644 --- a/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java +++ b/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java @@ -2,6 +2,7 @@ package net.gredler.aegis4j; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; @@ -35,24 +36,33 @@ public final class AegisAgent { */ public static void premain(String args, Instrumentation instr) { instrumentation = instr; - if(args.trim().equalsIgnoreCase("dynamic")) return; Path path = null; boolean started = false; - for(String arg: args.split(";")) { - if (started) throw new IllegalArgumentException("Aegis4j ERROR: parameter ordering means patching already started"); - String normalisedaArg = arg.trim().toLowerCase(); - if(normalisedaArg.isEmpty() || normalisedaArg.startsWith("block=") || normalisedaArg.startsWith("unblock=")) { - try { - Patcher.start(instr, toBlockList(normalisedaArg, path), getModificationsInputStream(path)); - started = true; - } catch (IOException e) { - throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e); + + if (args != null) { + if (args.trim().equalsIgnoreCase("dynamic")) return; + for (String arg : args.split(";")) { + if (started) throw new IllegalArgumentException("Aegis4j ERROR: parameter ordering means patching already started"); + String normalisedaArg = arg.trim().toLowerCase(); + if (normalisedaArg.isEmpty() || normalisedaArg.startsWith("block=") || normalisedaArg.startsWith("unblock=")) { + try { + Patcher.start(instr, toBlockList(normalisedaArg, path), getModificationsInputStream(path)); + started = true; + } catch (IOException e) { + throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e); + } + } else if (normalisedaArg.startsWith("path=")) { + String pathString = arg.trim().substring(5); + if (pathString.startsWith(File.pathSeparator)) { + path = Paths.get(pathString); + } else { + Path agentJar = Paths.get(AegisAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath()); + path = agentJar.resolveSibling(pathString); + } + System.out.println("Aegis4j patching from " + path + " mods file"); + } else { + throw new IllegalArgumentException("Aegis4j ERROR: unrecognised parameters " + arg); } - } else if (normalisedaArg.startsWith("path=")) { - path = Paths.get(arg.trim().substring(5)); - System.out.println("Aegis4j patching from " + path + " mods file"); - } else { - throw new IllegalArgumentException("Aegis4j ERROR: unrecognised parameters " + arg); } } if (!started) {