Защита PHP-редиректа

Как известно, открытый редирект на сайте — это нехорошо и небезопасно. Google предупреждал об этом еще в 2009 году.

Открытый редирект — это скрипт на сайте, который перенаправляет на произвольный URL, указанный в GET-параметре, например:

http://mysite.ru/redirect.php?url=http%3A%2F%2Fbadsite.ru%2F

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

Что предлагает Google?

  1. Проверять HTTP referer — ничего не гарантирует, его можно подделать.
  2. Запретить редиректы на внешние сайты — мы предполагаем, что они все-таки нужны.
  3. Ввести белый список — можно, но решение не гибкое.
  4. Подписывать редиректы, хэшируя URL назначения — уже интересно.
  5. Вообще избавиться от редиректов — не наш случай.
  6. Закрыть скрипты редиректов от поисковых роботов в robots.txt — да, этим тоже не следует пренебрегать.
  7. Удалять спамные ссылки через Webmaster Tools — надо надеяться, что до этого не дойдет.

Разберемся, как защитить PHP-редирект с помощью хэширования ссылок, т.е. подписывая каждый редирект и проверяя хэш. Google ссылается на статью о HMAC в Википедии, откуда есть ссылки на примеры реализации и на других языках, помимо PHP. Что касается PHP, нас интересует функция hash_hmac:

string hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] )

Нужно определиться с методом хэширования и секретным ключом. Пример использования:

$url = 'http://destination-url.com/';
$url_hash = hash_hmac('md5', $url, 'MySecretKey', false);

Мы получим хэш вида «3ee86697efa66d0cd54d670433bb4d28». Теперь нужно сформировать ссылку с этим хэшем:

http://mysite.ru/go.php?url=http%3A%2F%2Fdestination-url.com%2F&hash=3ee86697efa66d0cd54d670433bb4d28

Сам скрипт безопасного редиректа может выглядеть так:

<?php

$supplied_url = $_GET['url'];
$supplied_hash = $_GET['hash'];

if (isset($supplied_url) && isset($supplied_hash)) {
  $computed_hash = hash_hmac('md5', $supplied_url, 'MySecretKey', false);
  if (!strcmp($computed_hash, $supplied_hash)) {
    header('Location: ' . $supplied_url);
   exit;
  }
}

?>

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