路径表达式中使用的非受控数据¶
ID: cs/path-injection
Kind: path-problem
Security severity: 7.5
Severity: error
Precision: high
Tags:
- security
- external/cwe/cwe-022
- external/cwe/cwe-023
- external/cwe/cwe-036
- external/cwe/cwe-073
- external/cwe/cwe-099
Query suites:
- csharp-code-scanning.qls
- csharp-security-extended.qls
- csharp-security-and-quality.qls
访问由用户控制的路径可能允许攻击者访问意外的资源。这可能导致敏感信息泄露或被删除,或者攻击者能够通过修改意外文件来影响行为。
从用户控制的数据简单构建的路径可能是绝对路径,或者可能包含意外的特殊字符,例如“..”。此类路径可能指向文件系统上的任何位置。
建议¶
在使用用户输入构造文件路径之前对其进行验证。
常见的验证方法包括检查规范化路径是否是相对路径且不包含任何“..”组件,或者检查路径是否包含在安全文件夹中。您应该使用哪种方法取决于应用程序如何使用路径,以及路径是否应该是单个路径组件。
如果路径应该是单个路径组件(例如文件名),则可以检查输入中是否存在任何路径分隔符(“/”或“\”)或“..”序列,如果找到任何内容,则拒绝输入。
请注意,删除“../”序列_不够_,因为输入可能仍然包含路径分隔符后跟“..”。例如,如果仅删除“../”序列,则输入“…/…//”仍将导致字符串“../”。
最后,最简单(但限制最多)的选择是使用安全模式的允许列表,并确保用户输入与其中一种模式匹配。
示例¶
在此示例中,从 HTTP 请求中读取用户提供的文件名,然后使用该文件名访问文件并将其发送回用户。但是,恶意用户可以在文件系统的任何位置输入文件名,例如“/etc/passwd”或“../../../etc/passwd”。
using System;
using System.IO;
using System.Web;
public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
string filename = ctx.Request.QueryString["path"];
// BAD: This could read any file on the filesystem.
ctx.Response.Write(File.ReadAllText(filename));
}
}
如果输入应该只是一个文件名,则可以检查它是否不包含任何路径分隔符或“..”序列。
using System;
using System.IO;
using System.Web;
public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
string filename = ctx.Request.QueryString["path"];
// GOOD: ensure that the filename has no path separators or parent directory references
if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
{
ctx.Response.StatusCode = 400;
ctx.Response.StatusDescription = "Bad Request";
ctx.Response.Write("Invalid path");
return;
}
ctx.Response.Write(File.ReadAllText(filename));
}
}
如果输入应该在特定目录内,则可以检查解析后的路径是否仍然包含在该目录内。
using System;
using System.IO;
using System.Web;
public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
string filename = ctx.Request.QueryString["path"];
string user = ctx.User.Identity.Name;
string publicFolder = Path.GetFullPath("/home/" + user + "/public");
string filePath = Path.GetFullPath(Path.Combine(publicFolder, filename));
// GOOD: ensure that the path stays within the public folder
if (!filePath.StartsWith(publicFolder + Path.DirectorySeparatorChar))
{
ctx.Response.StatusCode = 400;
ctx.Response.StatusDescription = "Bad Request";
ctx.Response.Write("Invalid path");
return;
}
ctx.Response.Write(File.ReadAllText(filename));
}
}