转载

利用Java SecurityManager进行权限控制

上一节对使用策略文件来定义危险函数的用法进行了说明,本文主要是通过代码的方式对权限进行控制。关于 SecurityManager 的机制可以具体看看 java沙箱绕过 中的Java Security Manager介绍章节。

具体来说, SecurityManager 可以对JAVA中的诸如文件访问、命令执行、反射的方法的精准控制。

SecurityManager控制

文件读取的控制

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 来设置一些白名单、黑名单也是一个非常好的方法。至于如何到底是选择策略文件还是通过代码的方式来实现,主要是看自己项目的需求。

原文  http://blog.spoock.com/2019/12/24/Getting-Started-with-Java-SecurityManager-from-Zero-2/
正文到此结束
Loading...