CVE-2024-23683: Insufficient Information (NVD-CWE-noinfo)
value: Artemis Java Test Sandbox versions less than 1.7.6 are vulnerable to a sandbox escape when an attacker crafts a special subclass of InvocationTargetException. An attacker can abuse this issue to execute arbitrary Java when a victim executes the supposedly sandboxed code.






CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 8.2
Published: 2024-01-19T21:15Z
Repository: https://github.com/ls1intum/Ares
Language: Java
Files changed: src/main/java/de/tum/in/test/api/security/ArtemisSecurityManager.java, src/main/java/de/tum/in/test/api/security/SecurityConstants.java, src/test/java/de/tum/in/test/api/SecurityTest.java, src/test/java/de/tum/in/testuser/SecurityUser.java, src/test/java/de/tum/in/testuser/subject/SecurityPenguin.java, src/test/java/org/apache/xyz/MaliciousInvocationTargetException.java
Commit date: 2022-01-17 13:40:35 +0100
Commit message: Merge pull request from GHSA-883x-6fch-6wjx

* Blacklist ReflectionUtils.getUnderlyingCause as it is in the invocation

This means that trusted classes are no longer have a fully whitelisted
stack when the cause is fetched and exception methods are invoked.

* Allow security manager to work with method names when checking the stack

* Add security test for a malicious InvocationTargetException

A variant of the exploit found by Daniel, related to GHSA-883x-6fch-6wjx

Co-authored-by: Daniel Kirschten <melodicahaspa@gmail.com>
Co-authored-by: Christian Femers <c.femers@tum.de>

