您的位置:首页 > 游戏 > 游戏 > 人防门电气图纸符号大全久久建筑网_湖北省建设工程信息网官网_网站安全检测工具_百度上搜索关键词如何在首页

人防门电气图纸符号大全久久建筑网_湖北省建设工程信息网官网_网站安全检测工具_百度上搜索关键词如何在首页

2024/12/22 19:11:28 来源:https://blog.csdn.net/m0_71385141/article/details/144596546  浏览:    关键词:人防门电气图纸符号大全久久建筑网_湖北省建设工程信息网官网_网站安全检测工具_百度上搜索关键词如何在首页
人防门电气图纸符号大全久久建筑网_湖北省建设工程信息网官网_网站安全检测工具_百度上搜索关键词如何在首页

代码沙箱实现

代码沙箱:只负责接收代码和输入,返回编译运行的结果,不负责判题(可作为独立项目/服务,提供给其他需要执行代码的项目使用)
以Java语言为主,实现代码沙箱。主要学习其思想、关键流程。
(可扩展实现C、C++代码沙箱)

Java原生实现代码沙箱(原生:尽可能不借助第三方库和依赖,用最干净、最原始的方式实现)–代码沙箱实现原理:接受代码->编译代码(javac)->执行代码(java)

新建一个新的SpringBoot项目并且成功运行(Java8、SpringBoot2.7)

javac SimpleCompute.java 编译了 SimpleCompute.java 源文件javac .\SimpleCompute.java
.\SimpleCompute.java:10: 错误: 编码GBK的不可映射字符System.out.println("缁撴灉涓?" + a + b);^
1 个错误
编译后的程序中文乱码:命令行终端编码是GBK,和java代码文件本身的UTF-8编码不一致,导致乱码。
通过chcp查看命令查看编码,GBK是活动代码页: 936
解决方案:
javac -encoding utf-8 .\SimpleCompute.java
java -cp . SimpleCompute 1 2
结果:3
实际OJ系统中,对用户输入的代码会有一定的要求,便于系统统一处理。所以此处我们把用户输入代码的类名限制为Main 减少类名不一致的风险,而且不用从用户代码中提取类名,更方便。
示例代码:
public class Main {public static void main(String[] args) {int a = Integer.parseInt(args[0]);int b = Integer.parseInt(args[1]);System.out.println("结果:" + (a + b));}
}

实际执行

javac -encoding utf-8 .\Main.java         
java -cp . Main 1 2              
结果:3
核心流程实现(核心实现思路:用程序代替人工,用程序来操作命令行,去编译执行代码 Process Java-java进程执行管理类)
  1. 将用户代码保存为一个Main文件
  2. 编译代码,得到class文件
  3. 执行代码,得到输出结果
  4. 收集整理输出结果
  5. 文件清理
  6. 错误处理(提升程序健壮性)
1. 将用户代码保存为一个Main文件
  • 引入Hutool工具类,读取文件
<!-- https://hutool.cn/docs/index.html#/--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.8</version></dependency>

新建目录,每个用户的代码都存放在独立的目录下,便于维护

public class JavaNativeCodeSandbox implements CodeSandbox {private static final String GlOBAL_CODE_DIR_NAME = "tmpCode";private static final String GlOBAL_JAVA_CLASS_NAME = "Main.java";public static void main(String[] args) {JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox();ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();executeCodeRequest.setInputList(Arrays.asList("2 2", "1 4"));executeCodeRequest.setUserCode(ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8));executeCodeRequest.setCodeLanguage("java");ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);System.out.println(executeCodeResponse);}@Overridepublic ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {String userCode = executeCodeRequest.getUserCode();String userDir = System.getProperty("user.dir");//获取全局路径String globalCodePathName = userDir + File.separator + GlOBAL_CODE_DIR_NAME;//绝对代码路径//判断全局代码目录是否存在,若文件夹不存在就新建文件夹if (FileUtil.exist(globalCodePathName)) {FileUtil.mkdir(globalCodePathName);}//把用户代码隔离存放 用户代码文件夹String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();String userCodePath = userCodeParentPath + File.separator + GlOBAL_JAVA_CLASS_NAME;File userCodeFile = FileUtil.writeString(userCode, userCodePath, StandardCharsets.UTF_8);return null;}
}
2. 编译代码,得到class文件
  • 读取编译字符串,执行编译命令,java获取控制台输出
 //2.编译代码,得到class文件
