likes
comments
collection
share

简单聊聊java的SecurityManager

作者站长头像
站长
· 阅读数 3

老铁们好,我是V,今天我们简单聊聊java的Security Manager

什么是Java Security Manager?

首先我们来简单地看一段关于反射的代码

package org.example;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;


public class ReflectDemo {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Security");
        list.add("Manager");

        Field sizeField = ArrayList.class.getDeclaredField("size");
        sizeField.setAccessible(true);

        int size = (int) sizeField.get(list);

        System.out.println(String.format("size:[%s]", size));

    }

}

各位大佬肯定是一眼就看出size为3,没错的确是3,但是这个不是问题的重点。

好的我们来运行起来,验证下结果

简单聊聊java的SecurityManager

不好意思,用的JDK21,忘记加参数 --add-opens java.base/java.util=ALL-UNNAMED 了

我们加上参数,运行下

简单聊聊java的SecurityManager

但上面的都不是重点,我们debug看下

简单聊聊java的SecurityManager

这个sm是什么XP?为啥是空的呢?

下面就来到重点部分了

SecurityManager简介

Java 平台强调安全性。数据的完整性受到 Java 语言和 VM 内置内存安全性的保护:变量在使用前初始化,数组边界检查,内存释放是完全自动的。同时,数据的机密性受到 Java 类库对现代加密算法和协议(如 SHA-3、EdDSA 和 TLS 1.3)的可信实现的保护。

SecurityManager是一个长期存在的安全性元素,它可以追溯到 Java 1.0。在 Web 浏览器下载 Java 小程序的时代,安全管理器通过在沙箱中运行小程序来保护用户机器的完整性和数据的机密性,从而拒绝访问文件系统或网络等资源。Java 类库的体积很小(Java 1.0 中只有 <> 个包),这使得代码变得可行,例如,在执行任何操作之前咨询安全管理器。安全管理器在不受信任的代码(来自远程计算机的小程序)和受信任的代码(本地计算机上的类)之间划出了一条明线:它将批准所有涉及受信任代码的资源访问的操作,但拒绝不受信任代码的操作。

在 Java 1.2 中,重新设计了SecurityManager,以专注于应用最小权限原则:默认情况下,所有代码都将被视为不受信任,受制于沙盒样式的控制,这些控制会阻止访问资源,并且用户将通过授予他们访问特定资源的特定权限来信任特定代码库。从理论上讲,类路径上的应用程序 JAR 在使用 JDK 的方式上可能比 Internet 中的小程序更受限制。限制权限被视为限制代码主体中可能存在的任何漏洞影响的一种方式,实际上是一种纵深防御机制。

SecurityManager主要防范两种威胁:恶意意图(尤其是远程代码中的恶意)和意外漏洞(尤其是本地代码中的漏洞)。

开启SecurityManager

上面的示例程序,我们加上 -Djava.security.manager 试下,直接抛出异常了

简单聊聊java的SecurityManager

debug看下,原来这个sm开启后他也不是一个摆设

简单聊聊java的SecurityManager

简单聊聊java的SecurityManager

原来自带的权限只有两个,并没有我们需要的权限

"java.lang.RuntimePermission" "exitVM"
"java.io.FilePermission" "/Users/valsong/git/v-git/security-demo/target/classes/-" "read"

使用security.policy开启部分权限

那么如何添加我们需要的权限呢?

首先准备一个 security.policy 文件

里面填写

grant codeBase "file:/Users/valsong/git/v-git/security-demo/target/classes" {
    permission java.lang.RuntimePermission "accessDeclaredMembers";
    permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
};

然后启动类的VM参数添加

--add-opens java.base/java.util=ALL-UNNAMED 
-Djava.security.manager 
-Djava.security.policy=/Users/valsong/git/v-git/security-demo/src/main/resources/security.policy

运行,这样就得到了我们期望的结果

简单聊聊java的SecurityManager

SecurityManager是如何进行权限拦截的呢?

很简单粗暴,java中很多方法中都会执行 sm.checkPermission 方法来验证是否有权限,

当SecurityManager不为空时就会执行该校验

简单聊聊java的SecurityManager

案例:使用SecurityManager禁止arthas连接

下面我们来测试下使用arthas来连接使用了java security manager的程序

