Как известно, открытый редирект на сайте — это нехорошо и небезопасно. Google предупреждал об этом еще в 2009 году.
Открытый редирект — это скрипт на сайте, который перенаправляет на произвольный URL, указанный в GET-параметре, например:
http://mysite.ru/redirect.php?url=http%3A%2F%2Fbadsite.ru%2F
Какие здесь могут быть уязвимости и как их может эксплуатировать злоумышленник, мы рассматривать не будем, лучше посмотрим, как защитить такой редирект, чтобы не выполнялись переходы на произвольные адреса.
Что предлагает Google?
- Проверять HTTP referer — ничего не гарантирует, его можно подделать.
- Запретить редиректы на внешние сайты — мы предполагаем, что они все-таки нужны.
- Ввести белый список — можно, но решение не гибкое.
- Подписывать редиректы, хэшируя URL назначения — уже интересно.
- Вообще избавиться от редиректов — не наш случай.
- Закрыть скрипты редиректов от поисковых роботов в robots.txt — да, этим тоже не следует пренебрегать.
- Удалять спамные ссылки через 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;
}
}
?>
Таким образом, редирект происходит только при совпадении предоставленного и вычисленного хэшей.