Volver a la wiki

Servicio de seguridad en filesystem (safe-fs)

Descripción general

Helper centralizado de defensa contra vulnerabilidades de manejo de archivos en el módulo de importación/restauración de la librería de racks (dominio racks/api).

Ubicación: racks/utils/safe_fs.py Líneas de código: 35 (3 funciones públicas + tests de cobertura) Introducción: s104 (Auditoría Suprema — dominio racks)

Componentes públicos

is_valid_session_id(session_id: str) -> bool

Valida que el session_id sea un UUID4 bien formado (32 caracteres hex o formato estándar 8-4-4-4-12 con guiones).

Propósito: Evitar path traversal inyectando secuencias como ../.. en el identificador de sesión.

Firma:

def is_valid_session_id(session_id) -> bool:
    """True only for a well-formed uuid session id (no path separators, no '..'))."""
    return isinstance(session_id, str) and bool(_SESSION_ID_RE.match(session_id))

Patrón regex: [0-9a-fA-F]{32} (hex compacto) o [0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12} (RFC 4122).

Consumido por:


is_within_directory(base_dir: str, target_path: str) -> bool

Resuelve rutas canonicalizadas (realpath) y verifica que el destino esté dentro del directorio base (no escape con .. o enlaces simbólicos).

Propósito: Guard contra Zip Slip (vulnerabilidad de archivos ZIP que escriben fuera del destino) y, desde v1.92.0, guard genérico de confinamiento para cualquier ruta controlada por el cliente (backups, imports).

Firma:

def is_within_directory(base_dir, target_path) -> bool:
    """True if *target_path* resolves to a location inside *base_dir*."""
    base = os.path.realpath(base_dir)
    target = os.path.realpath(target_path)
    return target == base or target.startswith(base + os.sep)

Consumido por:


safe_extract_zip(zip_ref: zipfile.ZipFile, dest_dir: str) -> None

Itera sobre todos los miembros del archivo ZIP y rechaza cualquiera cuya ruta destino no esté dentro de dest_dir (según is_within_directory). Solo después de pasar todos los controles ejecuta zipf.extractall().

Propósito: Prevenir Zip Slip validando TODOS los members antes de extraer ninguno.

Firma:

def safe_extract_zip(zip_ref, dest_dir) -> None:
    """Extract *zip_ref* into *dest_dir*, rejecting members that escape it (Zip Slip)."""
    for member in zip_ref.namelist():
        if not is_within_directory(dest_dir, os.path.join(dest_dir, member)):
            raise ValueError(f"Unsafe path in archive: {member!r}")
    zip_ref.extractall(dest_dir)

Lanza: ValueError si algún member es peligroso.

Consumido por:


Vulnerabilidades cerradas

BugFunción afectadaGuardaDetalle
Path traversal (../..)cleanup_session()is_valid_session_id()session_id no validado se interpolaba en MEDIA_ROOT/temp/<session_id> y pasaba a shutil.rmtree() → borrado arbitrario de directorios. (s104)
Zip Sliprestore_full(), analyze_restore_backup()safe_extract_zip() + is_within_directory()Members de ZIP sin sanear podían escribir fuera del directorio de extracción. (s104)
Path traversal cross-org, shutil.move destructivoconfirm_visio_import()is_within_directory(temp_dir, …)Guard previo (s220) confinaba a MEDIA_ROOT entero; un image_path apuntando a otra org resolvía dentro de ese prefijo y se movía (destruyendo el origen). Estrechado a temp_dir de la sesión. (v1.92.0, #478)
Path traversal, escritura fuera de sesiónconfirm_restore_backup()is_within_directory(temp_dir, …)image_file del manifest.json subido por el usuario, sin confinar. (v1.92.0, #478)
Lectura/escritura arbitraria_ensure_media_file() (restore_domains.py)is_within_directory(media_root, …) en origen y destinorel_new/rel_old del JSON del backup, sin confinar ninguno de los dos extremos. (v1.92.0, #478)

Tests


Véase también

Subir