我们继续复用上面的代码,加上一个while循环,让这段代码可以一直运行

public class ReflectDemo {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Security");
        list.add("Manager");

        Field sizeField = ArrayList.class.getDeclaredField("size");
        sizeField.setAccessible(true);

        int size = (int) sizeField.get(list);

        System.out.println(String.format("size:[%s]", size));


        while (true) {
            LockSupport.parkNanos(5000_000_000L);
            System.out.println(LocalDateTime.now());
        }

    }

}

运行程序后,使用arthas尝试连接

arthas报错

简单聊聊java的SecurityManager

程序报错

简单聊聊java的SecurityManager

如果想开启SecurityManager又想允许arthas连接呢?

在security.policy中添加以下权限后arthas连接正常

这里很多权限就没有细分了,如果要找出每个具体的权限需要花费不少时间

grant  {

        permission java.io.FilePermission "<<ALL FILES>>", "read,write";

        permission java.net.SocketPermission "*", "connect,listen,resolve,accept";

        permission java.lang.management.ManagementPermission "control";
        permission java.util.logging.LoggingPermission "control";

        permission java.util.PropertyPermission "*", "read,write";

        permission java.net.URLPermission "http:*","*:*";
        permission java.net.URLPermission "https:*","*:*";

        permission java.net.NetPermission "*";
        permission java.lang.RuntimePermission "*";
        permission java.lang.reflect.ReflectPermission "*";
        permission ognl.OgnlInvokePermission "*";
        permission java.security.SecurityPermission "*";

}

policy文件简介

JDK默认policy配置文件

默认的安全管理器配置文件是 $JAVA_HOME/jre/lib/security/java.policy,即当未指定配置文件时,将会使用该配置。内容如下:

// Standard extensions get all permissions by default

grant codeBase "file:${{java.ext.dirs}}/*" {
        permission java.security.AllPermission;
};

// default permissions granted to all domains

grant {
        // Allows any thread to stop itself using the java.lang.Thread.stop()
        // method that takes no argument.
        // Note that this permission is granted by default only to remain
        // backwards compatible.
        // It is strongly recommended that you either remove this permission
        // from this policy file or further restrict it to code sources
        // that you specify, because Thread.stop() is potentially unsafe.
        // See the API specification of java.lang.Thread.stop() for more
        // information.
        permission java.lang.RuntimePermission "stopThread";

        // allows anyone to listen on dynamic ports
        permission java.net.SocketPermission "localhost:0", "listen";

        // "standard" properies that can be read by anyone

        permission java.util.PropertyPermission "java.version", "read";
        permission java.util.PropertyPermission "java.vendor", "read";
        permission java.util.PropertyPermission "java.vendor.url", "read";
        permission java.util.PropertyPermission "java.class.version", "read";
        permission java.util.PropertyPermission "os.name", "read";
        permission java.util.PropertyPermission "os.version", "read";
        permission java.util.PropertyPermission "os.arch", "read";
        permission java.util.PropertyPermission "file.separator", "read";
        permission java.util.PropertyPermission "path.separator", "read";
        permission java.util.PropertyPermission "line.separator", "read";

        permission java.util.PropertyPermission "java.specification.version", "read";
        permission java.util.PropertyPermission "java.specification.maintenance.version", "read";
        permission java.util.PropertyPermission "java.specification.vendor", "read";
        permission java.util.PropertyPermission "java.specification.name", "read";

        permission java.util.PropertyPermission "java.vm.specification.version", "read";
        permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
        permission java.util.PropertyPermission "java.vm.specification.name", "read";
        permission java.util.PropertyPermission "java.vm.version", "read";
        permission java.util.PropertyPermission "java.vm.vendor", "read";
        permission java.util.PropertyPermission "java.vm.name", "read";

        permission java.util.PropertyPermission "sun.security.pkcs11.disableKeyExtraction", "read";
};

配置基本原则

  • 没有配置的权限表示没有。
  • 只能配置有什么权限,不能配置禁止做什么。
  • 同一种权限可多次配置,取并集。
  • 统一资源的多种权限可用逗号分割。

配置文件解释

基于路径部分授权

针对指定的路径进行授权

grant codeBase "file:${{java.ext.dirs}}/*" {
     permission java.lang.RuntimePermission "accessDeclaredMembers";
     permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
};

注意这里的路径只能到项目路径不能具体到package路径

例如:file:/Users/valsong/git/v-git/security-demo/target/classes 是可以的,

但是 file:/Users/valsong/git/v-git/security-demo/target/classes/org则不生效

所有路径授权

针对所有路径下的资源进行授权

grant {
     permission java.lang.RuntimePermission "accessDeclaredMembers";
     permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
};

权限类型

  1. AWTPermission:用于控制对AWT(抽象窗口工具包)相关功能的访问权限,如窗口创建、事件处理等。
  2. AllPermission:表示被赋予所有权限的特殊权限对象,它允许所有的操作。它一般用于调试和测试,并不建议在生产环境中使用。
  3. AttachPermission:用于控制对虚拟机的附加(Attach)权限,允许程序以附加者(Attach)的身份连接到正在运行的虚拟机。
  4. AudioPermission:用于控制对音频设备和相关功能的访问权限,如录音、播放声音等。
  5. AuthPermission:用于控制对认证(Authentication)和授权(Authorization)相关功能的访问权限,如访问安全上下文、权限管理等。
  6. BasicPermission:用于权限对象的基本类,它的子类包括许多其他权限类,如FilePermission、SocketPermission等。
  7. CardPermission:用于控制对智能卡设备和功能的访问权限。
  8. CryptoAllPermission:用于表示拥有所有密码学相关权限的特殊权限对象。
  9. CryptoPermission:用于控制对密码学功能和算法的访问权限。
  10. DelegationPermission:用于控制在安全策略文件中授予代码委派(Delegation)的权限,即在运行时将某些类的加载和解析请求委派给其他代码。
  11. FilePermission:用于控制对文件和目录的访问权限,包括读取、写入、执行文件等。
  12. FlightRecorderPermission:用于控制对飞行记录器(Flight Recorder)相关功能的访问权限,飞行记录器是用于记录和分析应用程序性能和行为的工具。
  13. InquireSecContextPermission:用于控制对安全上下文信息的查询权限,安全上下文包含有关当前执行代码的安全信息。
  14. JDIPermission:用于控制对Java调试接口(Java Debug Interface)相关功能的访问权限,允许调试器和其他工具与正在运行的虚拟机进行通信。
  15. JVMCIPermission:用于控制对 JVMCI(JVM Compiler Interface)相关功能的访问权限,JVMCI 是一种编译器接口,允许第三方编译器与虚拟机进行交互。
  16. JlinkPermission:用于控制对 jlink 工具的访问权限,jlink 用于将 Java 模块和依赖项组装为自定义运行时映像。
  17. LinkPermission:用于控制对链接库(native libraries)的访问权限,确保只有受信任的代码可以加载和执行链接库。
  18. LoggingPermission:用于控制对Java日志系统的访问权限,例如记录日志、访问特定日志处理器等。
  19. MBeanPermission:用于控制对MBean对象的操作权限。
  20. MBeanServerPermission:用于控制对 MBean 服务器的访问权限,MBean 是管理和监控 Java 应用程序的一种标准管理接口。
  21. MBeanTrustPermission:用于控制对 MBean 服务器的信任级别的权限控制,允许对信任级别进行精细的管理。
  22. ManagementPermission:用于控制对管理 API 的访问权限,允许管理应用程序、线程、内存等管理操作。
  23. NetPermission:用于控制对网络资源的访问权限,如网络连接、Socket 等。
  24. NetworkPermission:用于控制对网络协议的访问权限,如访问特定的网络协议。
  25. PrivateCredentialPermission:用于控制对私有凭证的访问权限。
  26. PropertyPermission:用于控制对系统属性的访问权限,允许读取和更改系统属性。
  27. ReflectPermission:用于控制对反射 API 的访问权限,允许程序通过反射调用私有方法、访问私有字段等。
  28. RuntimePermission:用于控制对运行时环境的访问权限,如修改系统属性、执行子进程等。
  29. SQLPermission:用于控制对 SQL 数据库相关功能的访问权限,如连接数据库、执行 SQL 语句等。
  30. SSLPermission:用于控制对 SSL(安全套接字层)相关功能的访问权限,如建立 SSL 连接、获取 SSL 客户端证书等。
  31. SecurityPermission:用于控制对安全管理器(Security Manager)的访问权限,包括修改安全策略、更改类加载器等。
  32. SelfPermission in PolicyFile:用于表示对策略文件的自我访问权限,即对自身配置文件的访问权限。
  33. SerializablePermission:用于控制对对象序列化和反序列化的访问权限,允许对象在网络间进行序列化和传输。
  34. ServicePermission:用于控制对Java服务的访问权限。
  35. SocketPermission:用于控制对网络套接字的访问权限。
  36. SubjectDelegationPermission:用于控制对Java主题委派(Subject Delegation)的访问权限,即在运行时允许将主体进行委派。
  37. URLPermission:用于控制对URL资源的访问权限。
  38. UnresolvedPermission:在特定的安全策略文件中,表示未解决的权限。

如何判断需要哪些权限

可以自己实现一个SecurityManager的代理,来打印所需要的权限

import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.Permission;


/**
 * ArthasSecurityManager
 *
 * 测试arthas连接需要哪些权限
 */
public class ArthasSecurityManager extends SecurityManager {

    //private Logger logger = LoggerFactory.getLogger(ArthasSecurityManager.class);
    private SecurityManager delegate;

    public ArthasSecurityManager(SecurityManager securityManager) {
        this.delegate = securityManager;
    }

    @Override
    public void checkPermission(Permission perm) {
        //logger.info("checkPermission, perm: {}", perm);
        System.out.println(String.format("checkPermission, perm: %s", perm));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkPermission(perm);
    }

    @Override
    public void checkPermission(Permission perm, Object context) {
        //logger.info("checkPermission, perm: {}", perm);
        System.out.println(String.format("checkPermission, perm: %s", perm));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkPermission(perm, context);
    }

    @Override
    public void checkCreateClassLoader() {
        //logger.info("checkCreateClassLoader");
        System.out.println("checkCreateClassLoader");
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkCreateClassLoader();
    }

    @Override
    public void checkAccess(Thread t) {
        //logger.info("checkAccess, thread: {}", t);
        System.out.println(String.format("checkAccess, thread: %s", t));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkAccess(t);
    }

    @Override
    public void checkAccess(ThreadGroup g) {
        //logger.info("checkAccess, ThreadGroup: {}", g);
        System.out.println(String.format("checkAccess, ThreadGroup:  %s", g));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkAccess(g);
    }

    @Override
    public void checkExit(int status) {
        //logger.info("checkExit, status: {}", status);
        System.out.println(String.format("checkExit, status:  %s", status));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkExit(status);
    }

    @Override
    public void checkExec(String cmd) {
        //logger.info("checkExec, cmd: {}", cmd);
        System.out.println(String.format("checkExec, cmd:  %s", cmd));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkExec(cmd);
    }

    @Override
    public void checkLink(String lib) {
        //logger.info("checkLink, checkLink: {}", lib);
        System.out.println(String.format("checkLink, checkLink:  %s", lib));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkLink(lib);
    }

    @Override
    public void checkRead(FileDescriptor fd) {
        //logger.info("checkRead, fd: {}", fd);
        System.out.println(String.format("checkRead, fd:  %s", fd));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkRead(fd);
    }

    @Override
    public void checkRead(String file) {
        //logger.info("checkRead, file: {}", file);
        System.out.println(String.format("checkRead, file:  %s", file));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkRead(file);
    }

    @Override
    public void checkRead(String file, Object context) {
        //logger.info("checkRead, file: {}", file);
        System.out.println(String.format("checkRead, file:  %s", file));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkRead(file, context);
    }

    @Override
    public void checkWrite(FileDescriptor fd) {
        //logger.info("checkWrite, fd: {}", fd);
        System.out.println(String.format("checkWrite, fd:  %s", fd));

        if (this.delegate == null) {
            return;
        }
        this.delegate.checkWrite(fd);
    }

    @Override
    public void checkWrite(String file) {
        //logger.info("checkWrite, file: {}", file);
        System.out.println(String.format("checkWrite, file  %s", file));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkWrite(file);
    }

    @Override
    public void checkDelete(String file) {
        //logger.info("checkDelete, file: {}", file);
        System.out.println(String.format("checkDelete, file  %s", file));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkDelete(file);
    }

