flawopen.com/path-traversal/Csharp

● CWE-918 · Haute
Recherche · FlawOpen

Path Traversal in C#

How Path.Combine discards root paths in C#, and how to verify boundaries using Path.GetFullPath and StringComparison.OrdinalIgnoreCase.

💡 Explication en Langage Simple (ELI5)

Imaginez un lecteur de carte d'hôtel programmé pour n'ouvrir que les chambres du 2e étage. Si un client tape '../../master-safe' sur le digicode, la serrure vulnérable remonte le couloir et déverrouille le coffre-fort principal du gérant.

Concepts Clés et Termes

Web Application Security
Composant d'architecture clé affecté par CWE-918.
CWE-918
Classification standard Common Weakness Enumeration (CWE) pour path-traversal-csharp.
Defense-in-Depth
Vérification d'ingénierie multicouche et isolation des limites à l'exécution.

Déroulement de l'Attaque Étape par Étape

Step 1

Saisie de Chemin de Fichier par le Client

Un point de terminaison accepte un nom de fichier ou un identifiant de ressource via un paramètre HTTP.

Step 2

Injection de Séquence de Traversée de Répertoire

L'attaquant injecte des séquences relatives comme '../', '..%2f' ou des chemins absolus non autorisés.

Step 3

Contournement de la Limite du Système de Fichiers

Le backend concatène naïvement le fichier sans vérifier le chemin canonique ni restreindre le répertoire racine.

Step 4

Divulgation ou Écriture Arbitraire de Fichiers

Le runtime ouvre et renvoie des fichiers système critiques (/etc/passwd, secrets d'API) à l'attaquant.

Code Source : Vulnérable vs Sécurisé

✕ IMPLÉMENTATION VULNÉRABLE
// 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");
    }
}
✓ PATCH SÉCURISÉ ET ROBUSTE
// 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");
    }
}

Liste de Contrôle de Sécurité pour l'Ingénierie

References