文件访问类漏洞

通常把这类漏洞归为一个类型,因为产生漏洞的原因都是因为程序对文件或目录访问控制不严、程序内部逻辑错误导致的任意文件或目录恶意访问漏洞。

1. 任意文件读取

任意文件读写漏洞即因为没有验证请求的资源文件是否合法导致的,此类漏洞在Java中有着较高的几率出现,任意文件读取漏洞原理很简单,但一些知名的中间件:Weblogic、Tomcat、Resin又或者是主流MVC框架:Spring MVC、Struts2都存在此类漏洞。

示例 - 存在恶意文件读取漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileInputStream" %>
<%@ page import="java.io.IOException" %>

<pre>
<%
// 获取参数并进行基本验证
String fileName = request.getParameter("name");
// 构建文件路径
String basePath = application.getRealPath("/");
File file = new File(basePath, fileName); // 使用File的构造函数处理路径拼接

// 验证文件是否存在且是文件
if (!file.exists() || !file.isFile()) {
out.println("文件不存在");
return;
}

FileInputStream in = null;
try {
in = new FileInputStream(file);
int tempbyte;
while ((tempbyte = in.read()) != -1) {
out.write(tempbyte);
}
} catch (IOException e) {
out.println("读取文件时发生错误: " + e.getMessage());
e.printStackTrace();
} finally {
// 确保资源关闭
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
%>
</pre>

1.1 目录任意文件读取漏洞测试

攻击者通过传入恶意的name参数可以读取服务器中的任意文件:http://localhost:8080/s1/?name=index.jsp,如下图:

2. 写文件

示例 - 存在恶意文件写入漏洞的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileOutputStream" %>
<%@ page import="java.io.IOException" %>

<%
// 获取参数
String fileName = request.getParameter("f");
String content = request.getParameter("c");

// 参数验证
if (fileName == null || fileName.trim().isEmpty() ||
content == null) {
out.println("错误:文件名和内容参数不能为空");
return;
}

String baseDir = application.getRealPath("/");
File file = new File(baseDir, fileName);
FileOutputStream fos = null;

try {
// 写入文件
fos = new FileOutputStream(file);
fos.write(content.getBytes("UTF-8")); // 指定字符编码
fos.flush();

// 输出结果
out.println("文件路径:" + file.getAbsolutePath() + "<br>");
out.println("文件是否存在:" + file.exists());
} catch (IOException e) {
out.println("写入文件时发生错误:" + e.getMessage());
e.printStackTrace();
} finally {
// 确保资源关闭
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
%>

2.1 跨目录写入文件测试

3. 删除文件

3.1 任意文件删除测试

示例 - 存在任意文件删除漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.IOException" %>

<%
// 获取文件参数
String fileName = request.getParameter("file");

// 验证参数是否为空
if (fileName == null || fileName.trim().isEmpty()) {
out.println("错误:文件参数不能为空");
return;
}

String baseDir = application.getRealPath("/");
File targetFile = new File(baseDir, fileName);

// 检查文件是否存在
if (!targetFile.exists()) {
out.println("错误:文件不存在 - " + targetFile.getAbsolutePath());
return;
}

// 6. 执行删除操作
try {
boolean isDeleted = targetFile.delete();
if (isDeleted) {
out.println("成功:文件已删除 - " + targetFile.getAbsolutePath());
} else {
out.println("失败:无法删除文件 - " + targetFile.getAbsolutePath());
}
} catch (SecurityException e) {
out.println("错误:没有权限删除文件 - " + e.getMessage());
e.printStackTrace();
}
%>

攻击者通过参入file参数即可删除服务器中的任意文件:

3.2 FileSystem任意文件删除测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.lang.reflect.Method" %>

<%
String file = request.getParameter("file");

Method m = Class.forName("java.io.DefaultFileSystem").getMethod("getFileSystem");
m.setAccessible(true);
Object fs = m.invoke(null);

Method m2 = fs.getClass().getMethod("delete", File.class);
m2.setAccessible(true);
out.print(m2.invoke(fs, new File(file)));
%>

攻击者通过反射调用 Filesystem 并执行delete方法,用来绕过对 File 对象 delete方法的防御。

4. 文件/目录复制、移动

示例 - 存在任意文件复制漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.nio.file.Files" %>
<%@ page import="java.nio.file.Path" %>
<%@ page import="java.nio.file.Paths" %>
<pre>
<%
try {
Path path = Files.copy(Paths.get(request.getParameter("source")), Paths.get(request.getParameter("dest")));

out.println(path);
} catch (IOException e) {
e.printStackTrace();
}
%>
</pre>

攻击者传入恶意的source和dest参数可以实现复制任何文件到任意的目录,比如攻击者可以在用户中心上传一张内容为WebShell恶意代码的1.jpg图片文件,然后通过漏洞将1.jpg图片文件,复制到同级目录并更新名称为1.jsp的可解析脚本文件,访问1.jsp文件即可实现控制服务器的目的

在实际环境中,应用系统可能根据需求在配置文件如web.xml中或代码层面如filter设置某些目录(如上传目录、资源目录等)禁止对 .jsp 脚本文件等可执行文件进行解析,因此,攻击者需要将恶意文件移动或复制到其他能够执行的目录进行解析。

5. 重命名文件

示例 - 存在文件名重命名漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%
String fileName1 = request.getParameter("s");
String fileName2 = request.getParameter("d");

File f = new File(fileName1);
File d = new File(fileName2);

f.renameTo(d);

out.println(d + "\t" + d.exists());
%>

攻击者传入恶意的s和d参数即可将文件名为1.txt的文本文件重命名为1.jsp可执行脚本文件,请求:http://localhost:8000/modules/filesystem/file-rename.jsp?s=/tmp/1.txt&d=/tmp/1.jsp

攻击者会使用重命名的方式将(txt、jpg等资源文件)重命名为可执行脚本文件(jsp)来获得webshell从而控制Web应用系统,并绕过某些安全防护机制。常见的攻击手段是在文件上传时,上传包含恶意代码的图片文件,再利用重命名将其转为可执行的脚本文件。