    @Override
    public void checkConnect(String host, int port) {
        //logger.info("checkConnect, host: {}, port: {}", host, port);
        System.out.println(String.format("checkConnect, host: %s port: %s", host, port));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkConnect(host, port);
    }

    @Override
    public void checkConnect(String host, int port, Object context) {
        //logger.info("checkConnect, host: {}, port: {}", host, port);
        System.out.println(String.format("checkConnect, host: %s port: %s", host, port));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkConnect(host, port, context);
    }

    @Override
    public void checkListen(int port) {
        //logger.info("checkListen, port: {}", port);
        System.out.println(String.format("checkListen, port: %s", port));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkListen(port);
    }

    @Override
    public void checkAccept(String host, int port) {
        //logger.info("checkAccept, host: {}, port: {}", host, port);
        System.out.println(String.format("checkAccept, host: %s port: %s", host, port));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkAccept(host, port);
    }

    @Override
    public void checkPropertiesAccess() {
        //logger.info("checkPropertiesAccess");
        System.out.println("checkPropertiesAccess");
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkPropertiesAccess();
    }

    @Override
    public void checkPropertyAccess(String key) {
        //logger.info("checkPropertyAccess, key: {}", key);
        System.out.println(String.format("checkPropertyAccess, key  %s", key));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkPropertyAccess(key);
    }

    @Override
    public void checkPrintJobAccess() {
        //logger.info("checkPrintJobAccess");
        System.out.println("checkPrintJobAccess");
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkPrintJobAccess();
    }

    @Override
    public void checkPackageAccess(String pkg) {
        //logger.info("checkPackageAccess, pkg: {}", pkg);
        System.out.println(String.format("checkPackageAccess, pkg  %s", pkg));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkPackageAccess(pkg);
    }

    @Override
    public void checkPackageDefinition(String pkg) {
        //logger.info("checkPackageDefinition, pkg: {}", pkg);
        System.out.println(String.format("checkPackageDefinition, pkg  %s", pkg));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkPackageDefinition(pkg);
    }

    @Override
    public void checkSetFactory() {
        //logger.info("checkSetFactory");
        System.out.println("checkSetFactory");
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkSetFactory();
    }

    @Override
    public void checkSecurityAccess(String target) {
        //logger.info("checkSecurityAccess, target: {}", target);
        System.out.println(String.format("checkSecurityAccess, target  %s", target));
        if (this.delegate == null) {
            return;
        }
        this.delegate.checkSecurityAccess(target);
    }

    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {

        System.out.println("===================");

        Field allowSecurityManagerField = System.class.getDeclaredField("allowSecurityManager");
        allowSecurityManagerField.setAccessible(true);
        allowSecurityManagerField.set(null,2);

        SecurityManager securityManager = System.getSecurityManager();

        securityManager = new ArthasSecurityManager(securityManager);
        System.setSecurityManager(securityManager);

        System.in.read();
    }

}

将最开始的demo简单改造下

public class ReflectDemo {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

        System.setSecurityManager(new ArthasSecurityManager(System.getSecurityManager()));

        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Security");
        list.add("Manager");

        Field sizeField = ArrayList.class.getDeclaredField("size");
        sizeField.setAccessible(true);

        int size = (int) sizeField.get(list);

        System.out.println(String.format("size:[%s]", size));

    }

}

执行下我们看下控制台输出

简单聊聊java的SecurityManager

可以看到需要的权限为

checkPermission, perm: ("java.lang.RuntimePermission" "accessDeclaredMembers")
checkPermission, perm: ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")

结束语

  1. 一般程序中SecurityManager都是不开启的,只有某些对安全要求比较高的程序会开启,例如elasticsearch
  2. 如果你希望对你的程序做一些限制,例如不希望使用反射,读取本地文件等,你可以使用java的SecurityManager对你的程序做一些限制,编写security.policy只开放你允许的权限
  3. 因为其脆弱的权限模型、困难的编程模型、性能不佳所以SercurityManager在生产环境中的使用率一直很低,并且无法防范很多类型的风险和漏洞,因此SecurityManager在JDK17之后已经标记为废弃了 openjdk.org/jeps/411

简单聊聊java的SecurityManager

恭喜你又学到了一个没用的知识,散会!