8.1 限制读取目录或文件

在读取文件或者目录的时候我们需要考虑到文件读取安全问题,严格控制用户传入参数,禁止或限制用户传入文件路径。
检测用户参数合法性代码示例(请根据具体业务需求调整判定逻辑):

  1. <%@ page import="java.io.File" %><%--
  2. Created by IntelliJ IDEA.
  3. User: yz
  4. Date: 2019/12/4
  5. Time: 6:08 下午
  6. To change this template use File | Settings | File Templates.
  7. --%>
  8. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  9. <%!
  10. // 定义限制用户遍历的文件目录常量
  11. private static final String IMAGE_DIR = "/data/images/";
  12. %>
  13. <%
  14. // 定义需要遍历的目录
  15. String dirStr = request.getParameter("dir");
  16. if (dirStr != null) {
  17. File dir = new File(dirStr);
  18. // 获取文件绝对路径,转换成标准的文件路径
  19. String fileDir = (dir.getAbsoluteFile().getCanonicalFile() + "/").replace("\\\\", "/").replaceAll("/+", "/");
  20. out.println("<h3>" + fileDir + "</h3>");
  21. // 检查当前用户传入的目录是否包含在系统限定的目录下
  22. if (fileDir.startsWith(IMAGE_DIR)) {
  23. File[] dirs = dir.listFiles();
  24. out.println("<pre>");
  25. for (File file : dirs) {
  26. out.println(file.getName());
  27. }
  28. out.println("</pre>");
  29. } else {
  30. out.println("目录不合法!");
  31. }
  32. }
  33. %>

请求遍历非系统限制的目录示例:
9. 任意文件/目录访问漏洞修复 - 图1

8.2 RASP防御恶意文件访问攻击

RASP可以使用Agent机制实现Hook任意的Java类API,因此可以轻易的捕获到Java程序读取的任意文件路径。RASP可以将Hook到的文件路径和Http请求的参数进行关联分析,检测Java读取的文件路径是否会受到Http请求参数的控制,如果发现请求参数最终拼接到了文件路径中应当立即阻断文件访问行为,并记录攻击日志。
为了提升RASP的防御能力,应当将Java SE中的所有与文件读写相关的最为底层的Java API类找出来,然后添加监视点。
Java底层操作IO的类API表(<=JDK14)

类名 类型 重要方法
java.io.WinNTFileSystem java.io delete/list/createDirectory/rename/setLastModifiedTime/listRoots
java.io.UnixFileSystem java.io delete/list/createDirectory/rename/setLastModifiedTime/listRoots
java.io.FileInputStream java.io open/read
java.io.FileOutputStream java.io open/write
java.io.RandomAccessFile java.io read/write/seek
sun.nio.ch.FileChannelImpl sun.nio open/read/map/transferTo0
sun.nio.ch.FileDispatcher sun.nio read/pread/readv/write/pwrite/writev/seek
sun.nio.ch.SocketDispatcher sun.nio read/readv/write/writev
sun.nio.ch.DatagramDispatcher sun.nio
read/readv/write/writev
sun.nio.fs.UnixNativeDispatcher sun.nio fopen/read/write/getcwd/link/unlink/rename/mkdir/chown
sun.nio.fs.WindowsNativeDispatcher sun.nio fopen/read/write/getcwd/link/unlink/rename/mkdir/chown
sun.nio.fs.UnixCopyFile sun.nio copy/copyDirectory/copyFile/move/copyLink/copySpecial/transfer
sun.nio.ch.IOUtil sun.nio read/write/randomBytes

Java IO 底层API关系图
9. 任意文件/目录访问漏洞修复 - 图2RASP防御思路:
9. 任意文件/目录访问漏洞修复 - 图3
当RASP检测到恶意的文件访问后会立即阻断文件读取:
9. 任意文件/目录访问漏洞修复 - 图4

8.2.1 禁止文件名空字节访问

在低版本的JDK中允许文件名中包含空字节(俗称%00截断),为了防止该问题,RASP应当在任何文件被访问的时候检测文件名是否包含了空字节,如果有应当立即终止文件的访问。
检测文件名空字节示例代码:

  1. /**
  2. * 检查文件名中是否包含了空字节,禁止出现%00字符截断
  3. *
  4. * @param file 访问文件
  5. * @return 是否包含空字节
  6. */
  7. private static boolean nullByteValid(File file) {
  8. return file.getName().indexOf('\u0000') < 1;
  9. }

8.2.2 禁止写入动态脚本文件

为了避免Web应用被写入恶意的WebShell后门文件,RASP应当在Web应用启动后禁止任何动态脚本的写入操作。在任何与写入文件相关的Java底层方法执行前都应当检测写入的文件后缀是否合法。
禁止写入如下类型的动态脚本文件:jsp,jspx,jspa,jspf,asp,asa,cer,aspx,php
文件写入检测应当处理各类文件写入事件,如:写文件、重命名文件、复制/移动文件、移动目录;RASP设置Hook点时也应当严格处理上述IO操作的类文件(一个都不能漏掉,漏掉一个几乎等于全功尽弃),如果新版本的JDK新增或修改了底层IO操作类应当做同步支持。

8.2.3 文件名和请求参数关联分析

RASP应当分析Hook到的文件路径和请求参数的关联性,分析每一个参数是否对最终Hook到的文件路径有必然的关联关系。
如传入的某个参数最终和Hook到的文件路径完全一致,那么应当立即禁止文件访问请求,因为即便用户请求的不是恶意文件也肯定是一个存在任意文件读取漏洞的业务功能,攻击者可以修改传入的参数实现读取服务器中的任意文件。

8.2.4 文件名检测规则和黑名单

攻击者在验证文件读取类漏洞时通常会使用一些常用的技巧和路径,如:WEB-INF/web.xml/etc/passwd../../../../../../../etc/passwd等。RASP应该有一些内置的黑名单和检测规则来防止黑客攻击。