• 学习笔记-java代码审计-文件操作


    java代码审计-文件操作

    0x01 文件上传

    这段代码来自菜鸟教程:

    1. private static final String UPLOAD_PATH = "/tmp/upload";
    2. private boolean uploadWithFileUpload(HttpServletRequest request) {
    3. if(!ServletFileUpload.isMultipartContent(request)) {
    4. //response.getWriter().print("Error: 表单必须包含 enctype=multipart/form-data");
    5. return false;
    6. }
    7. /*省略部分初始化代码*/
    8. try {
    9. List fileItems = servletFileUpload.parseRequest(request);
    10. if(fileItems != null && fileItems.size() > 0) {
    11. for(FileItem fileItem : fileItems) {
    12. String fileName = new File(fileItem.getName()).getName(); //获取文件名
    13. String filePath = UPLOAD_PATH + File.separator + fileName; //上传路径
    14. File storeFile = new File(filePath);
    15. fileItem.write(storeFile); //写文件
    16. }
    17. //response.getWriter().println("upload success");
    18. }
    19. } catch (Exception e) {
    20. //response.getWriter().print(e.getMessage());
    21. return false;
    22. }
    23. return true;
    24. }

    标准的任意文件上传代码。。。现在的web框架会将上传的文件放在web访问不到的位置存储,这样会一定程度上缓解上传漏洞,但如果在展示文件时处理不当,就会导致任意文件读取或下载。

    servlet3之后,可以不使用commons-fileupload、commons-io这两个jar包来处理文件上传,转而使用request.getParts()获取上传文件。此外,servlet3还支持以注解的形式定义一些上传的属性。

    1. private boolean uploadWithAnnotation(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    2. Part part = request.getPart("fileName");
    3. if(part == null) {
    4. return false;
    5. }
    6. String filename = UPLOAD_PATH + File.separator + part.getSubmittedFileName();
    7. part.write(filename);
    8. part.delete();
    9. return true;
    10. }

    可以看到这种方法代码比较简洁,需要注意的是要在类前面加@MultipartConfig这个注解,否则会报错。

    防御

    文件上传参考这个https://mp.weixin.qq.com/s/LzVseWzLP3CjQsmYzvELZA

    1. 判断文件content-type

    2. 文件上传后改名

      1. String fileExt = filename.substring(filename.lastIndexOf(".") + 1); //获取后缀名
      2. String savename = UPLOAD_PATH + File.separator + DigestUtils.md5Hex(System.currentTimeMillis() + filename) + "." + fileExt;
    3. 设置严格的后缀名白名单

      使用esapi的后缀名检测,需要在对应的resource目录建立ESAPI.propertiesvalidation.properties,文件内容可以留空,也可以下载官方内容https://github.com/ESAPI/esapi-java-legacy/blob/develop/src/test/resources/esapi

      1. //检测后缀名
      2. if(!ESAPI.validator().isValidFileName("upload", filename, ALLOW_EXT, false)) {
      3. response.getWriter().println("后缀名不合法");
      4. }
    4. 对文件大小进行限制

      基于ServletFileUpload

      1. DiskFileItemFactory factory = new DiskFileItemFactory();
      2. ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
      3. servletFileUpload.setSizeMax(1024 * 400);

      基于注解( version > servlet3)

      1. @MultipartConfig(maxFileSize = 1000 * 1024 * 1024, maxRequestSize = 1000 * 1024 * 1024)
      2. public class UploadServlet extends HttpServlet {}
    5. 条件允许的情况下,将文件放到web访问不到的位置存储

    0x02 文件操作(下载/读取/删除)

    这几个操作比较类似,都是对文件的操作。

    java的文件读取一般有两种方法,一种是基于InputStream,另一种是基于FileReader。

    读取

    1. //InputStream
    2. File file = new File(filename);
    3. InputStream inputStream = new FileInputStream(file);
    4. while(-1 != (len = inputStream.read())) {
    5. outputStream.write(len);
    6. }
    1. //FileReader
    2. String fileContent = "";
    3. FileReader fileReader = new FileReader(filename);
    4. BufferedReader bufferedReader = new BufferedReader(fileReader);
    5. String line = "";
    6. while (null != (line = bufferedReader.readLine())) {
    7. fileContent += (line + "\n");
    8. }

    下载

    读写操作都有的情况下,用流比较方便。

    1. //stream
    2. String filename = request.getParameter("filename");
    3. File file = new File(filename);
    4. response.reset();
    5. response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("utf-8")));
    6. response.addHeader("Content-Length", "" + file.length());
    7. response.setContentType("application/octet-stream; charset=utf-8");
    8. InputStream inputStream = new FileInputStream(file);
    9. OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
    10. int len;
    11. while(-1 != (len = inputStream.read())) {
    12. outputStream.write(len);
    13. }
    14. inputStream.close();
    15. outputStream.close();

    删除

    1. String filename = request.getParameter("filename");
    2. File file = new File(filename);
    3. if(file != null && file.exists() && file.delete()) {
    4. response.getWriter().println("delete success");
    5. } else {
    6. response.getWriter().println("delete failed");
    7. }

    防御

    防御的核心在于对传入的文件名进行正确的过滤,防止跨目录。

    1. private boolean checkFilename(String filename) {
    2. filename = filename.replace("\\", "/"); //消除平台差异
    3. //将文件操作限定在指定目录中
    4. File file = new File(filename);
    5. if(file == null || !file.exists() || file.getCanonicalFile().getParent().equals(new File(DOWNLOAD_PATH).getCanonicalPath())) { //检测上级目录是否为指定目录
    6. return false;
    7. }
    8. //检测文件名中是否有敏感字符
    9. List blackList = Arrays.asList("./", "../");
    10. for(String badChar : blackList) {
    11. if(filename.contains(badChar)) {
    12. return false;
    13. }
    14. }
    15. //对文件后缀名设置白名单
    16. List whiteExt = Arrays.asList("png", "jpg", "gif", "jpeg", "doc");
    17. String fileExt = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
    18. if(!whiteExt.contains(fileExt)) {
    19. return false;
    20. }
    21. return true;
    22. }

    0x03 文件写入

    1. //FileWriter
    2. FileWriter fileWriter = new FileWriter(filename);
    3. BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
    4. bufferedWriter.write(fileContent);
    5. bufferedWriter.close();

    防御

    文件写入与上述三个操作略有不同,在防御时需要对写入的内容进行过滤。

    1. private boolean checkFilecontent(String fileContent) {
    2. List blackContents = Arrays.asList("<%@page", "<%!");
    3. for(String blackContent : blackContents) {
    4. if(fileContent.contains(blackContent)) {
    5. return false;
    6. }
    7. }
    8. return true;
    9. }

    在写入时最好将标签进行htmlencode

    1. String fileContent = request.getParameter("content");
    2. fileContent = ESAPI.encoder().encodeForHTML(fileContent);

    点击关注,共同学习!安全狗的自我修养

    github haidragon

    https://github.com/haidragon

  • 相关阅读:
    redux最佳实战(一)
    gRPC [Node.js] MasterClass:构建现代API和微服务
    ArrayList的线程安全类CopyOnWriteArrayList
    Spring Boot+RabbitMQ 通过fanout模式实现消息接收(支持消费者多实例部署)
    SpringMvc如何向Session域中设置数据
    微信小程序基础
    【无标题】
    【Unity3D】Unity 游戏画面帧更新 ( 游戏物体 GameObject 移动 | 借助 Time.deltaTime 进行匀速运动 )
    【SpringBoot】搭建第一个SpringBoot项目 - group、artifact等项目元数据详解
    pycharm集成通义灵码
  • 原文地址:https://blog.csdn.net/sinat_35360663/article/details/127830741