verifique o URL solicitante

9

Usando o WP 4.8.2

Qual é a melhor maneira de verificar o URL solicitante ao processar uma solicitação com a rest-api?

Por exemplo, um site recebe uma solicitação e você deseja verificar se veio de um URL 'permitido'. E falhe se o URL não for permitido.

Isso não 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
fonte
Depois de comentar o condicional, a resposta ainda é enviada. Então, acho que estou usando o gancho errado.
Shanebp 5/10
Se você verificou como $requeste os $urlvars se parecem com via var_dumpou similar, acho que a inspeção das entradas e saídas sempre leva a uma resposta adequada.
farinspace
2
o URL de referência é facilmente falsificado e não pode ser usado para nenhum tipo de segurança.
Milo
Estamos usando tokens e ssl. Também gostaríamos de verificar o URL de referência, independentemente de poder ou não ser falsificado.
Shanebp 7/10
2
é uma API aberta para a web, o que é essa conversa sobre árbitros como autenticação e SSL simplesmente não é relevante. Você provavelmente também está desativando as proteções do CORS ... A menos que esteja disponível apenas para usuários logados, isso tem zero de segurança.
Mark Kaplun

Respostas:

5

Esse filtro definitivamente não é o que você está procurando. Esse filtro é acionado antes de retornar o resultado, o WP_REST_Request::from_url()que parece ser um método de fábrica usado apenas internamente para manipular incorporações.

Uma opção melhor é retornar uma WP_Errorinstância no rest_pre_dispatchfiltro .

Algumas advertências:

Conforme mencionado por @milo, o referenciador não é confiável e não deve ser usado para uma verificação de segurança.

Além disso, não é garantido que seja definido.

Com aqueles fora do caminho, aqui está um exemplo de como você pode usar o rest_pre_dispatchfiltro para causar falha na solicitação se for de um referenciador incorreto:

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
fonte
4

Tudo o que você recebe do cliente é considerado como entrada do usuário e não deve ser confiável. Como o cabeçalho pode ser facilmente manipulado e abusado, minha sugestão é não usar esse método se você estiver contando com ele para obter dados confidenciais.

Se os pedidos vierem de uma página, você poderá ter outra abordagem. Caso contrário, qualquer pessoa poderá enviar uma solicitação para a API do nada e alterar o referenciador.

Digamos que você tenha várias páginas filtradas como "Permitidas" . Você pode criar um aviso apenas para essas páginas e validá-las em sua solicitação.

Se um nounce existir e for válido, a solicitação será permitida. Caso contrário, bloqueie-o.

Jack Johansson
fonte
4
+1 ... é uma API .... a suposição de que você recebe chamadas apenas de navegadores é ridícula.
Mark Kaplun
Sim, acho que o nounce é uma abordagem melhor, pois não existe se alguém enviar diretamente uma solicitação à API.
Jack Johansson
4

A resposta de @ssnepenthe está certa ao dizer que o gancho que você está usando não é o item certo na solicitação de entrada.

As informações de solicitação estão disponíveis imediatamente para o PHP, para que você possa usar o gancho mais antigo disponível para verificá-las. E se você quiser fazer isso no contexto da API de solicitação, use o gancho mais antigo de uma solicitação da API REST. 'rest_pre_dispatch'sugerido por @ssnepenthe está bem, talvez outra opção possa rest_authentication_errorspermitir que você retorne um erro caso algo esteja errado.

Mas Jack Johansson está certo ao dizer que os cabeçalhos HTTP (como o cabeçalho do referenciador usado no aswer de @ ssnepenthe) não são confiáveis, pois são facilmente alterados pelo cliente. Portanto, seria como colocar um guarda de segurança na frente de uma porta que apenas pergunta "é seguro deixá-lo entrar?" para quem quiser entrar: isso não vai funcionar.

Mas a solução proposta pela resposta de Jack Johansson (a nonce) também não é uma solução real: o ponto principal das nonces é mudar com o tempo, e um ponto de extremidade público da API não pode ter coisas que mudam com base no tempo. Além disso, os nonces do WP são confiáveis ​​apenas quando há um usuário conectado, o que pode não ser o caso de uma API pública e, se um usuário está conectado, provavelmente não há razão para verificar o domínio recebido: você confia no usuário, não no máquina do usuário.

Então o que fazer?

Bem, mesmo que os cabeçalhos HTTP não sejam confiáveis, nem todas as informações disponíveis são $_SERVERprovenientes de cabeçalhos.

Normalmente, todos os $_SERVERvalores cujas chaves são iniciadas começam com HTTP_cabeçalhos e precisam ser tratados como entrada não segura do usuário .

Mas, por exemplo, $_SERVER['REMOTE_ADDR']contém o endereço IP usado para a conexão TCP com o servidor, o que significa que é confiável 1 .

O que também significa que:

  • configurar corretamente o servidor para gerar o $_SERVER['REMOTE_HOST']valor (por exemplo, no Apache você precisará HostnameLookups Ondentro do seu httpd.conf) esse valor
  • usando gethostbyaddrpara fazer uma pesquisa DNS reversa para resolver o nome de domínio do IP armazenado em$_SERVER['REMOTE_ADDR']

você poderia obter bastante confiável um nome de host que você pode usar para verificar contra uma whitelist (para o código, você pode adaptar o código de @ de ssnepenthe aswer onde você iria substituir $referer = $request->get_header('referer')com $referer = gethostbyaddr($_SERVER['REMOTE_ADDR'])).

Mas há um problema .

Se o servidor da web estiver protegido por um proxy reverso (solução bastante comum, na verdade), a conexão TCP com o servidor da web será realmente feita pelo proxy, assim $_SERVER['REMOTE_ADDR']será o IP do proxy, e não o IP do cliente que enviou a solicitação originalmente.

O IP original da solicitação nesses casos geralmente está disponível como $_SERVER['HTTP_X_FORWARDED_FOR'], mas ser um desses $_SERVERvalores que começa com HTTP_ele não é realmente confiável.

Portanto, se seu servidor da web estiver protegido por um proxy reverso 2, mesmo $_SERVER['REMOTE_ADDR']isso não seria útil para essa proteção e uma lista de permissões baseada em domínio só poderia ser implementada no nível do proxy.

Em suma, uma solução confiável para a proteção de endpoints da API deve ser implementada usando algum mecanismo de autenticação real (por exemplo, oAuth) ou deve ser feita agindo diretamente na configuração do servidor e não no nível do aplicativo.


Notas

1 Bem, em teoria, pode ser quebrado se alguém invadir seu ISP ou se um invasor agir de dentro da sua LAN, em ambos os casos, há muito pouco que você poderia fazer para estar seguro.

2 Se você não souber se possui um proxy reverso, poderá enviar uma solicitação do PC local e verificar se $_SERVER['REMOTE_ADDR']no servidor corresponde ao IP do PC local e também se $_SERVER['HTTP_X_FORWARDED_FOR']está presente e se corresponde ao IP do PC local.

gmazzap
fonte
O OP está tentando obter o referenciador, então presumi que ele queira fazê-lo em uma página, sem fazer ping diretamente na API.
Jack Johansson
@JackJohansson, na verdade, o OP nunca mencionou o referenciador :) Eles dizem que querem "verificar se veio de um URL 'permitido'", que parece estar procurando o ponto de extremidade da API na lista de permissões para domínios específicos, e também o trecho de código no OP dá o mesmo ideia para mim.
gmazzap