上一节对使用策略文件来定义危险函数的用法进行了说明,本文主要是通过代码的方式对权限进行控制。关于 SecurityManager
的机制可以具体看看 java沙箱绕过
中的Java Security Manager介绍章节。
具体来说, SecurityManager
可以对JAVA中的诸如文件访问、命令执行、反射的方法的精准控制。
以 FileInputStream()
为例进行说明:
public 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代码,
SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(name); }
就是利用 SecurityManager
对权限进行校验。
public Process start() throws IOException { // Must convert to array first -- a malicious user-supplied // list might try to circumvent the security check. String[] cmdarray = command.toArray(new String[command.size()]); cmdarray = cmdarray.clone(); for (String arg : cmdarray) if (arg == null) throw new NullPointerException(); // Throws IndexOutOfBoundsException if command is empty String prog = cmdarray[0]; SecurityManager security = System.getSecurityManager(); if (security != null) security.checkExec(prog); // ... // other code }
可以看到同样利用 SecurityManager
对权限进行校验
SecurityManager security = System.getSecurityManager(); if (security != null) security.checkExec(prog);
java.lang.Class:getDeclaredMethods()
public Method[] getDeclaredMethods() throws SecurityException { checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true); return copyMethods(privateGetDeclaredMethods(false)); }
跟踪进入到 java.lang.Class:checkMemberAccess()
:
private 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()
方法。
以文件访问控制为例:
import 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 { @Override 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()
方法。如下:
class MySecurityManager extends SecurityManager { @Override public void checkRead(String file) { if (file.contains(".txt")) { throw new SecurityException("No Access " + file); } } @Override public void checkRead(String file, Object context) { checkRead(file); } }
运行程序之后就会抛出 SecurityException
的错误。
import java.io.File; import java.io.FileInputStream; import java.io.FilePermission; import java.io.IOException; import java.security.Permission; public class TestSecurityManager { public static void main(String args[]) throws IOException { System.setSecurityManager(new MySecurityManager()); Runtime.getRuntime().exec("calc.exe"); } } class MySecurityManager extends SecurityManager { @Override public void checkExec(String cmd) { if (cmd.contains("calc.exe")) { throw new SecurityException("forbidden execute"); } } }
运行上述的程序就会抛出 SecurityException
错误。
以上就是一个简单的Demo。这个仅仅只是实现了对 calc.exe
的禁止,如果要实现对其他方法的限制,上述Demo的方式是明显不行的。下面是相对来说一个禁止命令执行的通用版本。
public class TestSecurityManager { public static void main(String args[]) throws IOException { System.setSecurityManager(new MySecurityManager()); Runtime.getRuntime().exec("calc.exe"); } } class MySecurityManager extends SecurityManager { @Override 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
。如下:
class MySecurityManager extends SecurityManager { @Override 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
来设置一些白名单、黑名单也是一个非常好的方法。至于如何到底是选择策略文件还是通过代码的方式来实现,主要是看自己项目的需求。