必须用 path.getfullpath 归一化路径后再校验:先拼接用户输入与根目录,再调用 path.getfullpath 得到规范绝对路径,最后用 startswith 检查是否以安全根目录开头(结尾加路径分隔符防前缀误匹配)。

用 Path.GetFullPath 校验路径是否超出预期目录
用户传入的文件名(比如通过 URL、表单或配置)如果含 .. 或绝对路径,直接拼接可能读写系统任意位置。光靠字符串替换或正则过滤不可靠——绕过方式太多。
正确做法是:先拼出完整路径,再用 Path.GetFullPath 归一化,然后检查它是否仍在允许根目录之下:
- 调用
Path.GetFullPath后,路径一定是规范化的绝对路径(如C:data..configile.txt→C:configile.txt) - 必须用
StartsWith检查归一化后的路径是否以你设定的“安全根目录”开头(注意结尾加防止前缀匹配误判,如C:safe不能匹配C:safeguard) - 不要用
Path.Combine后直接判断原始字符串——它不处理..,也不做归一化
string userSupplied = "..\..\windows\system32\cmd.exe";
string rootDir = @"C:ppdata";
string candidate = Path.Combine(rootDir, userSupplied);
string fullPath = Path.GetFullPath(candidate); // → C:windowssystem32cmd.exe
if (!fullPath.StartsWith(rootDir + Path.DirectorySeparatorChar, StringComparison.Ordinal))
{
throw new InvalidOperationException("非法路径访问");
}
别用 string.Replace 或正则删 ..
手动清理 .. 看似简单,实则漏洞百出:URL 编码绕过(%2e%2e)、Unicode 点号(..)、混合斜杠(../..)、空字节截断等都能逃逸。
这类“消毒”逻辑在应用层永远追不上攻击变种,且和 .NET 的路径解析行为不一致。
-
Path.GetFullPath是 .NET 运行时内部路径解析的真实实现,它才代表最终操作系统看到的路径 - 所有自定义过滤都只是“预判”,而真实路径解析由
GetFullPath决定,必须以它为唯一权威 - 即使你删了
..,后续Path.Combine仍可能因参数含绝对路径而跳出去(例如Path.Combine("C:\safe", "C:\evil\file.txt")结果仍是C:evilile.txt)
读文件前务必校验路径,写文件更要严格
读文件漏洞常被当成信息泄露,但写文件漏洞更危险——攻击者可覆盖 web.config、写入 WebShell、篡改日志伪造痕迹。
- 读操作至少要防止越界读敏感配置或源码;写操作必须额外禁止写到非数据目录、禁止覆盖关键文件(如检查目标文件是否已存在且不在白名单内)
- 不要依赖文件扩展名黑名单(如禁止
.aspx)——Windows 允许长文件名+短名,web<code>.aspx可能被识别为WEB~1.ASP - 对上传场景,建议用随机文件名+独立存储目录,原始文件名只存数据库,不参与路径构造
File.Exists 和 Directory.Exists 不是安全栅栏
这两个方法只是查询存在性,它们本身不触发路径校验,也不阻止后续操作越权。更糟的是,它们可能被竞态条件利用:你检查 Exists 返回 true,紧接着 File.ReadAllText 却打开另一个同名但不同路径的文件(符号链接、重解析点等)。
- 路径校验必须在任何 I/O 操作之前完成,且基于
Path.GetFullPath的结果 - 避免“先检查后执行”模式(TOCTOU),尤其在多线程或有符号链接的环境里
- 如果必须用符号链接,确保挂载点本身也在你的安全根目录控制下,并启用
FileOptions.OpenReparsePoint显式控制解析行为
Path.GetFullPath 直接拼接或信任原始输入,就等于把文件系统大门钥匙交给了用户。











