Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/badges/jacoco.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ In Maven:
<dependency>
<groupId>io.github.pixee</groupId>
<artifactId>java-security-toolkit</artifactId>
<version>1.0.6</version>
<version>1.0.7</version>
</dependency>
```
In Gradle:
```kotlin
implementation("io.github.pixee:java-security-toolkit:1.0.6")
implementation("io.github.pixee:java-security-toolkit:1.0.7")
```

## Contributing
## Contributing
We'd love to get contributions! See [CONTRIBUTING.md](CONTRIBUTING.md).

### Building
Expand Down
33 changes: 32 additions & 1 deletion src/main/java/io/github/pixee/security/Reflection.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ public static Class<?> loadAndVerify(final String name) throws ClassNotFoundExce
return loadAndVerify(name, defaultRestrictions());
}

/**
* This method sandboxes the classloading to prevent possibly dangerous types from being loaded,
* using the default restrictions.
*
* @param name the name of the type to load
* @param initialize whether to initialize the class, passed to {@link Class#forName(String,
* boolean, ClassLoader)}
* @param loader the ClassLoader to use, passed to {@link Class#forName(String, boolean,
* ClassLoader)}
* @throws ClassNotFoundException if the class is not found
* @return the result of {@link Class#forName(String)}, if it passes the default restrictions
*/
public static Class<?> loadAndVerify(
final String name, final boolean initialize, final ClassLoader loader)
throws ClassNotFoundException {
return loadAndVerify(
name, defaultRestrictions(), () -> Class.forName(name, initialize, loader));
}

/**
* This method sandboxes the classloading to prevent possibly dangerous types from being loaded.
*
Expand All @@ -72,6 +91,14 @@ public static Class<?> loadAndVerify(final String name) throws ClassNotFoundExce
public static Class<?> loadAndVerify(
final String name, final Set<ReflectionRestrictions> restrictions)
throws ClassNotFoundException {
return loadAndVerify(name, restrictions, () -> Class.forName(name));
}

private static Class<?> loadAndVerify(
final String name,
final Set<ReflectionRestrictions> restrictions,
final ClassSupplier classSupplier)
throws ClassNotFoundException {

// we can do this check up front before we even load the type
if (restrictions.contains(ReflectionRestrictions.MUST_NOT_INVOLVE_CODE_EXECUTION)) {
Expand All @@ -83,7 +110,7 @@ public static Class<?> loadAndVerify(
}

// load the type so we can do the other checks
final Class<?> type = Class.forName(name);
final Class<?> type = classSupplier.get();

if (restrictions.contains(ReflectionRestrictions.MUST_BE_PUBLIC)) {
final int modifiers = type.getModifiers();
Expand Down Expand Up @@ -116,5 +143,9 @@ public static Class<?> loadAndVerify(
"groovy.",
"org.python.");

private interface ClassSupplier {
Class<?> get() throws ClassNotFoundException;
}

private static final String typeNotAllowedMessage = "type not allowed";
}
13 changes: 12 additions & 1 deletion src/test/java/io/github/pixee/security/ReflectionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

/** Test the protections of {@link Reflection}. */
final class ReflectionTest {

@ParameterizedTest
Expand All @@ -26,6 +27,11 @@ void it_protects_dangerous_reflection(final String type) {

// run the same test and confirm that the defaultRestrictions() returns this
assertThrows(SecurityException.class, () -> Reflection.loadAndVerify(type));

// run the same test again on the other signature
assertThrows(
SecurityException.class,
() -> Reflection.loadAndVerify(type, true, getClass().getClassLoader()));
}

@Test
Expand All @@ -38,7 +44,10 @@ void it_enforces_public_restriction() throws ClassNotFoundException {
Reflection.loadAndVerify(
ReflectionTest.class.getName(), setOf(ReflectionRestrictions.MUST_BE_PUBLIC)));

// the type we're testing is public and so we should be able to load it
// the type we're testing is public, and so we should be able to load it
Reflection.loadAndVerify(
Reflection.class.getName(), setOf(ReflectionRestrictions.MUST_BE_PUBLIC));

Reflection.loadAndVerify(
Reflection.class.getName(), setOf(ReflectionRestrictions.MUST_BE_PUBLIC));
}
Expand All @@ -53,6 +62,8 @@ void it_enforces_public_restriction() throws ClassNotFoundException {
void it_loads_normal_classes(final String typeName) throws ClassNotFoundException {
Class<?> type = Reflection.loadAndVerify(typeName);
assertThat(type, is(not(nullValue())));
type = Reflection.loadAndVerify(typeName, true, getClass().getClassLoader());
assertThat(type, is(not(nullValue())));
}

@Test
Expand Down