verifique la url solicitante

9

Usando WP 4.8.2

¿Cuál es la mejor manera de verificar la URL solicitante al procesar una solicitud con la API de resto?

Por ejemplo, un sitio recibe una solicitud y desea verificar si proviene de una URL 'permitida'. Y fallar si la URL no está permitida.

Esto no funciona:

function my_check_request_url( $request, $url ) {

    $bits = parse_url( $url );

    if ( $bits['host'] != 'example.com' )
       $request = false;

    return $request;

}
add_filter( 'rest_request_from_url', 'my_check_request_url', 10, 2 );
shanebp
fuente
Después de comentar el condicional, la respuesta aún se envía. Así que creo que estoy usando el gancho equivocado.
shanebp
¿Ha comprobado qué aspecto $requesty qué $urlvars se ven a través de var_dumpo similar? Me parece que inspeccionar las entradas y salidas siempre conduce a una respuesta adecuada.
farinspace
2
la URL de referencia se falsifica fácilmente y no se puede utilizar para ningún tipo de seguridad.
Milo
Estamos usando tokens y ssl. También nos gustaría verificar la URL de referencia, independientemente de si puede ser falsificada o no.
shanebp
2
es una API abierta a la web, de qué se habla sobre los árbitros como autenticación y SSL simplemente no es relevante. Probablemente también esté deshabilitando las protecciones CORS ... A menos que esté disponible solo para usuarios registrados, esto no tiene seguridad.
Mark Kaplun

Respuestas:

5

Ese filtro definitivamente no es el que estás buscando. Ese filtro se dispara antes de devolver el resultado, WP_REST_Request::from_url()que parece ser un método de fábrica que solo se usa internamente para manejar incrustaciones.

Una mejor opción es devolver una WP_Errorinstancia en el rest_pre_dispatchfiltro .

Algunas advertencias:

Como mencionó @milo, el árbitro no es confiable y no debe usarse para un control de seguridad.

Además, no se garantiza su configuración.

Con eso fuera del camino, aquí hay un ejemplo de cómo puede usar el rest_pre_dispatchfiltro para hacer que la solicitud falle si proviene de un mal árbitro:

function wpse281916_rest_check_referer( $result, $server, $request ) {
    if ( null !== $result ) {
        // Core starts with a null value.
        // If it is no longer null, another callback has claimed this request.
        // Up to you how to handle - for this example we will just return early.
        return $result;
    }

    $referer = $request->get_header( 'referer' );

    if ( ! $referer ) {
        // Referer header is not set - If referer is required, return a WP_Error instance instead.
        return $result;
    }

    $host = wp_parse_url( $referer, PHP_URL_HOST );

    if ( ! $host ) {
        // Referer is malformed - If referer is required, return a WP_Error instance instead.
        return $result;
    }

    if ( 'mysite.com' !== $host ) {
        // Referer is set to something that we don't allow.
        return new WP_Error(
            'invalid-referer',
            'Requests must contain a valid referer',
            compact( 'referer' )
        );
    }

    // Otherwise we are good - return original result and let WordPress handle as usual.
    return $result;
}
add_filter( 'rest_pre_dispatch', 'wpse281916_rest_check_referer', 10, 3 );
ssnepenthe
fuente
4

Cualquier cosa que reciba del cliente se considera entrada del usuario y no se debe confiar. Como el encabezado se puede manipular y abusar fácilmente, mi sugerencia es no utilizar este método si confía en él para obtener datos confidenciales.

Si las solicitudes provienen de una página, puede tener otro enfoque. De lo contrario, cualquiera puede enviar una solicitud a la API desde ninguna parte y alterar el referente.

Supongamos que tiene un montón de páginas que se filtran como "Permitidas" . Puede crear un nombre solo para estas páginas y luego validarlas en su solicitud.

Si existe un sustantivo y es válido, se permite la solicitud. De lo contrario, bloquéelo.

Jack Johansson
fuente
44
+1 ... es una API ... la suposición de que solo recibe llamadas de los navegadores es ridícula.
Mark Kaplun
Sí, creo que el sustantivo es un mejor enfoque, ya que no existe si alguien envía directamente una solicitud a la API.
Jack Johansson
4

El contestador de @ssnepenthe tiene razón al decir que el gancho que está utilizando no es el correcto en la solicitud entrante.

La información de solicitud está disponible de inmediato para PHP, por lo que puede utilizar el primer enlace disponible para verificarlos. Y si desea hacer esto en el contexto de la API de solicitud, debe usar el primer enlace de una solicitud de API REST. 'rest_pre_dispatch'sugerido por @ssnepenthe está bien, tal vez otra opción podría ser la rest_authentication_errorsque le permitiría devolver un error en caso de que algo esté mal.

Pero Jack Johansson tiene razón al decir que los encabezados HTTP (como el encabezado de referencia utilizado en la respuesta de @ ssnepenthe) no son confiables, ya que el cliente puede cambiarlos fácilmente. Entonces sería como poner un guardia de seguridad frente a una puerta que solo pregunta "¿es seguro dejarte entrar?" a cualquiera que quiera entrar: eso no va a funcionar.

Pero la solución que propuso la respuesta de Jack Johansson (un nonce) tampoco es una solución real: el objetivo principal de nonces es cambiar con el tiempo, y un punto final API público no puede tener cosas que cambien en función del tiempo. Además, los WP nonces son confiables solo cuando hay un usuario conectado, lo que podría no ser el caso para una API pública y si un usuario está conectado, probablemente no haya razón para verificar el dominio entrante: confía en el usuario, no en el máquina de usuario.

¿Entonces lo que hay que hacer?

Bueno, incluso si los encabezados HTTP no son confiables, no toda la información disponible $_SERVERproviene de encabezados.

Normalmente, todos los $_SERVERvalores cuyas claves comienzan con las que comienzan HTTP_provienen de encabezados y deben tratarse como entradas inseguras del usuario .

Pero, por ejemplo, $_SERVER['REMOTE_ADDR']contiene la dirección IP utilizada para la conexión TCP a su servidor, lo que significa que es confiable 1 .

Lo que también significa que:

  • configurar correctamente el servidor para generar el $_SERVER['REMOTE_HOST']valor (por ejemplo, en Apache necesitará HostnameLookups Ondentro de su httpd.conf) ese valor
  • usando gethostbyaddrpara hacer una búsqueda inversa de DNS para resolver el nombre de dominio de la IP almacenada en$_SERVER['REMOTE_ADDR']

usted podría obtener bastante fiable un nombre de host que se puede utilizar para comprobar en contra de una lista blanca (para el código, se puede adaptar el código de aswer @ de ssnepenthe donde debería reemplazar $referer = $request->get_header('referer')con $referer = gethostbyaddr($_SERVER['REMOTE_ADDR'])).

Pero hay un problema .

Si su servidor web está detrás de un proxy inverso (una solución bastante común, en realidad), la conexión TCP al servidor web en realidad la realiza el proxy, por $_SERVER['REMOTE_ADDR']lo que será la IP del proxy y no la IP del cliente que envió la solicitud originalmente.

La IP de solicitud original en tales casos generalmente está disponible como $_SERVER['HTTP_X_FORWARDED_FOR'], pero ser uno de esos $_SERVERvalores que comienzan con HTTP_ella no es realmente confiable.

Por lo tanto, si su servidor web está detrás de un proxy inverso 2, incluso el $_SERVER['REMOTE_ADDR']no sería útil para dicha protección y una lista blanca basada en el dominio solo podría implementarse en el nivel del proxy.

En resumen, una solución confiable para la seguridad de los puntos finales API debe implementarse utilizando algún mecanismo de autenticación real (por ejemplo, oAuth) o debe hacerse actuando directamente en la configuración del servidor y no a nivel de aplicación.


Notas

1 Bueno, en teoría, podría romperse si alguien piratea su ISP o si un atacante actúa desde dentro de su LAN, en ambos casos hay muy poco que pueda hacer para estar seguro.

2 Si no sabe si está detrás de un proxy inverso, puede enviar una solicitud desde su PC local y verificar si $_SERVER['REMOTE_ADDR']el servidor coincide con la IP local de la PC y también si $_SERVER['HTTP_X_FORWARDED_FOR']está presente y si coincide con la IP local de la PC.

gmazzap
fuente
El OP está tratando de obtener el referente, por lo que asumí que quiere hacerlo en una página, sin hacer ping directamente a la API.
Jack Johansson
@JackJohansson en realidad el OP nunca mencionó al árbitro :) Dicen que quieren "verificar si proviene de una URL 'permitida'", lo que parece que están buscando incluir en la lista blanca el punto final de la API para dominios específicos, y también el fragmento de código en OP da el mismo idea para mi.
gmazzap