проверьте запрашивающий URL

9

Использование WP 4.8.2

Как лучше всего проверить запрашивающий URL при обработке запроса с помощью rest-api?

Например, сайт получает запрос, и вы хотите проверить, пришел ли он с «разрешенного» URL. И потерпеть неудачу, если URL не разрешен.

Это не работает:

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
источник
После комментирования условного ответа ответ все равно отправляется. Поэтому я думаю, что я использую не тот крючок.
Shanebp
Если вы проверили, как $requestи $urlкак выглядят переменные с помощью var_dumpили аналогично, я обнаружил, что проверка входов и выходов всегда приводит к правильному ответу.
Farinspace
2
Ссылочный URL-адрес легко подделывается и не может быть использован для какой-либо безопасности.
Майло
Мы используем токены и SSL. Мы также хотели бы проверить ссылающийся URL, независимо от того, может ли он быть подделан.
Shanebp
2
это API, открытый для Интернета, о чем идет речь о рефери в качестве аутентификации, а SSL просто не имеет значения. Вы, вероятно, также отключаете защиту CORS ... Если она не доступна только зарегистрированным пользователям, это обеспечивает нулевую безопасность.
Марк Каплун

Ответы:

5

Этот фильтр определенно не тот, который вы ищете. Этот фильтр срабатывает перед возвратом, результатом WP_REST_Request::from_url()которого является фабричный метод, который используется только для внутренних операций.

Лучший вариант - вернуть WP_Errorэкземпляр в rest_pre_dispatchфильтр .

Некоторые предостережения:

Как упомянул @milo, реферер ненадежен и не должен использоваться для проверки безопасности.

Кроме того, это не гарантируется быть установленным.

Вне зависимости от этого, вот пример того, как вы можете использовать rest_pre_dispatchфильтр для сбоя запроса, если он поступил от плохого реферера:

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
источник
4

Все, что вы получаете от клиента, считается вводом пользователя и не должно быть доверенным. Поскольку заголовком можно легко манипулировать и злоупотреблять, я предлагаю не использовать этот метод, если вы полагаетесь на него для получения конфиденциальных данных.

Если запросы поступают со страницы, у вас может быть другой подход. В противном случае любой может отправить запрос в API из ниоткуда и сменить реферера.

Допустим, у вас есть несколько страниц, которые отфильтрованы как «Разрешенные» . Вы можете создать существительное только для этих страниц, а затем подтвердить их в своем запросе.

Если существительное существует и является действительным, запрос разрешается. В противном случае заблокируйте это.

Джек Йоханссон
источник
4
+1 ... это API .... предположение, что вы получаете звонки только из браузеров, смешно.
Марк Каплун
Да, я думаю, что nounce - лучший подход, так как он не существует, если кто-то напрямую отправляет запрос в API.
Джек Йоханссон
4

Ответ @ssnepenthe прав, говоря, что используемый вами хук не является правильным во входящем запросе.

Информация запроса немедленно доступна для PHP, поэтому вы можете использовать самый ранний доступный хук для их проверки. И если вы хотите сделать это в контексте API запроса, вы должны использовать самый ранний хук запроса REST API. 'rest_pre_dispatch'предложено @ssnepenthe хорошо, может быть, другой вариант может бытьrest_authentication_errors , который позволит вам вернуть ошибку в случае, если что-то не так.

Но Джек Йоханссон прав, говоря что HTTP-заголовки (например, заголовок referer, используемый в aswer @ ssnepenthe) не являются надежными, так как они очень легко изменяются клиентом. Так что это все равно, что поставить охранника перед дверью, который просто спросит: "Безопасно ли вам входить?" всем, кто хочет войти: это не сработает.

Но решение, предложенное ответом Джека Йоханссона (одноразовый номер), также не является реальным решением: весь смысл одноразовых номеров состоит в том, чтобы изменяться со временем, и у конечной точки общедоступного API не может быть вещей, которые изменяются в зависимости от времени. Более того, одноразовые номера WP заслуживают доверия только тогда, когда есть вошедший в систему пользователь, что может не соответствовать общедоступному API и если пользователь вошел в систему, вероятно, нет причин проверять входящий домен: вы доверяете пользователю, а не пользователю. пользовательская машина.

Так что делать?

Ну, даже если заголовки HTTP не являются надежными, не вся доступная информация $_SERVERпоступает из заголовков.

Обычно все $_SERVERзначения, чьи ключи начинаются с имени, начинаются с HTTP_заголовков и должны рассматриваться как небезопасный ввод пользователя .

Но, к примеру, $_SERVER['REMOTE_ADDR']содержит IP - адрес , используемый для подключения TCP к вашему серверу, что означает , что он является доверительным 1 .

Что также означает, что либо:

  • правильная настройка сервера для генерации $_SERVER['REMOTE_HOST']значения (например, в Apache, которое вам нужно HostnameLookups Onвнутри вашего httpd.conf) это значение
  • использование gethostbyaddrдля обратного поиска DNS для разрешения доменного имени IP-адреса, хранящегося в$_SERVER['REMOTE_ADDR']

вы можете получить достаточно надежно имя хоста , которое вы могли бы использовать , чтобы проверить против белого списка (для кода, вы можете адаптировать код из @ ssnepenthe - х aswer , где вы бы заменить $referer = $request->get_header('referer')с $referer = gethostbyaddr($_SERVER['REMOTE_ADDR'])).

Но есть проблема .

Если ваш веб-сервер находится за обратным прокси-сервером (на самом деле, это довольно распространенное решение), TCP-подключение к веб-серверу фактически осуществляется через прокси-сервер, так $_SERVER['REMOTE_ADDR']же как и IP-адрес прокси-сервера, а не IP-адрес клиента, который первоначально отправил запрос.

Исходный IP-адрес запроса в таких случаях обычно доступен как $_SERVER['HTTP_X_FORWARDED_FOR'], но будучи одним из тех $_SERVERзначений, которые начинаются с HTTP_него, на самом деле нельзя доверять.

Таким образом, если ваш веб-сервер находится за обратным прокси-сервером 2, даже $_SERVER['REMOTE_ADDR']он не будет полезен для такой защиты, а белый список на основе домена может быть реализован только на уровне прокси.

Короче говоря, надежное решение для защиты конечных точек API должно быть либо реализовано с использованием какого-либо реального механизма аутентификации (например, oAuth), либо оно должно действовать непосредственно на конфигурации сервера, а не на уровне приложения.


Ноты

1 Теоретически, он может быть взломан, если кто-то взломал вашего интернет-провайдера или если злоумышленник действует изнутри вашей локальной сети, в обоих случаях вы можете сделать очень мало для обеспечения безопасности.

2 Если вы не знаете, используете ли вы обратный прокси-сервер, вы можете отправить запрос со своего локального ПК и проверить, соответствует ли $_SERVER['REMOTE_ADDR']на сервере IP-адрес локального ПК, а также, если $_SERVER['HTTP_X_FORWARDED_FOR']он присутствует, и соответствует ли он IP-адресу локального ПК.

Gmazzap
источник
ОП пытается получить реферера, поэтому я предположил, что он хочет сделать это на странице, без прямой проверки API.
Джек Йоханссон
@JackJohansson на самом деле OP никогда не упоминал реферера :) Они говорят, что хотят «проверить, пришел ли он с« разрешенного »URL», что звучит так, как будто они ищут конечную точку API белого списка для особых доменов, а также фрагмент кода в OP дает то же самое Идея для меня.
gmazzap