XML文件上传功能在各类业务系统中应用广泛,若未对上传路径做严格校验,攻击者可通过构造包含../的路径字符串,突破预期的上传目录限制,将文件写入系统敏感位置,这就是典型的路径遍历攻击。这类攻击的核心是利用操作系统对../路径的解析规则,向上回溯目录层级,从而绕过预设的存储路径限制。

../路径遍历攻击原理
当应用接收XML文件上传请求时,若直接将用户传入的文件名或路径拼接作为最终存储路径,攻击者就可以在文件名中插入../字符。例如用户传入的文件名为../../etc/passwd.xml,应用若未做处理,拼接后的路径可能指向系统根目录下的敏感文件位置,从而覆盖或写入恶意内容。
常见的攻击场景包括:
- 直接在文件名中插入../字符,尝试回溯目录
- 对../做URL编码、双重编码等绕过简单校验
- 结合XML外部实体注入漏洞,构造特殊的文件路径 payload
防范措施详解
1. 严格校验上传路径与文件名
服务端需要对接受到的路径和文件名做白名单校验,禁止出现../、..等目录回溯相关的字符组合。可以通过正则匹配过滤掉所有包含路径跳转特征的字符串,仅允许字母、数字、下划线、点号等安全字符存在于文件名中。
import java.util.regex.Pattern;
public class PathValidator {
// 正则匹配文件名,仅允许字母、数字、下划线、点号、横杠,禁止路径相关字符
private static final Pattern SAFE_FILE_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_\-\.]+$");
public static boolean isValidFileName(String fileName) {
if (fileName == null || fileName.trim().isEmpty()) {
return false;
}
// 先解码可能存在的URL编码,避免编码绕过
String decodedFileName = java.net.URLDecoder.decode(fileName, java.nio.charset.StandardCharsets.UTF_8);
// 检查是否包含../或..特征
if (decodedFileName.contains("../") || decodedFileName.contains("..\")) {
return false;
}
// 校验文件名是否符合白名单规则
return SAFE_FILE_NAME_PATTERN.matcher(decodedFileName).matches();
}
}
2. 规范化文件路径后再存储
即使做了字符过滤,仍可能存在绕过情况,因此需要对拼接后的完整路径做规范化处理,使用系统提供的路径规范化方法,解析出真实的绝对路径,再判断是否在允许的上传目录范围内。
import os
# 定义允许的上传根目录
UPLOAD_ROOT_DIR = "/var/www/upload/xml/"
def save_upload_file(upload_dir, file_name, file_content):
# 拼接原始路径
raw_path = os.path.join(upload_dir, file_name)
# 规范化路径,解析出真实绝对路径
normalized_path = os.path.normpath(raw_path)
# 判断规范化后的路径是否在允许的上传根目录内
if not normalized_path.startswith(os.path.normpath(UPLOAD_ROOT_DIR)):
raise ValueError("非法上传路径,禁止访问目录外位置")
# 写入文件
with open(normalized_path, "wb") as f:
f.write(file_content)
3. 限制上传目录的权限与访问范围
上传目录的权限需要严格控制,仅授予应用进程必要的写入权限,禁止执行权限。同时可以将上传目录设置为独立的磁盘分区,避免攻击者通过路径遍历写入系统目录后造成更大范围的影响。另外可以为上传的文件生成随机的唯一文件名,避免直接使用用户传入的文件名。
<?php
$uploadDir = '/var/www/upload/xml/';
// 生成唯一随机文件名,避免用户传入的文件名带来风险
$uniqueFileName = md5(uniqid() . time()) . '.xml';
$targetPath = $uploadDir . $uniqueFileName;
// 校验上传文件是否为合法的XML类型
$allowedTypes = ['application/xml', 'text/xml'];
if (!in_array($_FILES['xml_file']['type'], $allowedTypes)) {
die('仅允许上传XML格式文件');
}
// 移动上传文件到目标路径
if (move_uploaded_file($_FILES['xml_file']['tmp_name'], $targetPath)) {
echo '文件上传成功,存储路径为:' . $targetPath;
} else {
echo '文件上传失败';
}
?>
4. 禁用XML外部实体解析
如果XML上传后需要解析内容,需要禁用XML解析器的外部实体加载功能,避免攻击者结合XXE漏洞构造恶意路径,进一步放大路径遍历攻击的危害。以Java为例,禁用外部实体的配置如下:
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.xml.sax.SAXException;
public class SafeXmlParser {
public static DocumentBuilder getSafeDocumentBuilder() throws SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 禁用外部实体
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
return factory.newDocumentBuilder();
}
}
检测与修复建议
开发者可以通过模糊测试的方式,尝试上传文件名包含../、..%2F、%2e%2e%2f等payload的XML文件,观察文件是否被存储到预期目录之外。如果发现漏洞,需要优先上线路径校验和规范化逻辑,同时排查所有文件上传相关接口,避免同类问题重复出现。定期对上传目录做文件扫描,及时发现异常写入的文件,也是降低攻击危害的有效手段。