Co-authored-by: Daniel Kirschten <melodicahaspa@gmail.com>
Diff: diff --git a/src/main/java/de/tum/in/test/api/security/ArtemisSecurityManager.java b/src/main/java/de/tum/in/test/api/security/ArtemisSecurityManager.java
index 152c574..6d0c055 100644
--- a/src/main/java/de/tum/in/test/api/security/ArtemisSecurityManager.java
+++ b/src/main/java/de/tum/in/test/api/security/ArtemisSecurityManager.java
@@ -509,23 +509,24 @@ public final class ArtemisSecurityManager extends SecurityManager {
 
 	private boolean isNotPrivileged(StackFrame stackFrame) {
 		return !AccessController.class.getName().equals(stackFrame.getClassName());
 	}
 
-	private boolean isCallNotWhitelisted(String call) {
+	private boolean isCallNotWhitelisted(String className, String methodName) {
+		String call = className + "." + methodName; //$NON-NLS-1$
 		return SecurityConstants.STACK_BLACKLIST.stream().anyMatch(call::startsWith)
 				|| (SecurityConstants.STACK_WHITELIST.stream().noneMatch(call::startsWith)
-						&& (configuration == null || !(configuration.whitelistedClassNames().contains(call)
-								|| configuration.trustedPackages().stream().anyMatch(pm -> pm.matches(call)))));
+						&& (configuration == null || !(configuration.whitelistedClassNames().contains(className)
+								|| configuration.trustedPackages().stream().anyMatch(pm -> pm.matches(className)))));
 	}
 
 	private boolean isStackFrameNotWhitelisted(StackFrame sf) {
-		return isCallNotWhitelisted(sf.getClassName());
+		return isCallNotWhitelisted(sf.getClassName(), sf.getMethodName());
 	}
 
 	private boolean isStackFrameNotWhitelisted(StackTraceElement ste) {
-		return isCallNotWhitelisted(ste.getClassName());
+		return isCallNotWhitelisted(ste.getClassName(), ste.getMethodName());
 	}
 
 	public static Optional<StackTraceElement> firstNonWhitelisted(StackTraceElement... elements) {
 		for (StackTraceElement ste : elements) {
 			if (INSTANCE.isStackFrameNotWhitelisted(ste))
diff --git a/src/main/java/de/tum/in/test/api/security/SecurityConstants.java b/src/main/java/de/tum/in/test/api/security/SecurityConstants.java
index 47c0c57..d1a53e4 100644
--- a/src/main/java/de/tum/in/test/api/security/SecurityConstants.java
+++ b/src/main/java/de/tum/in/test/api/security/SecurityConstants.java
@@ -13,11 +13,12 @@ public final class SecurityConstants {
 	static final String SECURITY_PACKAGE_NAME = SecurityConstants.class.getPackageName();
 
 	static final Set<String> STACK_WHITELIST = Set.of("java.", "org.junit.", "jdk.", "org.eclipse.", "com.intellij", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
 			"org.assertj", "org.opentest4j.", "com.sun.", "sun.", "org.apache.", "de.tum.in.test.", "net.jqwik", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
 			"ch.qos.logback", "org.jacoco", "javax.", "org.json", SECURITY_PACKAGE_NAME); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
-	static final Set<String> STACK_BLACKLIST = Set.of(BlacklistedInvoker.class.getName());
+	static final Set<String> STACK_BLACKLIST = Set.of(BlacklistedInvoker.class.getName(),
+			"org.junit.platform.commons.util.ReflectionUtils.getUnderlyingCause"); //$NON-NLS-1$
 
 	static final Set<String> PACKAGE_USE_BLACKLIST = Set.of(SECURITY_PACKAGE_NAME, "de.tum.in.test.api.internal", //$NON-NLS-1$
 			"jdk.internal", "sun."); //$NON-NLS-1$ //$NON-NLS-2$
 
 	private SecurityConstants() {
diff --git a/src/test/java/de/tum/in/test/api/SecurityTest.java b/src/test/java/de/tum/in/test/api/SecurityTest.java
index d561d6b..abc958d 100644
--- a/src/test/java/de/tum/in/test/api/SecurityTest.java
+++ b/src/test/java/de/tum/in/test/api/SecurityTest.java
@@ -24,10 +24,11 @@ class SecurityTest {
 	private final String testDefinePackage = "testDefinePackage";
 	private final String testEvilPermission = "testEvilPermission";
 	private final String testExecuteGit = "testExecuteGit";
 	private final String testMaliciousExceptionA = "testMaliciousExceptionA";
 	private final String testMaliciousExceptionB = "testMaliciousExceptionB";
+	private final String testMaliciousInvocationTargetException = "testMaliciousInvocationTargetException";
 	private final String testNewClassLoader = "testNewClassLoader";
 	private final String testNewSecurityManager = "testNewSecurityManager";
 	private final String tryManageProcess = "tryManageProcess";
 	private final String trySetSecurityManager = "trySetSecurityManager";
 	private final String trySetSystemOut = "trySetSystemOut";
@@ -78,10 +79,16 @@ class SecurityTest {
 	@TestTest
 	void test_testMaliciousExceptionB() {
 		tests.assertThatEvents().haveExactly(1, testFailedWith(testMaliciousExceptionB, SecurityException.class));
 	}
 
+	@TestTest
+	void test_testMaliciousInvocationTargetException() {
+		tests.assertThatEvents().haveExactly(1,
+				testFailedWith(testMaliciousInvocationTargetException, SecurityException.class));
+	}
+
 	@TestTest
 	void test_testNewClassLoader() {
 		tests.assertThatEvents().haveExactly(1, testFailedWith(testNewClassLoader, SecurityException.class));
 	}
 
diff --git a/src/test/java/de/tum/in/testuser/SecurityUser.java b/src/test/java/de/tum/in/testuser/SecurityUser.java
index e2ac0f5..8aacfa0 100644
--- a/src/test/java/de/tum/in/testuser/SecurityUser.java
+++ b/src/test/java/de/tum/in/testuser/SecurityUser.java
@@ -75,10 +75,15 @@ public class SecurityUser {
 	@Test
 	void testMaliciousExceptionB() {
 		assertFalse(SecurityPenguin.maliciousExceptionB());
 	}
 
+	@Test
+	void testMaliciousInvocationTargetException() throws Exception {
+		SecurityPenguin.maliciousInvocationTargetException();
+	}
+
 	@Test
 	void testNewClassLoader() throws IOException {
 		SecurityPenguin.newClassLoader();
 	}
 
diff --git a/src/test/java/de/tum/in/testuser/subject/SecurityPenguin.java b/src/test/java/de/tum/in/testuser/subject/SecurityPenguin.java
index cbe5dce..8f178cf 100644
--- a/src/test/java/de/tum/in/testuser/subject/SecurityPenguin.java
+++ b/src/test/java/de/tum/in/testuser/subject/SecurityPenguin.java
@@ -17,10 +17,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.xyz.Circumvention;
 import org.apache.xyz.FakeTrustedClass;
 import org.apache.xyz.MaliciousExceptionB;
+import org.apache.xyz.MaliciousInvocationTargetException;
 
 import de.tum.in.test.api.io.IOTester;
 
 public final class SecurityPenguin {
 
@@ -47,10 +48,14 @@ public final class SecurityPenguin {
 			// nothing
 		}
 		return ab.get();
 	}
 
+	public static void maliciousInvocationTargetException() throws Exception {
+		throw new MaliciousInvocationTargetException();
+	}
+
 	@SuppressWarnings("resource")
 	public static void newClassLoader() throws IOException {
 		new URLClassLoader(new URL[0]).close();
 	}
 
diff --git a/src/test/java/org/apache/xyz/MaliciousInvocationTargetException.java b/src/test/java/org/apache/xyz/MaliciousInvocationTargetException.java
new file mode 100644
index 0000000..75c1bb6
--- /dev/null
+++ b/src/test/java/org/apache/xyz/MaliciousInvocationTargetException.java
@@ -0,0 +1,22 @@
+package org.apache.xyz;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class MaliciousInvocationTargetException extends InvocationTargetException {
+
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	public Throwable getTargetException() {
+		try {
+			Files.readString(Path.of("pom.xml"));
+		} catch (IOException e) {
+			throw new UncheckedIOException(e);
+		}
+		return new Error("succeeded");
+	}
+}

Vulnerable code: 	private boolean isCallNotWhitelisted(String call) {
						&& (configuration == null || !(configuration.whitelistedClassNames().contains(call)
								|| configuration.trustedPackages().stream().anyMatch(pm -> pm.matches(call)))));
		return isCallNotWhitelisted(sf.getClassName());
		return isCallNotWhitelisted(ste.getClassName());
	static final Set<String> STACK_BLACKLIST = Set.of(BlacklistedInvoker.class.getName());
Fixed code: 	private boolean isCallNotWhitelisted(String className, String methodName) {
		String call = className + "." + methodName; //$NON-NLS-1$
						&& (configuration == null || !(configuration.whitelistedClassNames().contains(className)
								|| configuration.trustedPackages().stream().anyMatch(pm -> pm.matches(className)))));
		return isCallNotWhitelisted(sf.getClassName(), sf.getMethodName());
		return isCallNotWhitelisted(ste.getClassName(), ste.getMethodName());
	static final Set<String> STACK_BLACKLIST = Set.of(BlacklistedInvoker.class.getName(),
			"org.junit.platform.commons.util.ReflectionUtils.getUnderlyingCause"); //$NON-NLS-1$
	private final String testMaliciousInvocationTargetException = "testMaliciousInvocationTargetException";
	@TestTest
	void test_testMaliciousInvocationTargetException() {
		tests.assertThatEvents().haveExactly(1,
				testFailedWith(testMaliciousInvocationTargetException, SecurityException.class));
	}

	@Test
	void testMaliciousInvocationTargetException() throws Exception {
		SecurityPenguin.maliciousInvocationTargetException();
	}

import org.apache.xyz.MaliciousInvocationTargetException;
	public static void maliciousInvocationTargetException() throws Exception {
		throw new MaliciousInvocationTargetException();
	}

package org.apache.xyz;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;

public class MaliciousInvocationTargetException extends InvocationTargetException {

	private static final long serialVersionUID = 1L;

	@Override
	public Throwable getTargetException() {
		try {
			Files.readString(Path.of("pom.xml"));
		} catch (IOException e) {
			throw new UncheckedIOException(e);
		}
		return new Error("succeeded");
	}
}