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' 등의 상대 경로 또는 절대 경로 우회 문자열을 삽입합니다.
백엔드가 표준화(Canonical) 경로 검증 없이 기본 디렉토리와 문자열을 결합하여 경계를 이탈합니다.
서버가 /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.