String compileCmd = String.format("javac -encoding utf-8%s", userCodeFile.getAbsolutePath());//编译命令
try {Process compileProcess = Runtime.getRuntime().exec(compileCmd);//执行编译命令//分批获取进程的正常输出BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()));StringBuilder compileOutputStringBuilder = new StringBuilder();String compileOutputLine;//逐行读取while ((compileOutputLine = bufferedReader.readLine()) != null) {compileOutputStringBuilder.append(compileOutputLine);}System.out.println(compileOutputStringBuilder);int exitValue = compileProcess.waitFor();//获取退出码 等待程序执行完毕,获取错误码判断程序执行结果if (exitValue == 0) {//正常退出System.out.println("编译成功!");} else {//异常退出System.out.println("编译失败!错误码:" + exitValue);//分批获取进程的异常输出BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getErrorStream()));String errorCompileOutputLine;StringBuilder errorCompileOutputStringBuilder = new StringBuilder();//逐行读取while ((errorCompileOutputLine = errorBufferedReader.readLine()) != null) {errorCompileOutputStringBuilder.append(errorCompileOutputLine);}System.out.println(errorCompileOutputStringBuilder);}
} catch (IOException | InterruptedException e) {throw new RuntimeException(e);
}
3. 执行代码,得到输出结果
  • 封装工具类,将执行命令以及处理执行结果的代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;/*** 执行进程并获取信息* @Author Adellle* @Date 2024/12/19 18:23* @Version 1.0*/
public class ProcessUtils {/*** @param runProcess* @param optName* @return*/public static ExecuteMessage runProcessAndGetMessage(Process runProcess,String optName) {ExecuteMessage executeMessage = new ExecuteMessage();try {int exitValue = 0;exitValue = runProcess.waitFor();executeMessage.setExitValue(exitValue);if (exitValue == 0) {//正常退出System.out.println(optName+"成功!");//分批获取进程的正常输出BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(runProcess.getInputStream()));StringBuilder compileOutputStringBuilder = new StringBuilder();String compileOutputLine;//逐行读取while ((compileOutputLine = bufferedReader.readLine()) != null) {compileOutputStringBuilder.append(compileOutputLine);}executeMessage.setMessage(compileOutputStringBuilder.toString());} else {//异常退出System.out.println(optName+"失败!错误码:" + exitValue);//分批获取进程的正常输出BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(runProcess.getInputStream()));StringBuilder compileOutputStringBuilder = new StringBuilder();String compileOutputLine;//逐行读取while ((compileOutputLine = bufferedReader.readLine()) != null) {compileOutputStringBuilder.append(compileOutputLine);}//分批获取进程的异常输出BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(runProcess.getErrorStream()));String errorCompileOutputLine;StringBuilder errorCompileOutputStringBuilder = new StringBuilder();//逐行读取while ((errorCompileOutputLine = errorBufferedReader.readLine()) != null) {errorCompileOutputStringBuilder.append(errorCompileOutputLine);}executeMessage.setMessage(compileOutputStringBuilder.toString());executeMessage.setErrorMessage(errorCompileOutputStringBuilder.toString());}} catch (InterruptedException | IOException e) {e.printStackTrace();}return executeMessage;}
}
  • 本次使用的是非交互式的判题方式,无需用户输入。但是很多OJ都是与用户交互式的判题模式,可以让用户不断输入内容并获取,具体代码可以自己优化完善。
4. 收集整理输出结果
  • 获取程序执行时间和空间
    获取程序执行时间:使用Spring的StopWatch,此处使用每次样例执行时间的最大值作为执行时间
StopWatch stopWatch=new StopWatch();
stopWatch.start();
stopWatch.stop();
5. 文件清理(防止服务器内存浪费)
if (userCodeFile.getParentFile() != null) {if (FileUtil.del(userCodeParentPath)) {System.out.println("删除成功!");} else {System.out.println("删除失败!");}}
6. 错误处理(提升程序健壮性)
private ExecuteCodeResponse getErrorResponse(Throwable e) {ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();executeCodeResponse.setOutputList(new ArrayList<>());executeCodeResponse.setMessage(e.getMessage());executeCodeResponse.setStatus(2);//代码沙箱错误executeCodeResponse.setJudgeInfo(new JudgeInfo());return executeCodeResponse;
}

异常情况演示

目前为止,核心流程都已经实现,若真正上线,安全吗?
用户提交恶意代码

