0x00 前言
在 Java Web 应用程序中,任意文件操作是一个很常见的安全漏洞,它允许攻击者通过操纵应用程序来访问服务器上的文件。这种漏洞通常是由于应用程序对用户输入的文件路径参数处理不当导致的。
一般情况下,可以关注相关的文件操作类,来辅助审计。
-
InputStream
-
File
-
OutputStreaam
-
BufferedInputStream
-
FileInputStream
-
......
根据参数位置,可以分为request请求参数以及请求路径path两种。
request请求参数中的任意文件操作风险比较常见:
而请求路径Path的则比较复杂,根据实现方式可以粗略分为自定义Servlet以及类似 @PathVariable 注解框架自带的特性两种。实际利用时可能会存在一些限制,具体的分析可见https://forum.butian.net/share/2265。
本质上还是因为没有对用户可控的内容进行安全检查,导致了相关的风险。
0x01 常见绕过缺陷
一般情况下,针对任意文件操作风险,一般会采用
白名单
或者
输入验证
的方式进行防护
但是在实际应用系统中,由于涉及到的功能比较复杂,结合框架自身特点等种种特殊条件下,相关的安全措施可能会有绕过的缺陷。下面结合实际审计案例进行梳理,分享一些实际的案例。
1.1 过滤内容不严谨
首先是输入验证,一般情况下主要会对类似
../
的输入进行安全检查。但是在特定场景下这类的措施是不足以抵御相关漏洞的。下面是实际审计中遇到的一些案例。
1.1.1 windows目录穿越符
对于任意文件操作的风险常常会通过过滤类似../进行防护,例如下面的例子:
public static boolean check(String filePath) {
if (StringUtil.isBlank(filePath)) {
return true;
}
return StringUtil.contains(filePath, "../");
}
最简单的攻击者可能尝试使用 Windows 特有的目录穿越符(如
..\
或
%5C
表示反斜杠)来访问上级目录。从而绕过相应的安全措施。
1.1.2 java.net.URL处理特性
在Java中,可以通过使用URL类来实现获取共享文件夹文件的file协议。例如下面的例子:
URL url = new URL("FilePath");
InputStream inputStream = url.openStream();
// 使用 InputStreamReader 和 BufferedReader 包装 InputStream
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
// 逐行读取文件内容
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 关闭 BufferedReader 和 InputStream
reader.close();
inputStream.close();
某些应用程序可能只检查文件后缀。限制只能访问类似png、jpg等图片资源,在一定程度上收敛了风险。实际上可以通过#的方式绕过,URL在解析时会忽略相应的内容:
URL url = new URL("file:/var/log/../../../../etc/passwd#.png");
System.out.println(url.getPath());
对于过滤了
../
的情况,实际上也很好解决,查看java.net的源码,可以看到在解析时做了一层URL解码。结合Spring Controller在解析时自解码一层的特点,可以通过双重URL编码的方式,绕过对应的
../
检查:
1.1.3 与其他安全措施冲突
由于业务系统的复杂性,除了任意文件下载的防护,类似xss、sql注入的防护可能都会通过相关的filter进行处理。也方便管理。在某些情况下可以存在冲突,导致了防护的绕过。下面是一个实际的例子:
在filter中对获取参数内容的方法进行了重写,首先调用cleanAnyFileRead方法将类似
../
输入替换成null,最后使用Jsoup组件进行xss处理,将相关xss内容替换成null:
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return JsoupUtil.clean(cleanAnyFileRead((String) value));
}
private static String cleanAnyFileRead(String value) {
value = value.replaceAll("\\.+/", "");
return value;
}
因为存在先后顺序,这里存在冲突。可以利用最后xss内容替换,在
../
中植入xss poc,来绕过对
../
的检查:
..