Situación actual: PHP2Shell. Dos vulnerabilidades críticas de PHP (CVSS 9.2) permiten ejecutar código malicioso sin autenticación. Parches desde el 21/10/2026.Qué hacer ahora →

Evitar el Server-Side Request Forgery (SSRF) en PHP

Cómo evitan los desarrolladores que una aplicación PHP consulte sistemas internos por encargo de terceros: listas de permitidos, restricción de protocolos, comprobación de la dirección resuelta y límites en la red.

Actualizado 6 min de lecturaDesarrollo seguro

En resumen: para evitar SSRF en PHP, su aplicación solo debe recuperar direcciones incluidas en una lista de permitidos fija, exclusivamente por HTTPS y sin seguir redirecciones. La dirección IP resuelta se comprueba frente a rangos privados y reservados y después se fija para la conexión. Un cortafuegos para el tráfico saliente hace que los sistemas internos sigan siendo inalcanzables incluso ante un error en el código.

Cuando el propio servidor hace peticiones para terceros

Vistas previas de enlaces en un chat, importación de una imagen de producto mediante URL, webhooks hacia sistemas de socios, un lector de feeds en el CMS: muchas aplicaciones PHP recuperan direcciones que ha indicado un usuario. Es una función útil y, al mismo tiempo, una puerta hacia dentro.

En el Server-Side Request Forgery, abreviado SSRF, un atacante consigue que la aplicación envíe una petición a un destino de su elección. La clave: la petición procede del propio servidor. Alcanza así sistemas que están cerrados desde fuera, como bases de datos, interfaces de administración en la red interna, servicios que solo escuchan en localhost o los servicios de metadatos de los proveedores de nube.

En el OWASP Top 10 de 2021, SSRF era una categoría propia. En proyectos PHP, la vulnerabilidad aparece con frecuencia donde alguien ha construido deprisa una función de «añadir imagen mediante URL».

SSRF en PHP: qué funciones recuperan direcciones

No solo cURL recupera direcciones. Debería vigilar estos puntos:

Función o ámbito Por qué es relevante
cURL (curl_exec()) La vía más frecuente, son posibles muchos protocolos
file_get_contents(), fopen(), copy() con URL Usan wrappers de URL cuando allow_url_fopen está activo
getimagesize() y bibliotecas de imagen Pueden cargar archivos remotos
Procesamiento de XML, clientes SOAP En ciertas circunstancias cargan recursos externos
Bibliotecas HTTP como Guzzle Se basan en cURL o en streams, mismas reglas

Si su aplicación no necesita abrir archivos remotos con funciones de archivo, desactive allow_url_fopen en el servidor. Un tema relacionado, la inclusión de archivos mediante rutas procedentes de la petición, lo trata la guía Evitar la inclusión local y remota de archivos.

La defensa en cinco pasos

OWASP recomienda una combinación de medidas que, por separado, podrían eludirse, pero que juntas resisten:

  1. Lista de permitidos en lugar de URL libre: lo mejor es que el usuario solo elija entre destinos conocidos, o que indique únicamente un identificador y la aplicación construya la dirección.
  2. Permitir solo HTTPS: excluir en cURL otros protocolos como file, gopher o ftp, también para las redirecciones.
  3. No seguir redirecciones: de lo contrario, una dirección permitida puede redirigir a un destino interno.
  4. Comprobar y fijar la dirección resuelta: resolver el nombre de host, comprobar la IP frente a los rangos privados, de loopback y de enlace local, y usar exactamente esa IP para la conexión. De lo contrario, una segunda resolución DNS puede devolver otro destino.
  5. Limitar la red: hacia fuera, el servidor web solo debe poder alcanzar los destinos que necesita.

Un patrón seguro con cURL

El ejemplo siguiente aplica los pasos 1 a 4 para IPv4. Los nombres de host y el flujo son ejemplos; para IPv6 rigen las mismas comprobaciones.

<?php
declare(strict_types=1);

function ladeVonPartner(string $url): string
{
    $teile = parse_url($url);
    if (!is_array($teile) || ($teile['scheme'] ?? '') !== 'https'
        || isset($teile['user']) || isset($teile['pass']) || isset($teile['port'])) {
        throw new InvalidArgumentException('Adresse nicht erlaubt.');
    }

    $host = strtolower($teile['host'] ?? '');
    $erlaubt = ['api.partner.example', 'bilder.partner.example'];
    if (!in_array($host, $erlaubt, true)) {
        throw new InvalidArgumentException('Ziel nicht erlaubt.');
    }

    $ip = gethostbyname($host);
    $oeffentlich = filter_var($ip, FILTER_VALIDATE_IP,
        FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
    if ($oeffentlich === false) {
        throw new RuntimeException('Ziel löst auf eine nicht erlaubte Adresse auf.');
    }

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER  => true,
        CURLOPT_FOLLOWLOCATION  => false,
        CURLOPT_PROTOCOLS       => CURLPROTO_HTTPS,
        CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
        CURLOPT_RESOLVE         => [$host . ':443:' . $ip],
        CURLOPT_CONNECTTIMEOUT  => 3,
        CURLOPT_TIMEOUT         => 10,
    ]);
    $antwort = curl_exec($ch);
    if ($antwort === false) {
        throw new RuntimeException('Abruf fehlgeschlagen.');
    }
    return $antwort;
}