  1. 执行阻塞,占用资源不释放(如果多个用户可能会把服务器资源占用不释放导致服务器崩溃)
public class SleepError {public static void main(String[] args) throws InterruptedException {long ONE_HOUR = 60 * 60 * 1000L;Thread.sleep(ONE_HOUR);System.out.println("睡丸辣");}
}
  1. 占用内存,不释放(程序可能试图创建大量对象,导致堆内存被耗尽)
import java.util.ArrayList;
import java.util.List;/*** 浪费系统内存** @Author Adellle* @Date 2024/12/19 20:45* @Version 1.0*/
public class Main {public static void main(String[] args) throws InterruptedException {List<byte[]> bytes = new ArrayList<>();while(true){bytes.add(new byte[10000]);}}
}

实际运行中我们会发现JVM内存占用到达一定空间后程序会自动报错:java.lang.OutOfMemoryError: Java heap space
这是JVM的一种保护机制
3. 读文件(越过权限,读取数据库信息)

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;/*** 读取服务器文件(文件信息泄露) ** @Author Adellle* @Date 2024/12/19 20:45* @Version 1.0*/
public class Main {public static void main(String[] args) throws InterruptedException, IOException {String property = System.getProperty("user.dir");String filePath = property+ File.separator+"src/main/resources/application.yml";List<String> strings= Files.readAllLines(Paths.get(filePath));System.out.println(String.join("\n",strings));}
}
  1. 写文件,文件信息泄露(越权,植入木马)
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;/*** 向服务器写文件,植入木马** @Author Adellle* @Date 2024/12/19 20:45* @Version 1.0*/
public class Main {public static void main(String[] args) throws InterruptedException, IOException {String property = System.getProperty("user.dir");String filePath = property+ File.separator+"src/main/resources/木马程序.bat";String errorProgram="java -version 2>&1";Files.write(Paths.get(filePath), Arrays.asList(errorProgram));System.out.println("木马植入成功!你又丸辣哈哈");}
}
  1. 运行其他程序(直接通过Process执行危险程序,或者电脑上其他程序)
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;/*** 向服务器写文件,植入木马** @Author Adellle* @Date 2024/12/19 20:45* @Version 1.0*/
public class Main {public static void main(String[] args) throws InterruptedException, IOException {String property = System.getProperty("user.dir");String filePath = property + File.separator + "src/main/resources/木马程序.bat";Process process=Runtime.getRuntime().exec(filePath);process.waitFor();//分批获取进程的正常输出BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));StringBuilder compileOutputStringBuilder = new StringBuilder();String compileOutputLine;//逐行读取while ((compileOutputLine = bufferedReader.readLine()) != null) {
//            compileOutputStringBuilder.append(compileOutputLine);System.out.println(compileOutputLine);}System.out.println("执行异常程序成功!");}
}
  1. 执行高危命令
    甚至不用写木马文件,直接执行系统自带的危险命令(例如删除服务器文件,删除数据库,或者获取系统上的所有文件信息)
怎么解决?(解决方案)
  1. 超时控制:通过创建一个守护线程,超时后自动中断process实现
Process runProcess = Runtime.getRuntime().exec(runCmd);//执行运行命令
new Thread(() -> {try {Thread.sleep((TIME_OUT));runProcess.destroy();} catch (InterruptedException e) {throw new RuntimeException(e);}
}).start();
  1. 限制给用户分配资源:不能让每个Java进程的执行占用的JVM最大内存空间都和系统的一致,实际应该小一点 在启动Java时,可以指定JVM的参数:-Xmx256m(最大堆空间大小)-Xm(初始堆空间大小)java -Xmx256m -Dfile.encoding=UTF-8 -cp %s Main %s
  2. 限制代码(禁止写读写文件的代码)黑白名单
    先定义一个黑白名单
//字典树校验黑名单代码
WordTree wordTree = new WordTree();
wordTree.addWords(blackList);
FoundWord foundWord = wordTree.matchWord(userCode);
if (foundWord != null) {System.out.println(foundWord.getFoundWord());ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();executeCodeResponse.setMessage("无操作权限!请输入正确代码");return executeCodeResponse;
}

字典树:节约空间,节约时间查找
缺点:无法遍历所有的黑名单;不同的编程语言对应领域和关键词都不同,成本太大。
4. 限制用户操作权限(文件、内存、CPU、网络、执行)
Java安全管理器(实现更加严格的限制)Security Manager:
5. 运行环境隔离

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com