Java代码审计之目录遍历漏洞详解
字数 1067 2025-08-11 22:57:21

Java代码审计之目录遍历漏洞详解

一、目录遍历漏洞原理

目录遍历漏洞(Path Traversal)是指通过用户输入参数,后端直接将参数拼接到指定路径下读取用户文件名时,由于对用户输入参数控制不严格,攻击者可以利用特殊字符跳转到服务器敏感目录,读取或操作敏感文件。

风险影响

  • 读取服务器密码文件
  • 访问程序数据库
  • 获取Redis等核心配置文件
  • 覆盖受保护的文件或目录

二、人工代码审计关键点

1. 审计关键字

需要重点关注以下Java关键字:

new FileInputStream(path)
new FileOutputStream(path)
new File(path)
RandomAccessFile fp = new RandomAccessFile(fname,"r");
mkdirs
getOriginalFilename
entry.getName()

2. 审计关键类和函数

sun.nio.ch.FileChannelImpl
java.io.File.list/listFiles
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileSystem/Win32FileSystem/WinNTFileSystem/UnixFileSystem
sun.nio.fs.UnixFileSystemProvider/WindowsFileSystemProvider
java.io.RandomAccessFile
sun.nio.fs.CopyFile
sun.nio.fs.UnixChannelFactory
sun.nio.fs.WindowsChannelFactory
java.nio.channels.AsynchronousFileChannel
FileUtil/IOUtil
filePath/download/deleteFile/move/getFile

3. 审计要点

  • 检查对用户传递的文件对象/文件名/文件路径的处理
  • 验证是否限制了可操作文件的路径、文件类型、文件所有者
  • 检查是否排除了敏感文件
  • 检查getPath()、getAbsolutePath()的路径判断逻辑
  • 搜索安全策略配置文件中的权限设置:
    • permission
    • Java.io.FilePermission
    • grant

三、常见漏洞代码示例

1. 未过滤文件路径

// 创建读取要拷贝的文件
InputStream inStream = new FileInputStream(file1);
// 创建要复制到的文件,filename未经校验
OutputStream inStream = new FileOutputStream(new File(file2+"\\"+filename));

2. 直接使用上传文件名

String orgName = mf.getOriginalFilename(); // 获取文件名
File file = new File(orgName);
file.mkdirs(); // 创建文件根目录

3. ZIP解压路径覆盖

// 开始解压
Enumeration entries = zipFile.entries();
// 遍历entries获得entry
while(entries.hasMoreElements()){
    ZipEntry entry = (ZipEntry)entries.nextElement();
    ...
    File targetFile = new File(entry.getName());
    targetFile.getParentFile().mkdirs();
}

四、渗透测试方法

1. 常见测试方式

  • 尝试使用路径跳转符:../..\%2e%2e%2f%2e%2e%5c%20%0a
  • 在Unix系统中尝试使用换行符:etc/passwd%0a.jpg
  • Java特定绕过:%c0%ae(解析为".")
  • 使用%00截断符绕过白名单:WINDOWS/SchedLgU.txt%00.js

2. 测试示例

http://www.test.com/my.jsp?file=Windows/system.ini
http://www.test.com/my.jsp?file=%2e./...%2fWindows/system.ini
http://192.168.32.163/view.php?page=%20.etc/passwd
http://www.target.com/%c0%ae%c0%ae/%c0%ae%c0%ae/foo/bar

五、漏洞修复方案

1. 全局过滤关键字

private String fileNameValidate(String str) {
    String fileNameListStr = "..,~,/,\\,%"; // 请求体中不能携带的关键字
    if(null!=fileNameListStr && !"".equals(fileNameListStr)) {
        str = str.toLowerCase();
        String[] badStrs = fileNameListStr.split(",");
        for (int i = 0; i < badStrs.length; i++) {
            if (str.indexOf(badStrs[i]) >= 0) {
                return badStrs[i];
            }
        }
    }
    return null;
}

2. 正则表达式过滤

private static Pattern FilePattern = Pattern.compile("[\\s\\\\/:\\*\\?\\\"<>\\|]");
public static String filenameFilter(String str) {
    return str==null?null:FilePattern.matcher(str).replaceAll("");
}

3. 白名单验证

public class CommUtils {
    private static final String patternString = "^[a-zA-Z\\d-_\\.]+$";
    private static final String patternString1 = "../,..\\,~,/,\\,%";
    
