Skip to content

Instantly share code, notes, and snippets.

@beargiles
Last active October 11, 2023 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beargiles/69f34af3bec7cde3a0c2bddb1409f3b5 to your computer and use it in GitHub Desktop.
Save beargiles/69f34af3bec7cde3a0c2bddb1409f3b5 to your computer and use it in GitHub Desktop.
Example of using AccessControlContext to restrict access to sensitive resources
/**
* Permissive SecurityManager
*
* This SecurityManager can still be highly restricive but it allows
* access to a sensitive service.
*/
public class PermissiveSecurityManager extends SecurityManager {
private final List<String> hosts = new ArrayList<>();
public PermissiveSecurityManager(String... hosts) {
hosts.addAll(Arrays.asList(hosts));
}
/**
* Simple check for authorized host + port. In practice we would
* want to check both host and port, e.g., using a reference set
* of NetPermissions.
*/
public void checkConnect(String host, int port) {
if (hosts.contains(host)) {
throw new SecurityException("unauthorized host: " + host);
}
if (port != 443) {
throw bew SecurityException("unauthorized port: " + port);
}
}
public void checkPermission(java.security.Permission permission) {
System.out.println("permissive check!");
// allow
}
/**
* Defer to AccessControlContext
*/
public void checkPermission(java.security.Permission permission, Object ctx) {
System.out.println("permissive check with ctx!");
((java.security.AccessControlContext) ctx).checkPermission(permission);
}
}
/**
* Example of using AccessControlContexts to restrcit access
* to sensitive resources.
*
* This is desirable since it allows us to ensure all access
* to the sensitive resource is identified and can be carefully
* audited. Without the SM/ACC it would be possible for a naive
* developer (or rogue actor) to add code that introduces a
* security vulnerability.
*
* We should still use static and dynamic vulnerability analysis,
* of course, but by explicitly limiting the access to PrivilegedActions
* it is easier to consider additional steps such as signed jars or
* sealed classes for the sensive code without adding that burden
* to the rest of the code base.
*
* IMPORTANT - THIS DOESN"T (CURRENTLY) WORK DUE TO FAILURE OF
* ACC.checkPermission(). I don't know if that's because of my
* current JVM or if I've overlooked a step in creating it. However
* the general idea still applies.
*/
public class RestrictedAccessWithACC {
private final String host = "cnn.com";
public static void main(String[] args) throws Exception {
final URL url = new URL("https://" + host + "/");
// create and use permissive security manager
final SecurityManager permissive = new PermissiveSecurityManager(host);
System.setSecurityManager(permissive);
// this shows unexpected bahavior
sanityCheck(permissive);
// Cache this SM's ACC.
final AccessControlContext ctx = (java.security.AccessControlContext) permissive.getSecurityContext();
// create and use restrictive security manager
final SecurityManager restrictive = new RestrictiveSecurityManager();
System.setSecurityManager(restrictive);
// this should fail since it uses the restrictive ACC.
final SensitiveNetworkAccess access = new SensitiveNetworkAccess(url);
try {
java.security.AccessController.doPrivileged(access);
} catch (SecurityException e) {
System.err.printf("security exception! %s\n", e.getMessage());
}
// this should successed since it uses the permissive ACC. However it
// fails since ctx.checkPermission() is failing.
try {
java.security.AccessController.doPrivileged(access, ctx);
} catch (SecurityException e) {
System.err.printf("security exception! %s\n", e.getMessage());
}
}
/**
* Sanity check - it shows AccessControlContext.checkPermission() is
* not acting as expected.
*/
static void sanityCheck(SecurityManager permissive) {
final Permission getCookieHandlerPermission = new NetPermission("getCookieHandler");
// this should work since the current SecurityManager is 'permissive'
final AccessControlContext ctx = AccessController.getContext();
// this explicitly creates the ACC from the security manager
final AccessControlContext obj = (AccessControlContext) permissive.getSecurityContext();
// these work, as expected
permissive.checkPermission(getCookieHandlerPermission);
permissive.checkPermission(getCookieHandlerPermission, obj);
permissive.checkPermission(getCookieHandlerPermission, ctx);
// these don't't work even though though permission.checkPermission() succeeeded.
ctx.checkPermission(getCookieHandlerPermission);
obj.checkPermission(getCookieHandlerPermission);
}
}
/**
* Restrictive SecurityManager
*
* A restrictive SecurityManager, e.g., it might block ALL access to the
* network in order to prevent data exfiltration.
*/
public class RestrictiveSecurityManager extends SecurityManager {
/**
* Explicitly whitelist.
*/
public void checkPropertiesAccess() {
// allow
}
/**
* Explicitly whitelist
*/
public void checkPropertyAccess(String key) {
// allow
}
/**
* Allow a few permissions but defer to parent SecurityManager
* for most things.
*/
public void checkPermission(Permission perm) {
if (!(perm instanceof ReflectPermission)
&& !(perm instanceof SecurityPermission)
&& !(perm instanceof RuntimePermission)
&& !(perm instanceof FilePermission)) {
super.checkPermission(perm);
}
}
/**
* Defer to AccessControlContext
*/
public void checkPermission(Permission perm, Object ctx) {
if (!(perm instanceof ReflectPermission)
&& !(perm instanceof SecurityPermission)
&& !(perm instanceof RuntimePermission)
&& !(perm instanceof FilePermission)) {
super.checkPermission(perm, ctx);
}
((java.security.AccessControlContext) ctx).checkPermission(perm);
}
}
/**
* Simple net client
*
* Simple net client - we just need something that requires the
* `getConnect()` permission.
*
* In practice we'll often use lambda functions instead of
* separate classes.
*/
public class SensitiveNetworkAccess implements PrivilegedAction<byte[]> {
private final URL url;
public NetworkAccess(URL url) {
this.url = url;
}
public byte[] run() {
try (InputStream is = url.openStream()) {
return is.readAllBytes();
} catch (IOException e) {
System.err.println("IOException!: " + e.getMessage();
}
return new byte[0];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment