说明
上一节对使用策略文件来定义危险函数的用法进行了说明,本文主要是通过代码的方式对权限进行控制。关于SecurityManager
的机制可以具体看看java沙箱绕过中的Java Security Manager介绍章节。
具体来说,SecurityManager
可以对JAVA中的诸如文件访问、命令执行、反射的方法的精准控制。
SecurityManager控制
文件读取的控制
以FileInputStream()
为例进行说明:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
其中Java代码,1
2
3
4SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
就是利用SecurityManager
对权限进行校验。
命令执行的控制
1 | public Process start() throws IOException { |
可以看到同样利用SecurityManager
对权限进行校验1
2
3SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkExec(prog);
反射的控制
java.lang.Class:getDeclaredMethods()
1
2
3
4public Method[] getDeclaredMethods() throws SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
return copyMethods(privateGetDeclaredMethods(false));
}
跟踪进入到java.lang.Class:checkMemberAccess()
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18private void checkMemberAccess(int which, Class<?> caller, boolean checkProxyInterfaces) {
final SecurityManager s = System.getSecurityManager();
if (s != null) {
/* Default policy allows access to all {@link Member#PUBLIC} members,
* as well as access to classes that have the same class loader as the caller.
* In all other cases, it requires RuntimePermission("accessDeclaredMembers")
* permission.
*/
final ClassLoader ccl = ClassLoader.getClassLoader(caller);
final ClassLoader cl = getClassLoader0();
if (which != Member.PUBLIC) {
if (ccl != cl) {
s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
}
}
this.checkPackageAccess(ccl, checkProxyInterfaces);
}
}
同样存在SecurityManager
利用checkPermission()
对操作的校验。
自定义SecurityManager
通过上述的示例演示,可以知道SecurityManager
在很多关键的位置都进行了动作的校验。在上一节中,我们通过策略文件同样就是对一些关键操作进行了权限定义。当JAVA程序运行至该操作时就会检查此权限。当然我们也可以通过自定义SecurityManager
来实现对某些文件的访问控制、某些操作的访问控制。
如果我们需要实现自定义的访问控制,我们需要继承SecurityManager
类,然后在其中实现自己的权限控制的方法。在java.lang.SecurityManager
中定义了很多的权限检测的方法,包括checkConnect()
、checkDelete()
、checkExec()
、checkListen()
、checkRead()
、checkPropertiesAccess()
等等。所有的这些方法都会最终调用checkPermission()
。所以如果我们要实现自定义的访问控制,那么我们就可以尝试重载checkPermission()
方法。
对文件的访问控制
以文件访问控制为例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27import java.io.FileInputStream;
import java.io.IOException;
public class TestSecurityManager {
public static void main(String args[]) throws IOException {
System.setSecurityManager(new MySecurityManager());
FileInputStream fis = new FileInputStream("./test.txt");
byte[] bs = new byte[1024];
fis.read(bs);
fis.close();
}
}
class MySecurityManager extends SecurityManager {
public void checkPermission(Permission perm) {
if(perm instanceof FilePermission) {
String action = perm.getActions();
if (action.equals("read")) {
String filename = perm.getName();
if (filename.contains(".txt")) {
throw new SecurityException("No Access" + filename);
}
}
}
}
}
这样写的比较的复杂。因为在SecurityManager
中直接存在checkRead()
方法用于对访问文件的控制,所以我们也可以选择直接重载checkRead()
方法。如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14class MySecurityManager extends SecurityManager {
public void checkRead(String file) {
if (file.contains(".txt")) {
throw new SecurityException("No Access " + file);
}
}
public void checkRead(String file, Object context) {
checkRead(file);
}
}
运行程序之后就会抛出SecurityException
的错误。
限制命令执行
1 | import java.io.File; |
运行上述的程序就会抛出SecurityException
错误。
以上就是一个简单的Demo。这个仅仅只是实现了对calc.exe
的禁止,如果要实现对其他方法的限制,上述Demo的方式是明显不行的。下面是相对来说一个禁止命令执行的通用版本。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class TestSecurityManager {
public static void main(String args[]) throws IOException {
System.setSecurityManager(new MySecurityManager());
Runtime.getRuntime().exec("calc.exe");
}
}
class MySecurityManager extends SecurityManager {
public void checkPermission(Permission perm) {
if (perm instanceof FilePermission) {
String action = perm.getActions();
if (action != null && action.contains("execute")) {
throw new SecurityException("forbidden execute");
}
}
}
}
注意需要通过FilePermission
来对权限进行控制。因为最终的命令执行其实最终都会调用本地文件来执行代码,所以通过对FilePermission
的检测,判断是否存在execute
的动作,从而禁止命令执行。
其他
通过这种防护是不是就一定万无一失了呢?如果setSecurityManager
被攻击者设置为null,这样就导致了我们所有的安全检查全部失效了,所以我们也需要保护我们自定义的SecurityManager
。如下:1
2
3
4
5
6
7
8
9
10
11
12class MySecurityManager extends SecurityManager {
public void checkPermission(Permission perm) {
// 禁止设置新的SecurityManager,保护自己
if (perm instanceof java.lang.RuntimePermission) {
String name = perm.getName();
if (name != null && name.contains("setSecurityManager")) {
throw new SecurityException("System.setSecurityManager denied!");
}
}
}
}
当我们设置了之后,我们通过检查setSecurityManager
方法禁止其他人对SecurityManager
进行设置。
总结
总的来说,当需要执行第三方的未知代码时,使用SecurityManager
来设置一些白名单、黑名单也是一个非常好的方法。至于如何到底是选择策略文件还是通过代码的方式来实现,主要是看自己项目的需求。