    public static String filePathFilter(String filepath) {
        final String[] split = patternString1.split(",");
        for(String s : split) {
            filepath = filepath.replace(s,"");
        }
        if(filepath.matches(patternString)) {
            return filepath;
        }
        return null;
    }
}

4. 严格输入验证

public class CleanPath {
    public static String cleanString(String aString) {
        if (aString == null) return null;
        String cleanString = "";
        for (int i = 0; i < aString.length(); ++i) {
            cleanString += cleanChar(aString.charAt(i));
        }
        return cleanString;
    }
    
    private static char cleanChar(char aChar) {
        // 0-9
        for (int i = 48; i < 58; ++i) {
            if (aChar == i) return (char) i;
        }
        // 'A'-'Z'
        for (int i = 65; i < 91; ++i) {
            if (aChar == i) return (char) i;
        }
        // 'a'-'z'
        for (int i = 97; i < 123; ++i) {
            if (aChar == i) return (char) i;
        }
        // 其他合法字符
        switch (aChar) {
            case '/': return '/';
            case '.': return '.';
            case '-': return '-';
            case '_': return '_';
            case ' ': return ' ';
        }
        return '%';
    }
}

5. 受信任目录白名单

public static void main(String[] args) {
    File file = new File(args[0]);
    if (!isInSecureDir(file)) {
        throw new IllegalArgumentException();
    }
    String canonicalPath = file.getCanonicalPath();
    if (!canonicalPath.equals("/img/java/file1.txt") && 
        !canonicalPath.equals("/img/java/file2.txt")) {
        // 无效文件,处理错误
    }
    FileInputStream fis = new FileInputStream(f);
}

6. Web服务器配置

  • IIS:删除可执行虚拟目录或关闭目录浏览
  • Apache:修改httpd.conf文件,将"Options Indexes FollowSymLinks"中的"Indexes"删除

六、参考链接

  1. https://blog.csdn.net/qq_41085151/article/details/113525348
  2. https://www.cnblogs.com/zhangruifeng/p/16077916.html
  3. https://www.cnblogs.com/jayus/p/11423769.html
  4. https://www.cnblogs.com/macter/p/16181588.html
  5. ofcms环境搭建: https://forum.butian.net/share/1229
Java代码审计之目录遍历漏洞详解 一、目录遍历漏洞原理 目录遍历漏洞(Path Traversal)是指通过用户输入参数,后端直接将参数拼接到指定路径下读取用户文件名时,由于对用户输入参数控制不严格,攻击者可以利用特殊字符跳转到服务器敏感目录,读取或操作敏感文件。 风险影响 读取服务器密码文件 访问程序数据库 获取Redis等核心配置文件 覆盖受保护的文件或目录 二、人工代码审计关键点 1. 审计关键字 需要重点关注以下Java关键字: 2. 审计关键类和函数 3. 审计要点 检查对用户传递的文件对象/文件名/文件路径的处理 验证是否限制了可操作文件的路径、文件类型、文件所有者 检查是否排除了敏感文件 检查getPath()、getAbsolutePath()的路径判断逻辑 搜索安全策略配置文件中的权限设置: permission Java.io.FilePermission grant 三、常见漏洞代码示例 1. 未过滤文件路径 2. 直接使用上传文件名 3. ZIP解压路径覆盖 四、渗透测试方法 1. 常见测试方式 尝试使用路径跳转符: ../ 、 ..\ 、 %2e%2e%2f 、 %2e%2e%5c 、 %20 、 %0a 等 在Unix系统中尝试使用换行符: etc/passwd%0a.jpg Java特定绕过: %c0%ae (解析为".") 使用 %00 截断符绕过白名单: WINDOWS/SchedLgU.txt%00.js 2. 测试示例 五、漏洞修复方案 1. 全局过滤关键字 2. 正则表达式过滤 3. 白名单验证 4. 严格输入验证 5. 受信任目录白名单 6. Web服务器配置 IIS :删除可执行虚拟目录或关闭目录浏览 Apache :修改httpd.conf文件,将"Options Indexes FollowSymLinks"中的"Indexes"删除 六、参考链接 https://blog.csdn.net/qq_ 41085151/article/details/113525348 https://www.cnblogs.com/zhangruifeng/p/16077916.html https://www.cnblogs.com/jayus/p/11423769.html https://www.cnblogs.com/macter/p/16181588.html ofcms环境搭建: https://forum.butian.net/share/1229