Algunos detalles son importantes. gethostbyname() devuelve el nombre de host cuando hay un error; la posterior comprobación de la IP lo detecta. Según el manual de PHP, los flags FILTER_FLAG_NO_PRIV_RANGE y FILTER_FLAG_NO_RES_RANGE excluyen, entre otros, los rangos 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8 y 169.254.0.0/16. Desde PHP 8.2 existe además FILTER_FLAG_GLOBAL_RANGE, que solo admite direcciones válidas globalmente. CURLOPT_RESOLVE hace que cURL use exactamente la dirección comprobada, manteniendo el nombre de host y la verificación del certificado.

Otros dos puntos limitan lo que un atacante aprende de una vulnerabilidad SSRF. No devuelva al usuario sin filtrar las respuestas y los mensajes de error del destino consultado: ya la diferencia entre «conexión rechazada» y «tiempo de espera agotado» revela qué servicios internos existen. Y limite la duración y el tamaño de la respuesta, para que la función no sirva para sobrecargar el propio servidor. Los webhooks hacia direcciones que introducen los propios clientes conviene enviarlos desde una cola y no durante la petición en curso.

Importante

Si su aplicación debe realmente recuperar direcciones arbitrarias, por ejemplo para vistas previas de enlaces, traslade esa función a un servicio separado sin acceso a la red interna. Entonces una lista de permitidos no es posible, y la separación de red es aún más importante.

La capa de red como red de seguridad

Las comprobaciones en el código pueden tener lagunas, por ejemplo cuando dos bibliotecas descomponen una URL de forma distinta. OWASP aconseja tratar esas discrepancias como rechazo y limitar además a nivel de red. Hacia fuera, un servidor web suele necesitar solo unos pocos destinos: orígenes de paquetes, proveedores de pago y de envío, relé de correo, determinadas interfaces.

Un cortafuegos para el tráfico saliente ayuda por partida doble. Detiene las peticiones SSRF hacia la red interna y dificulta, tras una intrusión, por ejemplo a través de una versión de PHP sin parchear, la descarga de más herramientas. Cómo construir las reglas lo describe Cortafuegos para el servidor web: abrir solo los puertos necesarios. Qué conexiones establece realmente un servidor lo muestra Comprobar las conexiones salientes de un servidor.

En entornos de nube forma parte de ello la variante reforzada del servicio de metadatos. Para AWS, OWASP cita el cambio a IMDSv2 y la desactivación de IMDSv1.

Localizar SSRF en el código existente

Un buen punto de partida es buscar los lugares en los que las funciones de recuperación reciben valores de la petición:

grep -rnE "(curl_init|file_get_contents|fopen|getimagesize|copy)\s*\(" src/ --include="*.php" | grep -E '\$_(GET|POST|REQUEST)|\$url'
grep -rn "CURLOPT_FOLLOWLOCATION" src/ --include="*.php"

Los resultados son tareas de revisión. Debería examinar con especial detenimiento las funciones que usan los administradores: importaciones, ajustes de webhooks, comprobaciones de actualización de plugins.

Se pasan por alto con facilidad los generadores de PDF que convierten HTML con imágenes u hojas de estilo incrustadas en documentos. Si una parte de ese HTML procede de entradas del usuario, por ejemplo en facturas con un logotipo de libre elección, el generador puede recuperar direcciones que determina el usuario. Revise allí los ajustes para la carga de recursos remotos y desactívela si no se necesita.

¿Cómo ayuda PHP SOS en concreto?

  • Encontramos todos los puntos en los que su aplicación recupera direcciones, también en plugins y bibliotecas.
  • Construimos una función de recuperación central con lista de permitidos, restricción de protocolos y comprobación de IP.
  • Definimos reglas para el tráfico saliente y protegemos los servicios de metadatos en entornos de nube.
  • Comprobamos la implementación desde fuera y documentamos los destinos permitidos.

Una revisión independiente de su aplicación la describe Auditoría de seguridad y prueba de penetración.

Preguntas frecuentes

¿Qué funciones de PHP pueden usarse indebidamente para SSRF?

Todas las que recuperan una dirección procedente de entradas del usuario: cURL, file_get_contents y fopen con wrappers de URL, funciones de imagen y de XML que cargan archivos remotos, así como bibliotecas para webhooks, feeds o vistas previas de enlaces.

¿Basta con bloquear las direcciones IP internas?

Por sí solo, no. OWASP califica las listas de bloqueo de fáciles de eludir y recomienda listas de permitidos. Además, la aplicación debe usar exactamente la dirección que ha comprobado y no debe seguir redirecciones.

¿Por qué es SSRF especialmente crítico en la nube?

Los proveedores de nube ofrecen a las instancias servicios internos de metadatos, a través de los cuales a veces pueden obtenerse credenciales. Si una aplicación puede enviar peticiones allí, un atacante puede acceder a esos datos a través de ella. Los proveedores ofrecen variantes reforzadas de estos servicios, que deberían estar activadas.

¿Qué aporta un cortafuegos para el tráfico saliente?

Limita a qué destinos puede llegar siquiera el servidor. Aunque falle una comprobación en el código, los sistemas internos siguen siendo inalcanzables. Además, dificulta tras una intrusión la descarga de código malicioso y la fuga de datos.

Fuentes

Contacto inmediato

Cada hora cuenta. Escríbanos ahora.

Describa brevemente el sistema, la versión de PHP y lo que ha observado. Nos pondremos en contacto, aclararemos la situación y trabajaremos en remoto hasta que el incidente quede cerrado y documentado.

Ayuda inmediata