|
2009-12-18
Обсудить на форуме
Принять участие в разработке библиотеки/утилиты можно на GitHub.
HTTP_UrlSigner позволяет динамически строить URL, защищенный
от подделки злоумышленниками. Имея такой URL, вы можете убедиться, что
он составлен именно вашей системой, а не кем-то еще, и извлечь из него
ранее записанные параметры, не опасаясь, что их могут подменить.
Пример использования HTTP_UrlSigner
|
Листинг 1: Пример использования: формирование URL
|
$signer = new HTTP_UrlSigner("very-secret-word", "http://slave.com/page/*?xyz");
echo $signer->buildUrl(array("a" => 123, "b" => array("x" => 1, "y" => 2)));
// Результат:
// http://slave.com/page/af0b386b9dc43dc0/a879fde2e01643fa1/estMsTU0MDA0wMVMrBwmqZ?xyz |
|
Листинг 2: Пример использования: извлечение параметров из URL
|
$signer = new HTTP_UrlSigner("very-secret-word", "http://slave.com/page/*?xyz");
print_r($signer->parseUrl($_SERVER['REQUEST_URI']));
// Результат:
// Array (
// [a] => 123
// [b] => Array (
// [x] => 1
// [y] => 2
// )
// )
// либо, если URL подделан, генерируется исключение
print_r($signer->parseUrl("http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"));
// Результат: такой же.
// Однако на этот раз проверяется полное совпадение URL, включая имя хоста. |
Зачем нужно цифровое подписывание?
Предположим, в составе вашего сайт имеются два домена: main.com
и slave.com. Находясь на main.com, вы хотите передать на страницу
сайта slave.com параметры array("name" => "Pupkin", "phone" => "1234567").
Конечно, это можно сделать так:
|
Листинг 3: Небезопасная передача параметров с main.com на slave.com
|
http://slave.com/page.php?name=Pupkin&phone=1234567 |
Однако злоумышленник может вручную сформировать этот URL и подменить
в нем телефон на другой. В результате скрипт page.php на slave.com
получит неверные данные.
Чтобы защититься от подделок, придумали алгоритм под названием
"цифровое подписывание". Он работает так: к передаваемым данным
добавляется специальный хэш, цифровая подпись:
|
Листинг 4: Пример URL с цифровой подписью
|
http://slave.com/page.php?name=Pupkin&phone=1234567&sign=d41d8cd98f00b204e9800998ecf8427e |
где значение параметра sign вычисляется по схеме:
|
Листинг 5: Пример URL с цифровой подписью
|
$signInUrl = md5($SECRET . serialize($params)); |
Параметр $SECRET необходимо хранить в секрете; его должны знать только сайты main.com
и slave.com, но никто больше. Теперь скрипт page.php на сайте slave.com извлекает
данные $params и проводит точно такую же операцию на своей стороне:
|
Листинг 6: Вычисление цифровой подписи на принимающей стороне
|
$calculatedSign = md5($SECRET . serialize($params)); |
Что это нам дало? Если значения переменных $calculatedSign и $signInUrl совпали,
значит, URL сформирован нами, а не злоумышленниками (ведь злоумышленник
не знает значения $SECRET).
Особенность HTTP_UrlSigner
Взглянем еще раз на пример URL, который формирует HTTP_UrlSigner:
|
Листинг 7: Пример URL, сформированного HTTP_UrlSigner
|
http://slave.com/page/af0b386b9dc43dc0/a879fde2e01643fa1/estMsTU0MDA0wMVMrBwmqZ?xyz |
Мы видим, что HTTP_UrlSigner не просто добавляет цифровую подпись в виде GET-параметра
к уже имеющемуся URL. Его задача шире:
- Обеспечить возможность передачи параметров без задействования QUERY_STRING; дать
возможность указать шаблон построения результирующего URL, где
вместо "*" вставляются подписанные данные.
- Гарантировать неизменность не только параметров, но всего
сформированного URL, с точностью до символа.
Передача данных без QUERY_STRING
Параметры (произвольный ассоциативный массив) должны упаковываться и при
необходимости вставляться в середину URL, а не только в QUERY_STRING.
Почему это важно? Дело в том, что подписанные URL часто используют для
доступа к медиа-контенту (картинки, JavaScript- и CSS-файлы и т. д.).
Для них очень важно правильное браузерное кэширование. Однако некоторые старые
браузеры или слишком "умные" прокси-серверы плохо относятся к URL, содержащим
непустой QUERY_STRING. Поэтому мы должны иметь полную свободу действия.
При формировании подписанных данных по возможности применяется
gzip-сжатие; в ряде случаев (если параметров много) это серьезно
сокращает длину URL. Кроме того, библиотека следит за тем, чтобы
в результирующем URL был как минимум один "/" на каждые 80 символов;
это необходимо для IE6, который неверно ведет себя при кэшировании
"файлов" (как он думает) с длинными (по его мнению) именами.
Подписывание URL с точностью до байта
HTTP_UrlSigner подписывает не параметры, а весь URL целиком
(при необходимости включая имя хоста и протокола). Таким образом,
злоумышленник не сможет даже добавить незначащих параметров в конец
URL: он сразу же окажется невалидным. Например, URL
http://slave.com/page/af0b386b9dc43dc0/a879fde2e01643fa1/estMsTU0MDA0wMVMrBwmqZ?xyz
пройдет проверку цифровой подписи, но если к нему добавить "&abc=1":
http://slave.com/page/af0b386b9dc43dc0/a879fde2e01643fa1/estMsTU0MDA0wMVMrBwmqZ?xyz&abc=1
то он уже будет некорректным. Зачем же это нужно?
Нужно это для защиты от злонамеренного "забивания" кэша
reverse-proxy (если он используется), а также для защиты от
DoS-атак.
Хорошая иллюстрация тут кэш nginx, о котором я писал
в статье Подводные камни при
использовании кэширования в nginx. Если ключ кэширования
выбирается равным REQUEST_URI,
то злоумышленник, добавляя в REQUEST_URI незначащие параметры,
может организовать нам серию "кэш-промахов", перегрузив тем
самым сервер.
HTTP_UrlSigner не дает этого сделать: любой URL,
отличающийся от эталонного, будет распознан как поддельный
и не даст перегрузить сервер или забить кэш nginx.
Резюме
На базе HTTP_UrlSigner вы можете строить инструменты,
требующие генерации URL, защищенных от подделки. Вероятнее всего,
эти инструменты также будут требовать интенсивного серверного
кэширования, которое удобно организовывать при помощи nginx.
Вот несколько примеров практического применения техники подписывания URL:
- Средство для объединения нескольких CSS- и JS-файлов в
один большой "на лету".
- Автоматический ресайз картинок "на лету" (здесь кэширование
просто обязательно!).
- Защищенная передача данных между доменами в случае, если
организация кросс-доменной сессии представляется сложной.
|