Como proteger uploads privados usando firmas y rewrite en PHP en WordPress

Contents

Introducción

Este tutorial explica paso a paso cómo proteger archivos subidos (uploads) privados en WordPress usando URLs firmadas (signatures) y reglas de rewrite / manejo por PHP. El objetivo es evitar el acceso directo a ficheros sensibles y permitir su entrega sólo cuando se valida una firma y condiciones adicionales (caducidad, usuario, IP, etc.). Se cubren alternativas para servir los ficheros (X-Sendfile, X-Accel-Redirect, PHP puro), cómo crear y verificar firmas seguras, cómo ocultar la carpeta de uploads al público y cómo integrar todo en WordPress mediante rewrite rules y hooks.

Arquitectura y consideraciones

  • Almacenar archivos privados: colócalos en una subcarpeta de uploads, por ejemplo wp-content/uploads/private, o mejor fuera del webroot si puedes. Si están en uploads, evita accesos directos con reglas del servidor.
  • Generación de URLs firmadas: crea una URL que incluya ruta, expiración y una firma HMAC con una clave secreta. La firma garantiza integridad y autenticidad.
  • Validación: un punto central (index.php / endpoint WP) valida la firma y condiciones y luego delega la entrega al servidor o a PHP.
  • Entrega eficiente: si el servidor lo soporta, usa X-Sendfile (Apache) o X-Accel-Redirect (Nginx) para que sea el propio servidor quien envie el archivo tras validación reduce carga PHP y mejora rendimiento.
  • Seguridad adicional: limitar expiración, enlazar URL a usuario o IP, proteger la clave secreta (definirla en wp-config.php) y normalizar rutas para evitar path traversal.

Requisitos previos

  • Acceso a los archivos de WordPress (para colocar ficheros o crear la carpeta private).
  • Capacidad para añadir código PHP en un plugin o functions.php (recomendado: plugin propio para gestión).
  • Permisos de configuración del servidor si quieres usar X-Sendfile o X-Accel-Redirect.

Paso 1 — Preparar carpeta segura

Crear una carpeta para archivos privados. Recomendaciones:

  • Preferible fuera del webroot: /var/www/private_uploads
  • Si usas wp-content/uploads/private, bloquea accesos directos mediante .htaccess (Apache) o reglas nginx.

Ejemplo .htaccess para bloquear acceso público (colocar dentro wp-content/uploads/private):

# Para Apache 2.2 y 2.4 (combinado)
ltIfModule mod_authz_core.cgt
  Require all denied
lt/IfModulegt
ltIfModule !mod_authz_core.cgt
  Order allow,deny
  Deny from all
lt/IfModulegt

Si prefieres nginx, ubica la carpeta fuera de la ruta pública o añade una location que deniegue el acceso directo.

Paso 2 — Guardar una clave secreta

Define una clave secreta robusta en wp-config.php (o un sistema KMS en producción). No la publiques ni la controles en repositorios sin protección.

// wp-config.php
define(PRIVATE_UPLOADS_SECRET, pon-aqui-una-clave-muy-larga-y-secreta-ygenerada-aleatoriamente)

Paso 3 — Generar la URL firmada (función PHP)

La función genera una URL con parametros: ruta relativa (a la carpeta privada), timestamp de expiración y la firma HMAC. Aquí hay una función de ejemplo que recibe el ID de attachment o la ruta relativa.

/
  Genera URL firmada para un archivo privado.
 
  @param string relative_path Ruta relativa dentro de la carpeta privada, por ejemplo invoices/2025/archivo.pdf
  @param int    ttl          Tiempo en segundos antes de expirar (por ejemplo 300 = 5 minutos)
  @param intnull user_id    Si se desea atar a un usuario, pasar su ID
  @return string URL pública firmada
 /
function get_protected_upload_url( relative_path, ttl = 300, user_id = null ) {
    secret = defined(PRIVATE_UPLOADS_SECRET) ? PRIVATE_UPLOADS_SECRET : 
    if ( empty(secret) ) {
        return  // no hay clave, no generar
    }

    expires = time()   (int) ttl
    data = relative_path .  . expires
    if ( user_id ) {
        data .=  . (int) user_id
    }

    signature = hash_hmac(sha256, data, secret)
    // URL amigable: /protected////
    parts = array(protected, signature, expires)
    if ( user_id ) {
        parts[] = (int) user_id
    }
    // url-encode para la ruta
    parts[] = rawurlencode( relative_path )

    // Usar site_url() para generar base
    return rtrim(site_url(), /) . / . implode(/, parts)
}

Paso 4 — Rewrite rules en WordPress

Registrar una regla de rewrite y variables de query para capturar signature / expires / user / path. Añade esto en un plugin o functions.php (mejor plugin).

// 1) Añadir query vars
add_filter(query_vars, function(vars){
vars[] = protected_sig
vars[] = protected_exp
vars[] = protected_uid
vars[] = protected_path
return vars
})

// 2) Registrar la regla de rewrite
add_action(init, function(){
add_rewrite_rule(
^protected/([0-9a-fA-F]{64})/([0-9] )/?([0-9] )?/(. )



Acepto donaciones de BAT's mediante el navegador Brave :)



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *