flawopen.com/path-traversal/Csharp
How Path.Combine discards root paths in C#, and how to verify boundaries using Path.GetFullPath and StringComparison.OrdinalIgnoreCase.
ホテルの客室カードリーダーが2階の部屋のみを開けるよう制限されている場面を例えに考えてみてください。もし宿泊客がドアのキーパッドに「../../master-safe」と入力すると、不備のある鍵が廊下を抜け出して支配人の金庫を直接解錠してしまいます。
Web Application SecurityCWE-918.CWE-918CWE-918):Standard Common Weakness Enumeration classification for path-traversal-csharp.Defense-in-DepthエンドポイントがHTTPリクエストパラメータからユーザー指定のファイル名やパスを受け取ります。
攻撃者が '../' や '..%2f' などの相対パス記号や絶対パス指定を挿入します。
バックエンドが正規化パスの検証を行わずに文字列を連結し、公開フォルダ外へのアクセスを許容します。
OSが /etc/passwd や設定ファイルなどの重要ファイルを読み取り、攻撃者へ返却します。
// VULNERABLE: Path.Combine discards baseFolder if userInput has a leading slash
using System.IO;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase {
private readonly string _baseFolder = @"C:\App\Storage\Uploads";
[HttpGet("download")]
public IActionResult Download([FromQuery] string file) {
// If file is "\Windows\System32\cmd.exe", basePath is discarded!
// If file is "..\..\App.config", it breaks out of the folder
string targetPath = Path.Combine(_baseFolder, file);
if (!System.IO.File.Exists(targetPath)) return NotFound();
return PhysicalFile(targetPath, "application/octet-stream");
}
}
// HARDENED: Canonicalize path and assert directory prefix containment
using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase {
private readonly string _baseFolder;
public FileController() {
_baseFolder = Path.GetFullPath(@"C:\App\Storage\Uploads");
}
[HttpGet("download")]
public IActionResult Download([FromQuery] string file) {
if (string.IsNullOrWhiteSpace(file)) return BadRequest("Invalid filename");
// 1. Strip leading slashes to prevent Path.Combine root discard
string cleanName = file.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
// 2. Resolve canonical full path
string fullPath = Path.GetFullPath(Path.Combine(_baseFolder, cleanName));
// 3. Strict boundary containment check with DirectorySeparatorChar
string safePrefix = _baseFolder.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
if (!fullPath.StartsWith(safePrefix, StringComparison.OrdinalIgnoreCase)) {
return Forbid("Path traversal attempt detected");
}
if (!System.IO.File.Exists(fullPath)) return NotFound();
return PhysicalFile(fullPath, "application/octet-stream");
}
}
Path.Combine()。Path.GetFullPath() to resolve traversal dots。