是,DirectoryInfo 和 FileInfo 在 Windows 和 .NET 6+ 的 Linux/macOS 上默认自动解析符号链接与交接点,不加提示;需用 File.GetAttributes() 结合 File.GetLinkTarget() 检测并拦截 symlink,遍历时须手动 BFS/DFS 并用 Path.GetFullPath() 归一化比对沙箱根路径。

符号链接会被 DirectoryInfo 和 FileInfo 自动解析吗?
会,而且默认就解析——这是最危险的起点。C# 的 DirectoryInfo 和 FileInfo 构造函数(以及几乎所有基于它们的 API,比如 GetFiles()、Exists)在 Windows 上默认跟随符号链接(symlink)和目录交接点(junction),不加任何提示。你传入一个指向 C:\secret 的 symlink,它就真去读 C:\secret,完全绕过你本意要操作的路径边界。
- 这不是 bug,是 .NET 对 Windows 文件系统语义的忠实实现
- 在 Linux/macOS 上,.NET 6+ 同样默认跟随 symlink(除非显式禁用)
-
File.GetAttributes()、Directory.GetDirectories()等静态方法也遵循同一行为
所以别假设“我只传了 ./uploads 就安全”——只要里面有个 symlink 指向 ../appsettings.json,你就已经越界了。
如何检测并拒绝符号链接(而不是跟随它)?
核心是绕过高阶封装,直接调用底层 Win32 或跨平台的文件属性检查:用 File.GetAttributes() 判断是否含 FileAttributes.ReparsePoint,再结合 File.GetLinkTarget()(.NET 6+)确认是否为 symlink(而非 junction 或 mount point)。
-
File.GetAttributes(path)返回的值包含ReparsePoint≠ 一定是 symlink;也可能是 junction、volume mount point,甚至 OneDrive 重定向 - 真正区分 symlink 需要
File.GetLinkTarget(path):它只对 symlink 返回目标路径,对 junction 返回null(或抛IOException) - 在 Linux/macOS 上,
GetLinkTarget同样有效,且不会自动解析
示例判断逻辑:
var attr = File.GetAttributes(path);
if ((attr & FileAttributes.ReparsePoint) != 0)
{
try
{
var target = File.GetLinkTarget(path); // .NET 6+
if (target is not null) // 是 symlink,不是 junction
throw new SecurityException($"Symlink detected: {path} → {target}");
}
catch (NotSupportedException) { /* 不支持的文件系统类型 */ }
}遍历目录时怎么防止 symlink 跳出沙箱?
不能依赖 Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories)——它会在内部无条件跟随所有 symlink,根本没机会拦截。
- 正确做法是手动 BFS/DFS 遍历,每进入一个子项前,先做 symlink 检查(如上)
- 对每个
DirectoryInfo实例,检查其FullName是否仍在预期根路径内(用Path.GetRelativePath()+ 字符串前缀判断不保险,因存在..绕过;应使用Path.GetFullPath()归一化后比对) - 注意:即使路径字符串看起来在范围内,也可能因 symlink 导致实际解析后越界(例如
./a/b/../c→/etc/shadow)
关键检查步骤:
- 获取候选路径的
Path.GetFullPath(candidate) - 检查该绝对路径是否以沙箱根的
Path.GetFullPath(sandboxRoot)开头(注意结尾斜杠) - 在检查前,必须先确认
candidate本身不是 symlink;否则归一化后的路径已失真
.NET 版本与平台差异带来的坑
.NET 5 在 Windows 上不提供 File.GetLinkTarget(),只能靠 P/Invoke DeviceIoControl + FSCTL_GET_REPARSE_POINT 解析 reparse data,成本高且易出错;升级到 .NET 6+ 是最简方案。
- Linux 上需确保运行用户有
readlink权限(通常默认有),但容器环境可能被限制 - macOS 的 symlink 行为基本一致,但 HFS+ 与 APFS 对 reparse point 处理略有不同,
GetLinkTarget已封装兼容 -
File.CreateSymbolicLink()创建的链接才受控;第三方工具(如mklink)创建的 junction 无法用GetLinkTarget获取目标,必须额外处理
最常被忽略的一点:即使你全程手动检查 symlink,如果最终用 File.Copy(src, dst, true) 操作一个 symlink,.NET 默认复制的是目标内容,不是链接本身——这会导致意外泄露真实文件。需要显式用 File.Copy(src, dst, false) 并配合 File.CreateSymbolicLink() 手动重建链接。










