Как Яндекс.Карты защищаются от парсинга

До недавнего времени Яндекс.Карты никак не ограничивали запросы к непубличному API карт. Их можно было парсить в несколько потоков с одного IP адреса в течение недели и не словить бан.

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

Так выглядит подписанный запрос
Так выглядит подписанный запрос

Подпись создается на стороне клиента, а значит найти алгоритм генерации это вопрос времени.

Для этого открываем maps.yandex.ru в Chrome, затем Dev Tools -> Network -> XHR. Кликаем на любой объект на карте, и во кладке с запросами наводим мышь на колонку Initiator у любого запроса с подписью.

Откроется такая цепочка вызовов:

Осталось найти в ней код, который генерирует подпись. Для этого сразу кликаем на вторую строчку и видим следующее:

Под цифрой 3 отмечена функция, которая генерирует подпись. Давайте ее разберем:

В качестве единственного аргумента функция принимает объект с параметрами будущего запроса.

После этого объект сортируется по ключу в порядке возрастания и преобразовывается в строку. Результат присваивается переменной t.

Последний этап это генерация подписи. За это отвечает странный код в цикле for, который является ничем иным как JS реализацией популярного хэш алгоритма djb2.

Алгоритм делает следующее:

  1. Задает изначальное значение хэша равным 5381. Почему именно это число? Потому что в результате тестов удалось выяснить, что так хэш-функция меньше подвержена коллизиям и обладает хорошим лавинным эффектом.
  2. Перебирает символы в строке с параметрами и умножает 33 на текущее значение хэша, преобразуя его в 32 битное благодаря оператору XOR. Почему именно 33? Никто точно не может объяснить. Возможно это связано с тем, что 33 легко заменить на побитовую операцию n << 5, которая выполняется процессором быстрее обычного умножения.
  3. Возвращает хэш, попутно еще раз преобразовав его в 32 битное число.

Затем полученная подпись добавляется к остальным параметрам и отправляется на сервер, где проверяется по такому же алгоритму, как и генерируется.

Алгоритм разобран, теперь проверим его. Для упрощения мы минуем реализацию сортировки параметров и сразу перейдем к генерации подписи.

Возьмем оригинальный запрос Яндекс, как пример того, что должно получиться:

https://yandex.ru/maps/api//tycoon/fetchAuthorizedBusinesses?ajax=1&csrfToken=b0c6443bcacfe78152b8dc3ca49f4754e1b0e88d%3A1626510632&s=3267941894&sessionId=1636510632632_659280

Параметр s равен 3267941894. Значит при генерации подписи для строки ajax=1&csrfToken=b0c6443bcacfe78152b8dc3ca49f4754e1b0e88d%3A1626510632&sessionId=1636510632632_659280 должно получиться именно это число.

В итоге мы выяснили, что подпись запросов не помогает защититься от парсинга, так как все вычисления происходят на стороне клиента. Скорее всего в Яндексе это понимают, и единственная причина по которой они ввели подписи – сломать все работавшие до этого момента парсеры