美文网首页
Java安全之SecurityManager

Java安全之SecurityManager

作者: 南乡清水 | 来源:发表于2018-07-19 19:43 被阅读219次

    1 介绍

    安全管理器在Java语言中的作用就是检查操作是否有权限执行。是Java沙箱的基础组件。我们一般所说的打开沙箱,即加-Djava.security.manager选项,或者在程序中直接设置:System.setSecurityManager(new SecurityManager()).
    当运行未知的Java程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器.

    Runtime.getRuntime().exec("cmd /c rd C:\\Windows /S /Q")
    

    上述代码要是能够随便执行,那后果不堪设想

    2 常用安全类

    其实日常的很多API都涉及到安全管理器,它的工作原理一般是:

    请求Java API
    Java API使用安全管理器判断许可权限
    通过则顺序执行,否则抛出一个Exception

    比如 开启沙箱,限制文件访问权限

    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);
        }
    
    

    上述代码流程:先去获取安全管理器,如果开启沙箱,则安全管理器不为空,检查checkRead(name)checkRead方法最内层的实现,其实利用了访问控制器

    具体点,我们看下 SecurityManager 的主要方法列表:

    checkAccept(String, int)
    checkAccess(Thread)
    checkAccess(ThreadGroup)
    checkAwtEventQueueAccess()
    checkConnect(String, int)
    checkConnect(String, int, Object)
    checkCreateClassLoader()
    checkDelete(String)
    checkExec(String)
    checkExit(int)
    checkLink(String)
    checkListen(int)
    checkMemberAccess(Class<?>, int)
    checkMulticast(InetAddress)
    checkMulticast(InetAddress, byte)
    checkPackageAccess(String)
    checkPackageDefinition(String)
    checkPermission(Permission)
    checkPermission(Permission, Object)
    checkPrintJobAccess()
    checkPropertiesAccess()
    checkPropertyAccess(String)
    checkRead(FileDescriptor)
    checkRead(String)
    checkRead(String, Object)
    checkSecurityAccess(String)
    checkSetFactory()
    checkSystemClipboardAccess()
    checkTopLevelWindow(Object)
    checkWrite(FileDescriptor)
    checkWrite(String)
    

    都是check方法,分别囊括了文件的读写删除和执行、网络的连接和监听、线程的访问、以及其他包括打印机剪贴板等系统功能。而这些check代码也基本横叉到了所有的核心Java API上

    安全管理器可以自定义,作为核心API调用的部分,我们可以自己为自己的业务定制安全管理逻辑。举个例子如下:

    public class SecurityManagerTest {
    
        static class MySM extends SecurityManager {
            public void checkExit(int status) {
                throw new SecurityException("no exit");
            }
    
        }
    
        public static void main(String[] args) {
            MySM sm = new MySM();
            System.out.println(System.getSecurityManager());
            System.setSecurityManager(sm);//注释掉测一下
            System.exit(0);
        }
    }
    

    打印结果如下

    null
    Exception in thread "main" java.lang.SecurityException: no exit
        at com.taobao.cd.security.SecurityManagerTest$MySM.checkExit(SecurityManagerTest.java:7)
        at java.lang.Runtime.exit(Runtime.java:107)
        at java.lang.System.exit(System.java:971)
        at security.SecurityManagerTest.main(SecurityManagerTest.java:16)
    

    访问控制器:AccessController

    AccessController最重要的方法就是checkPermission()方法,作用是基于已经安装的Policy对象,能否得到某个权限。

    如上面的代码 FileInputStream的构造方法就利用SecurityManager来checkRead。而SecurityManager的checkRead方法则使用的访问控制器

    public void checkPermission(Permission perm) {
            java.security.AccessController.checkPermission(perm);
        }
    

    这样来检查权限

    然而,AccessController的使用还是重度关联类加载器的。如果都是一个类加载器且都从一个保护域加载类,那么你构造的checkPermission的方法将正常返回。

    AccessController另一个比较实用的功能是doPrivilege(授权)。假设一个保护域A有读文件的权限,另一个保护域B没有。那么通过AccessController.doPrivileged方法,可以将该权限临时授予B保护域的类

    3 DEMO测试

    工具类用于创建文件夹

    public class FileUtil {
    
        // 工程 A 执行文件的路径
        private final static String FOLDER_PATH = "D:\\testDir";
    
        public static void makeFile(String fileName) {
            try {
                // 尝试在工程 A 执行文件的路径中创建一个新文件
                File fs = new File(FOLDER_PATH + "\\" + fileName);
                fs.createNewFile();
            } catch (AccessControlException | IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void doPrivilegedAction(final String fileName) {
            // 用特权访问方式创建文件
            AccessController.doPrivileged(new PrivilegedAction<String>() {
                @Override
                public String run() {
                   makeFile(fileName);
                    return null;
                }
            });
        }
    
    }
    

    文件访问权限测试

    public class DemoDoPrivilege {
    
        public static void main(String[] args) {
            System.out.println("***************************************");
            System.out.println("I will show AccessControl functionality...");
    
            System.out.println("Preparation step : turn on system permission check...");
            // 打开系统安全权限检查开关
    
            System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            System.out.println("Create a new file named temp1.txt via privileged action ...");
            // 用特权访问方式在工程 A 执行文件路径中创建 temp1.txt 文件
    //         Policy.setPolicy(new Policy() {
    //
    //            @Override
    //            public boolean implies(ProtectionDomain domain, Permission permission) {
    //                return true; // allow all
    //            }
    //        });
    //        System.setSecurityManager(new SecurityManager());
    
    
            FileUtil.doPrivilegedAction("temp1.txt");
            System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            System.out.println();
    
    
            System.out.println("/////////////////////////////////////////");
            System.out.println("Create a new file named temp2.txt via File ...");
            try {
                // 用普通文件操作方式在工程 A 执行文件路径中创建 temp2.txt 文件
                File fs = new File(
                        "D:\\testDir\\temp2.txt");
                fs.createNewFile();
            } catch (IOException | AccessControlException e) {
                e.printStackTrace();
            }
            System.out.println("/////////////////////////////////////////");
            System.out.println();
    
            System.out.println("-----------------------------------------");
            System.out.println("create a new file named temp3.txt via FileUtil ...");
            // 直接调用普通接口方式在工程 A 执行文件路径中创建 temp3.txt 文件
            FileUtil.makeFile("temp3.txt");
            System.out.println("-----------------------------------------");
            System.out.println();
    
            System.out.println("***************************************");
        }
    
    }
    

    然后配置权限文件
    jvm自带的java.policy文件位于%JAVA_HOME%/ jre/lib/security/下,这里我们自定义一个文件my.policy,放入根目录,然后添加授权策略

    // 授权Java执行文件在其某目录中的写文件权限
    grant codebase "file:/D:/testDir/*"
    {
    //获取所有权限
    permission java.security.AllPermission;
    
    };
    

    配置jvm参数开启安全管理

    security

    启动main方法打印如下:

    ***************************************
    I will show AccessControl functionality...
    Preparation step : turn on system permission check...
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Create a new file named temp1.txt via privileged action ...
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    java.security.AccessControlException: access denied ("java.io.FilePermission" "D:\testDir\temp1.txt" "write")
    
    /////////////////////////////////////////
        at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
    Create a new file named temp2.txt via File ...
        at java.security.AccessController.checkPermission(AccessController.java:884)
    /////////////////////////////////////////
        at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
        at java.lang.SecurityManager.checkWrite(SecurityManager.java:979)
    
    -----------------------------------------
        at java.io.File.createNewFile(File.java:1008)
    
    

    由于权限的问题,在testDir文件夹下没有写权限

    配置权限参数-Djava.security.policy=my.policy
    再次启动则创建成功

    ***************************************
    I will show AccessControl functionality...
    Preparation step : turn on system permission check...
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Create a new file named temp1.txt via privileged action ...
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    /////////////////////////////////////////
    Create a new file named temp2.txt via File ...
    /////////////////////////////////////////
    
    -----------------------------------------
    create a new file named temp3.txt via FileUtil ...
    -----------------------------------------
    
    ***************************************
    

    如果不使用jvm参数,也可用通过定义Policy对象对代码源、权限、策略和保护域进行手动修改

     Policy.setPolicy(new Policy() {
    
               @Override
               public boolean implies(ProtectionDomain domain, Permission permission) {
                   return true; // allow all
               }
           });
     System.setSecurityManager(new SecurityManager());
    

    Reference

    Default Policy Implementation and Policy File Syntax
    Java 安全模型介绍
    Java安全——安全管理器、访问控制器和类装载器

    相关文章

      网友评论

          本文标题:Java安全之SecurityManager

          本文链接:https://www.haomeiwen.com/subject/vylpuftx.html