防火防盗防导员! -- OJ系统JAVA程序安全控制

耗时一个月开发的OJ在线判题系统,文末有项目地址,目前还在更新代码~

针对上篇提到的异常情况,分别有如下方案,可以提高程序安全性
1、超时控制
2、限制给用户程序分配的资源
3、限制代码

1、超时控制

通过创建一个守护线程,超时后自动中断 Process 实现

// 超时控制
new Thread(() -> {
    try {
        Thread.sleep(TIME_OUT);
        System.out.println("超时了,中断");
        runProcess.destroy();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}).start();

2、限制资源分配

我们不能让每个 java 进程的执行占用的 JVM 最大堆内存空间都和系统默认的一致(正常默认8 G),实际上应该更小(执行用户的题目代码也不需要这么多),比如说256MB

在启动 java 程序时,可以指定 JVM 的参数: -Xmx256m (最大堆空间大小为256MB)

java -Xmx256m

注意,-Xmx参数,JVM的堆内存限制,不等同于系统实际占用的最大资源,可能会溢出
image.png

如果需要更严格的内存限制,要在系统层面去限制,而不是 JVM 层面的限制。

如果是 Linux 系统,可以使用 cgroup 来实现对某个进程的 CPU、内存等资源的分配

小知识-什么是 cgroup ?

**cgroup**是 Linux 内核提供的一种机制,可以用来限制进程组(包括子进程)的资源使用,例如内存、CPU 、磁盘 I/O 等。通过将 Java 进程放置在特定的 cgroup 中,你可以实现限制其使用的内存和 CPU 数。

小知识-常用 JVM 启动参数

  1. 内存相关参数:

○ -Xms: 设置 JVM 的初始堆内存大小。1680129124930162690_0.785241782385159
○ -Xmx: 设置 JVM 的最大堆内存大小。
○ -Xss: 设置线程的栈大小。
○ -XX:MaxMetaspaceSize: 设置 Metaspace(元空间)的最大大小。1680129124930162690_0.5789460697462636
○ -XX:MaxDirectMemorySize: 设置直接内存(Direct Memory)的最大大小。

  1. 垃圾回收相关参数:
    ○ -XX:+UseSerialGC: 使用串行垃圾回收器。
    ○ -XX:+UseParallelGC: 使用并行垃圾回收器。1680129124930162690_0.8186470925040801
    ○ -XX:+UseConcMarkSweepGC: 使用 CMS 垃圾回收器。
    ○ -XX:+UseG1GC: 使用 G1 垃圾回收器。
  2. 线程相关参数:
    ○ -XX:ParallelGCThreads: 设置并行垃圾回收的线程数。1680129124930162690_0.8722533709020122
    ○ -XX:ConcGCThreads: 设置并发垃圾回收的线程数。
    ○ -XX:ThreadStackSize: 设置线程的栈大小。
  3. JIT 编译器相关参数:
    ○ -XX:TieredStopAtLevel: 设置 JIT 编译器停止编译的层次。
  4. 其他资源限制参数:1680129124930162690_0.45881515540130735
    ○ -XX:MaxRAM: 设置 JVM 使用的最大内存。

3、限制代码 - 黑白名单

实现
先定义一个黑白名单,比如哪些操作是禁止的,可以就是一个列表:

private static final List<String> blackList = Arrays.asList("Files", "exec");

还可以使用字典树代替列表存储单词,用更少的空间存储更多的敏感词汇,并且实现更高效的敏感词查找。
字典树原理:
image.png

此处使用 HuTool 工具库的字典树工具类: WordTree ,不用自己写字典树!
1)先初始化字典树,插入禁用词

private static final WordTree WORD_TREE;

static {
    // 初始化字典树
    WORD_TREE = new WordTree();
    WORD_TREE.addWords(blackList);
}

2)校验用户代码是否包含禁用词:

        FoundWord foundWord = WORD_TREE.matchWord(code);
        if(foundWord != null){
            System.out.println("包含禁止词" + foundWord.getFoundWord());
            return null;
        }

本方案的缺点
1)无法遍历所有黑名单
2)不同的编程语言,对应的领域,关键词都不一样,限制人工成本很大

4、限制权限- java System.setSecurityManager(new MySecurityManager());安全管理器

目标:限制用户对文件,内存,CPU,网络等资源的操作和访问

java 安全管理器基本使用

java 安全管理器(Security Manager)是 Java 提供的保护 JVM、JAVA 安全的机制,可以实现更严格的资源和操作权限
编写安全管理器,只需要继承 Security Manager
1)所有权限放开:

package com.yupi.yuojcodesandbox.security;

import java.security.Permission;

/**
 * 默认安全管理器
 */
public class DefaultSecurityManager extends SecurityManager {

    // 检查所有的权限
    @Override
    public void checkPermission(Permission perm) {
        System.out.println("默认不做任何限制");
        System.out.println(perm);
        // super.checkPermission(perm);
    }
}

2)所有权限拒绝

package com.yupi.yuojcodesandbox.security;

import java.security.Permission;

/**
 * 禁用所有权限安全管理器
 */
public class DenySecurityManager extends SecurityManager {

    // 检查所有的权限
    @Override
    public void checkPermission(Permission perm) {
        throw new SecurityException("权限异常:" + perm.toString());
    }
}

3)限制读权限:

@Override
public void checkRead(String file) {
    throw new SecurityException("checkRead 权限异常:" + file);
}

4)限制写文件权限

@Override
public void checkWrite(String file) {
    throw new SecurityException("checkWrite 权限异常:" + file);
}

5)限制执行文件权限

@Override
public void checkExec(String cmd) {
	throw new SecurityException("checkExec 权限异常:" + cmd);
}

6)限制网络连接权限

@Override
public void checkConnect(String host, int port) {
    throw new SecurityException("checkConnect 权限异常:" + host + ":" + port);
}

结合项目使用

在程序中启用安全管理器:

        System.setSecurityManager(new MySecurityManager());

实际情况下,不应该在主类(开发者自己写的程序)中做限制,只需要限制子程序的权限即可。

启动子进程执行命令时,设置安全管理器,而不是在外层设置(会限制住测试用例的读写和子命令的执行)

具体操作如下:
1)根据需要开发自定义的安全管理器(MySecurityManager)
2)复制MySecurityManager 类到 resources/security 目录下,移除类的包名
3)手动输入命令编译 MySecurityManager 类,得到 class 文件
4)在运行 java 程序时,指定安全管理器 class 文件的路径,安全管理器的名称。
命令:
三个%s 分别是要运行的代码所在文件的父目录,安全管理器 class 文件的路径,输入参数

java -Xmx256m -Dfile.encoding=UTF-8 -cp %s;%s -Djava.security.manager=MySecurityManager Main %s

重新执行程序,发现资源成功被限制
image.png

安全管理器的优点

1)权限控制很灵活
2)实现简单

安全管理器的缺点

1)如果要做比较严格的权限限制,需要自己去判断哪些文件,包名需要允许读写,粒度太细,难以精细化控制
2)安全管理器本身也是 JAVA代码,也有可能存在漏洞,本质上还是程序层面的控制,没深入系统的层面

5、运行环境隔离

原理:操作系统层面上,把用户程序封装到沙箱里,和宿主机(我们的电脑 / 服务器) 隔离开,使得用户的程序无法影响宿主机。

实现方式: Docker 容器技术 (底层是用 cgroup,namespace 等方式实现的),也可以直接用 cgroup 实现

项目地址

(求求大佬们赏个star~)

前端:https://github.com/IMZHEYA/yoj-frontend
后端:https://github.com/IMZHEYA/yoj-backend
代码沙箱:https://github.com/IMZHEYA/yoj-code-sandbox

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值