C#文件系统沙箱 C#如何限制代码只能在指定目录内操作文件

来源:这里教程网 时间:2026-02-21 17:43:18 作者:

为什么
Directory.GetCurrentDirectory()
不能当沙箱用

很多开发者以为把工作目录切到某个子目录,再用相对路径操作文件,就能限制访问范围。实际上

Directory.SetCurrentDirectory()
是进程级的,且
..
、绝对路径、UNC 路径、符号链接(如果启用)都能绕过——
File.ReadAllText("../../../etc/passwd")
在 Windows 上虽不生效,但在 .NET 6+ 启用
System.IO.EnableUnsafeIO
或跨平台部署时风险真实存在。

真正可控的方式是:所有路径必须显式校验,且禁止解析为指定根目录之外的物理路径。

Path.GetFullPath()
+ 根目录白名单做路径规范化校验

核心逻辑是:把用户传入的任意路径(相对/绝对/含

..
)转成绝对路径,再判断是否以沙箱根目录为前缀,且不跨越边界(避免软链接逃逸需额外处理)。

string sandboxRoot = Path.GetFullPath(@"C:sandbox");
—— 必须用
GetFullPath
消除歧义
对每个待操作路径调用
Path.GetFullPath(userInput, sandboxRoot)
,第二个参数作为基准目录处理相对路径
检查结果是否以
sandboxRoot + Path.DirectorySeparatorChar
开头(注意末尾斜杠!否则
C:sandbox2
会被误判为合法)
拒绝含
:\
://
、UNC(
\servershare
)或空驱动器号的路径(如
"D:file.txt"
是相对当前 D 盘目录,不安全)

示例校验函数:

bool IsInSandbox(string userInput, string sandboxRoot)
{
    try
    {
        var fullPath = Path.GetFullPath(userInput, sandboxRoot);
        // 确保 sandboxRoot 以分隔符结尾,避免前缀误匹配
        var normalizedRoot = sandboxRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) 
                                  + Path.DirectorySeparatorChar;
        return fullPath.StartsWith(normalizedRoot, StringComparison.Ordinal) 
            && !fullPath.Contains(Path.VolumeSeparatorChar); // 排除 "C:xxx" 这类盘符相对路径
    }
    catch
    {
        return false;
    }
}

拦截
FileStream
File
类的底层调用

仅靠路径校验不够——用户可能直接 new

FileStream(@"C:windowssystem32hosts", FileMode.Open)
绕过你的封装。需要在关键 I/O 入口统一拦截:

不直接暴露
File
Directory
静态方法,封装成
SandboxFile.Read...
等方法,内部强制走校验
若需支持
FileStream
构造,重载时要求传入预校验后的
string safePath
,禁止接受原始字符串
对第三方库调用(如
XmlSerializer
JsonSerializer
的文件读写),必须传入
Stream
实例而非路径——自己用
File.OpenRead(safePath)
创建后传入
警惕
Assembly.LoadFile()
AppDomain.CreateDomain()
等可能触发文件加载的 API,它们不走你封装的路径校验

Windows 上的硬隔离:用
JobObject
限制进程文件句柄

纯托管层校验可被反射、动态代码或 P/Invoke 绕过。若需更强保障(如运行不受信脚本),需结合系统级隔离:

CreateJobObject
+
SetInformationJobObject
设置
JobObjectBasicLimitInformation
,开启
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
关键的是调用
AssignProcessToJobObject
把子进程(如编译后的 Roslyn 脚本进程)绑定到 Job 中
配合
JobObjectSecurityLimitInformation
禁用
JOB_OBJECT_SECURITY_RESTRICTED_TOKEN
,并设置最小权限令牌(去掉 SeBackupPrivilege 等)
注意:.NET 进程默认无法创建 JobObject,需以
SeCreateJobPrivilege
权限运行,且 Job 对象本身需设 DACL 防止被外部进程关联

这层机制不替代路径校验,而是兜底——即使代码绕过 C# 层校验,系统也会在打开越界文件时返回

ERROR_ACCESS_DENIED

路径校验容易漏掉符号链接和驱动器相对路径,JobObject 配置稍有不慎会导致子进程立即退出;两者必须配合使用,且沙箱根目录本身权限也要设为仅当前用户可遍历(

DirectorySecurity
控制)。

相关推荐

热文推荐