FlawOpen

flawopen.com/Path traversal/Python os.path.join()

¿os.path.join() de Python evita el path traversal?

CWE-22: Path TraversalPágina de referencia
Respuesta breve

No. Imagina la base como una habitación: .. atraviesa habitaciones principales; una ruta absoluta es una dirección nueva que hace que os.path.join() descarte la habitación inicial. Resuelve la ruta final y confirma que siga dentro del directorio permitido antes de abrirla.

VULNERABLE
import os

base_dir = "/srv/uploads"
user_filename = "../../outside/demo.txt"
path = os.path.join(base_dir, user_filename)
# Relative traversal can leave the base.

path = os.path.join(base_dir, "/outside/demo.txt")
# An absolute segment discards base_dir.
SECURE — resolve and check containment
import os

def resolve_under_base(base_dir, user_filename):
    if not isinstance(user_filename, str) or not user_filename:
        raise ValueError("Invalid filename")

    base = os.path.realpath(base_dir)
    candidate = os.path.realpath(
        os.path.join(base, user_filename)
    )

    try:
        inside = os.path.commonpath((base, candidate)) == base
    except ValueError:  # e.g. different Windows drives
        inside = False

    if not inside or candidate == base:
        raise ValueError("Invalid filename")
    return candidate

Dos comportamientos de unión que debes considerar

.. puede sacar una ruta relativa de su directorio previsto. La documentación de Python indica que una ruta absoluta descarta los componentes anteriores; en Windows, las reglas de unidad y raíz difieren de POSIX. pathlib.Path(base) / user_input también descarta lo anterior ante una ruta absoluta. Unir y normalizar no autoriza el acceso.

Por qué commonpath() es más seguro que los prefijos

Resuelve la base confiable y la ruta candidata antes de compararlas. os.path.commonpath() compara componentes, no prefijos de caracteres, y lanza ValueError ante raíces o unidades incompatibles; rechaza esos casos. realpath() resuelve enlaces simbólicos existentes al comprobar, pero no evita que cambien antes de abrir el archivo. Si terceros pueden modificar el árbol, usa descriptores y controles no-follow adecuados a la plataforma.

Preguntas frecuentes

¿pathlib se comporta igual?

Sí. El operador / de Path descarta componentes anteriores cuando la siguiente ruta es absoluta. Resuelve el resultado y verifica que esté dentro de la base permitida.

¿realpath() elimina las condiciones de carrera?

No. Resuelve enlaces existentes al validar; otro proceso puede cambiarlos antes de abrir el archivo. Restringe escrituras o usa operaciones relativas a descriptores con no-follow cuando estén disponibles.

Referencias

Véase también Path traversal (CWE-22): cómo funciona y cómo evitarlo en todos los lenguajes — flawopen.com ¿Qué es una vulnerabilidad Zip Slip? Node.js path.join() no evita Path Traversal PHP include() y rutas controladas por el usuario