Как распределённый краулер притворился человеком — и сам доказал, что он бот

Автор: Денис Аветисян
Примечание для любознательных и особенно любознательных. Это публичная версия реального расследования. Адреса, домены, названия сетей, часть чисел, временные интервалы и некоторые детали архитектуры изменены. Логика расследования и обнаруженные противоречия сохранены. Если вы хотели по этой статье восстановить устройство нашей защиты — примите мои соболезнования: именно для вас кое-где переставлена мебель.
Пролог. Посетитель, которого не существовало
Если однажды график посещаемости вашего сайта бодро пошёл вверх, а доход от рекламы остался лежать на полу, то вам не показалось.
Обычно в этот момент владелец сайта проходит три стадии принятия реальности. Сначала радуется: “Наконец-то алгоритмы меня полюбили!” Потом обновляет рекламный кабинет. Потом обновляет его ещё раз — уже с выражением лица человека, который трясёт пустой автомат с кофе.
Посетители есть. Денег нет.
И оба графика при этом могут быть честными. Google Analytics считает события и сессии. Рекламная система — состоявшиеся и допустимые показы. Сервер — работу, которую ему пришлось выполнить. Эти три числа вовсе не обязаны двигаться вместе.
GA4 автоматически исключает известных ботов. Известных — вот в чём фокус. Краулер, который надел обычный User-Agent, запустил достаточно браузерного кода или воспроизвёл стандартный аналитический сигнал, может оставить событие, похожее на человеческое. Google Analytics умеет принимать некоторые события непосредственно по HTTP через штатный Measurement Protocol. Его придумали, конечно, не для ботов, а для серверных и офлайн-взаимодействий. Он требует предварительной настройки и не открывает анонимную дверь в чужую статистику. Но принцип показателен: аналитика получает правильно оформленное событие, а не видеозапись человека перед экраном.
Но событие аналитики — ещё не рекламный показ. Бот может не загрузить рекламный скрипт, не дождаться аукциона, не отрисовать блок и не обеспечить видимость объявления. А если автоматические показы или клики всё же возникли, рекламные платформы стараются признать их недействительным трафиком. Google прямо пишет, что анализирует показы и клики автоматических источников и не выставляет рекламодателям счёт за отфильтрованный invalid traffic.
Экономика выходит удивительная. Счётчик посещений растёт. Рекламный доход — нет. Зато сервер честно выполняет TLS-рукопожатия, разбирает запросы и пишет их в журналы, а на динамических маршрутах может ещё и запускать приложение, обращаться к базе, формировать HTML и отдавать файлы.
Робот получает бесплатную экскурсию. Аналитика — новую сессию. Владелец — нагрузку на процессор.
Есть у серверных журналов одно неприятное свойство: они не умеют врать, но очень охотно помогают врать другим.
Вот строка: человек пришёл из Google. Вот следующая: открыл статью. Потом ещё одну. Загрузил картинку. Браузер современный, темп умеренный, ничего уголовного. Обычный вечерний читатель, который вместо сна решил выяснить, например, почему квантовая физика портит людям настроение.
На другом сайте появился другой читатель. На третьем — третий. Темы сайтов никак не связаны. Адреса разные. Браузеры тоже. Каждый посетитель по отдельности выглядел настолько нормально, что хотелось извиниться перед ним за подозрения и предложить печенье.
Но стоило положить их следы рядом, как возникло странное ощущение.
Представьте театральную постановку, в которой сорок актёров играют одного человека. Один входит в дверь. Второй снимает пальто. Третий заказывает кофе. Четвёртый пьёт его в соседнем городе. Пока вы смотрите на каждую сцену отдельно, всё прекрасно. Но целиком спектакль начинает беспокоить даже самого терпеливого зрителя.
У нас появился простой вопрос: можно ли вычислить распределённого бота, если он специально выучил все внешние признаки человека?
Сразу уточню: речь не о тупом ботнете, который за минуту просит десять тысяч случайных PHP-файлов, путает скобки в User-Agent и с разбега врезается в rate limit. И не об очередном ИИ-агенте, который честно подписался своим именем и методично читает сайт от первой страницы до последней. Таких посетителей обычно выдаёт либо громкость, либо оставленная на столе визитка.
Наш противник делал всё наоборот. Он приходил редко, представлялся обычным браузером, указывал поисковик, выдерживал паузы, переходил по внутренним ссылкам и иногда запрашивал ресурсы страницы. Адреса менялись раньше, чем локальный фильтр успевал накопить уверенность. Каждый отдельный узел видел почти естественного посетителя.
Вот чем он был опасен. Бот не пытался сломать дверь шумом. Он прочитал учебник о том, как выглядит человек, и пришёл сдавать экзамен.
Чтобы ответить, мы решили сделать то, что обычно очень полезно в науке и ужасно раздражает коллег: не доказывать, что фильтр прав, а попытаться доказать, что он ошибается.

1. Рыболовная сеть для серверных журналов
Обычный сайт видит интернет примерно так, как рыбак видит море через отверстие в ведре. Проплыла рыбка. Через минуту ещё одна. Может быть, это стая. Может быть, две независимые рыбки с одинаковыми жизненными целями. По отверстию не разберёшь.
У нас сайтов много, и они тематически далеки друг от друга. Поэтому события с разных площадок попадают в отдельный аналитический контур. Сам он сайты не обслуживает и в разговор посетителя с веб-сервером не вмешивается. Он лишь слушает, как хороший физик слушает подозрительно гудящий трансформатор.
Каждое событие превращается в небольшой набор безопасных признаков: откуда пришёл запрос, какую страницу попросил, каким браузером представился, что запросил потом, повторился ли этот рисунок на других площадках. Из таких событий строится непрерывно меняющийся граф.
Вершины графа — это не только адреса, но и сетевые группы, браузерные маски, страницы, источники переходов и ресурсы. Рёбра нужны, чтобы задавать вопросы поинтереснее:
-
появлялась ли одна и та же легенда на несвязанных сайтах;
-
следовала ли загрузка ресурсов за загрузкой HTML;
-
принадлежали ли части “сессии” одному наблюдаемому посетителю;
-
повторялся ли сценарий после смены адреса и браузера;
-
совместимы ли события по времени и причинности.
Часть графа пересчитывается на GPU в потоке. Не потому, что без видеокарты теперь неприлично выходить из дома, а потому, что сравнивать множество коротких зависимостей удобнее параллельно. На выходе система получает не магическое число “ботности”, а несколько конкурирующих объяснений поведения.
Один сайт по-прежнему видит рыбку. Центральная система видит, что рыбки плывут строем, синхронно поворачивают и почему-то все носят одинаковые накладные усы.
Страну, один адрес или единственный странный запрос мы уликой не считаем. Страна вообще очень плохое доказательство. Сервер может стоять в Сингапуре, владелец — в Канаде, оператор — в третьем месте, а настоящий пользователь проходить через корпоративный VPN из кухни в Мытищах.
Мы искали не плохой адрес. Мы искали плохую биографию.
2. Как выглядит человек с точки зрения фильтра
Человек в интернете оставляет беспорядок. И это прекрасный, живой беспорядок.
Он приходит из поисковика. Браузер забирает HTML, затем CSS, JavaScript, шрифты, картинки и маленькую иконку сайта, о существовании которой человек обычно даже не подозревает. Потом читатель нажимает ссылку, возвращается назад, отвлекается, открывает ещё одну вкладку и на семь минут исчезает, потому что ему позвонили.
Браузер в этом смысле похож на гостя, который пришёл на вечеринку не один. Вместе с ним в квартиру немедленно входят стили, скрипты, изображения, аналитика, шрифты и favicon — тот самый маленький человек, которого никто не приглашал, но он всё равно здесь.
У живого пользователя обычно есть причинная цепочка:
страница → ресурсы страницы → пауза → действие → следующая страница.
Однако здесь легко впасть в религиозный фанатизм. Не всякий человек грузит все ресурсы. Есть кэш, блокировщики, режимы экономии трафика, предварительная загрузка, Service Worker, мессенджеры и браузеры, собранные людьми, явно пережившими тяжёлое детство. Один прямой запрос без favicon ничего не доказывает.
Приходится собирать признаки вместе.
В пользу человека говорят поисковый переход, связанная загрузка ресурсов, последовательная навигация, относительно стабильный браузер, умеренный темп и полное равнодушие к административным дырам двадцатилетней давности.
Против него — точнее, против его легенды — говорят массовые походы по несвязанным сайтам, голый HTML без продолжения, постоянная смена браузерных масок, прямые запросы к случайным старым материалам и один сценарий, повторяемый разными адресами одной сети.
Но даже это не приговор. Это приглашение к допросу.
3. Испытание: оправдаем самого человечного подозреваемого
После первого калибровочного прогона система показала множество подозрительных сетевых профилей. Некоторые даже не старались. Они стучались в служебные файлы, искали старые веб-шеллы и представлялись библиотекой для HTTP-запросов. Тут детектив не нужен; достаточно таблички “не кормить”.
Но один кандидат выделялся.
Назовём его 203.0.113.0/24. Это заведомо подставное обозначение, а не реальный диапазон из расследования.
За восемь дней он оставил чуть больше сотни событий. В агрегатах были поисковые переходы, около полусотни статических ресурсов, несколько десятков внутренних переходов и всего несколько вариантов браузера. Ни одного любопытства к опасным административным путям. Никаких яростных всплесков. Никакой классической походки сканера.
Если бы этот профиль пришёл устраиваться на работу человеком, резюме выглядело бы убедительно:
-
пользуюсь Google и Яндексом;
-
читаю статьи;
-
смотрю картинки;
-
перехожу по ссылкам;
-
WordPress не ломаю;
-
в свободное время меняю IP, но кто сейчас без странностей.
Его-то мы и выбрали для проверки. Тестировать фильтр на существе, которое просит /.env триста раз в минуту, — всё равно что проверять детектор дыма, поднеся его к горящему дивану. Интереснее узнать, заметит ли он тлеющую спичку.
А теперь маленький эксперимент. Не читайте дальше несколько секунд и выберите версию:
-
обычный человек;
-
сотрудники компании за общим VPN;
-
поисковый робот;
-
распределённый краулер;
-
системный администратор, которому опять не спится.
Я выбрал второй вариант. Мне очень хотелось, чтобы фильтр оказался неправ.
4. Первые показания начинают противоречить друг другу
Агрегаты хороши, пока не задаёшь им невежливые вопросы. Мы выгрузили сырые события на границах того же периода. Получилось около полутора сотен запросов от нескольких десятков адресов.
И тут подозреваемый сделал первую удивительную вещь: почти половина запросов буквально представлялась строкой вроде:
Mozilla/5.0 (compatible; crawler)
Это было очаровательно. Система несколько дней пыталась решить, не обидит ли она живого человека, а живой человек тем временем ходил с бейджем “КРАУЛЕР”.
Но одна подпись всё равно ничего не решает. Может быть, это легитимный индексатор. Может быть, исследовательский инструмент. Может быть, разработчик решил назвать браузер честно — редкое, но теоретически допустимое расстройство.
Странность была в другом: эта маска вращалась между множеством адресов. А знаменитая “ресурсная цепочка”, которая делала кандидата таким человечным, состояла только из изображений.
Ни одного CSS.
Ни одного JavaScript.
Ни одного шрифта или favicon.
Почти все картинки возвращали 404 Not Found. У них не было Referer, то есть они не выглядели как продолжение только что открытой страницы. Не было POST-запросов, фоновых обращений и вообще ничего, что обычно делает вкладка, когда в ней действительно живёт сайт.
Стоп. Что именно мы наблюдаем?
Браузер не открывает статью, а затем сорок раз случайно забывает взять её стили и скрипты, зато аккуратно вспоминает адрес несуществующей картинки. Так ведёт себя не читатель. Так ведёт себя программа, у которой где-то лежит список старых URL.
Страница не была загружена. Разные адреса просто перебирали останки её медиатеки.
Кандидат получил человеческие баллы за “ресурсы”, но ресурсы принадлежали не человеческой сессии. Мы приняли склад старых дверных ручек за доказательство существования дома.
5. Поддельные алиби
Оставались поисковые переходы. Это сильный признак: человек действительно часто приходит из Google или Яндекса.
Проблема в том, что Referer — не справка, выданная поисковиком. Это записка, которую посетитель приносит с собой. На ней можно написать что угодно.
Один адрес сообщил, что пришёл из Google. Через несколько секунд тот же адрес уже уверял, что прибыл из Yahoo. Затем он открыл больше десятка статей за пару минут.
Другой чередовал Яндекс, Bing и внутренние ссылки с таким спокойствием, словно бегал между тремя вокзалами через одну и ту же дверь.
Третий сначала изображал переход из Google, а позже — из Baidu, сохранив при этом один и тот же старый мобильный браузер. Старый настолько, что настоящий телефон с ним, вероятно, уже лежал бы в музее рядом с пейджером и надеждой на приватность.
После этих “переходов” по-прежнему не загружались ресурсы страниц.
Разница существенная. Человек может прийти из разных поисковиков. Но он не приходит из нескольких поисковиков почти одновременно к одной и той же серии материалов, не оставляя после себя ни одной нормальной браузерной цепочки.
У нашего посетителя было не мало алиби. Их было слишком много.
Он не просто сканировал сайт. Он разыгрывал происхождение посетителя.
6. Кульминация: спектакль внутрисайтовой навигации
Хорошо, сказали мы. Картинки поддельные, поисковики подозрительные. Но внутренние переходы? Их ведь много. Здесь Referer указывает на предыдущую статью того же сайта. Именно так и читает человек.
Оказалось, почти весь этот спектакль поставили два адреса.
Первый открыл больше двух десятков материалов примерно за десять минут. Второй прошёл сопоставимый маршрут меньше чем за три. Запросы шли каждые несколько секунд и содержали безупречные внутренние Referer. Статья А вела на статью Б, статья Б — на статью В. Настоящий маленький литературный салон.
Только браузер снова ничего не загружал.
Ни стилей. Ни скриптов. Ни изображений самих страниц. Ни фоновых действий. Он переходил по сайту быстрее, чем человек успел бы прочитать заголовки, и при этом точно знал, какую ссылку следует указать предыдущей.
Можно возразить: а вдруг это очень быстрый читатель?
Конечно. Возможно также, что он читал двадцать статей одновременно, отключил всё содержимое, кроме HTML, менял поисковики на бегу, распределил картинки между соседями и время от времени надевал бейдж “crawler”. Наука не запрещает такую гипотезу. Она лишь просит сравнить её с более короткой.
Более короткая звучала так: программа заранее построила цепочку URL и последовательно подставляла правильный Referer.
Это было не чтение сайта.
Это была автоматическая постановка чтения сайта.
7. Кто скрывался за маской
Лишь разобравшись с поведением, мы посмотрели на происхождение сети. В таком порядке, не наоборот.
Записи RDAP, маршрутные объявления и независимые сетевые классификации указывали не на домашнего или мобильного оператора, а на инфраструктурный диапазон, связанный с серверными услугами и прокси. Реальное имя владельца здесь опущено: для вывода оно не нужно, а для нашей операционной безопасности — вполне.
Сам по себе дата-центр не делает посетителя ботом. Через VPN может пройти живой человек. Разработчик может читать собственный сайт с виртуальной машины. Администратор может устроить себе настолько сложный маршрут до статьи, что пакетам потребуется психолог.
Принадлежность сети стала не приговором, а последней деталью пазла. Она хорошо объясняла уже увиденное: ротацию адресов, отсутствие цельных сессий и повторение одного сценария разными узлами.
Если бы поведение было человеческим, серверное происхождение ничего бы не изменило.
Но поведение было поведением распределённого краулера.
8. Неожиданная развязка
Самого человечного кандидата оправдать не удалось.
Получилось ровно обратное: кандидат лучше всех остальных показал, что идея фильтра работает. Человеческие признаки действительно присутствовали — только каждый был изготовлен отдельно.
Поисковые Referer приносили одни запросы.
Внутреннюю навигацию играли другие.
Картинки перебирали третьи адреса.
События аналитики в отдельных эпизодах создавали четвёртые.
Браузерные User-Agent наклеивались сверху, как усы на детском спектакле.
На уровне сетевой группы получался человек. На уровне сессии человека не было.
Вот здесь графовый анализ оказался полезнее обычной таблицы баллов. Таблица говорит: “У этой сети есть страницы, переходы и ресурсы”. Граф спрашивает: “А связаны ли они в том порядке, в котором могли произойти?”
Бот собрал образ человека из правильных деталей. Но детали не складывались в причинную историю.
Отсюда и всплеск “посетителей” на дашборде без роста рекламных показов и дохода. Счётчику легенда порой нравилась. Рекламной системе и нашей реконструкции сессии — уже нет. Сервер философией не занимался и продолжал выполнять запросы, поэтому безусловно росла только нагрузка.
Это как предъявить следователю фотографию завтрака, билет на поезд и мокрый зонтик. Все предметы человеческие. Но если завтракали в Казани в 9:00, поезд ушёл из Омска в 9:03, а дождь шёл вчера в Буэнос-Айресе, зонтик становится не алиби, а уликой.
9. Что пришлось исправить перед боевым включением
Хорошее расследование должно поймать не только противника, но и собственную ошибку. Иначе это не расследование, а корпоративная презентация.
Мы обнаружили слабое место первой версии анализатора: на грубом уровне агрегации HTML и ресурсы могли “познакомиться”, хотя их запрашивали разные адреса и между ними не было логической сессии. Один узел приносил страницу, другой через сутки просил старую картинку, а система вежливо засчитывала им совместную прогулку.
Связи пришлось сделать строже. Теперь признаки страницы получают вес только внутри совместимой причинной цепочки. Что именно считается совместимой цепочкой, в публичной статье я, пожалуй, оставлю за занавесом. У фокусника должна быть хотя бы одна профессиональная тайна, особенно если в зале сидят люди с эксплойтами.
Перед включением реальных действий мы добавили несколько предохранителей:
-
подтверждённая авторизованная административная работа отделяется от публичного трафика;
-
фоновые обращения легитимных плагинов не маскируются под атаку только из-за высокой параллельности;
-
известным поисковым роботам доверяют не по красивой строке User-Agent, а по сочетанию независимых проверок;
-
сначала фиксируется новый baseline, и только потом система получает право что-либо блокировать;
-
каждое решение сохраняет объяснение, а любое изменение имеет проверяемый откат.
Фильтр некоторое время работал как судья без молотка: выносил вердикты, но никого не выводил из зала. Мы вручную разбирали самых опасных и самых человечных кандидатов, искали ложные срабатывания и специально проверяли собственные административные сценарии.
Только после этого поведенческий контур перевели в боевой режим, а выявленную кампанию отправили в блокировку.
10. Второе дело: карнавал GSCAN
Через некоторое время в журналах появился другой противник. Если первый краулер был актёром, мечтавшим сыграть обычного читателя, то этот явился на сцену с ломом и чемоданом чужих паспортов.
Мы условно назвали кампанию GSCAN.
Она перебирала характерные пути старых PHP-приложений, CGI-обработчиков, SQL-интерфейсов, PHPUnit, GeoServer и известных веб-шеллов. Проверяла выполнение команд. Искала уязвимости, которые давно должны были исчезнуть, но всё ещё живут в интернете, как носки за батареей: никто не признаётся, откуда они взялись, но выбрасывать страшно.
Небольшой музей дыр, которые почему-то всё ещё работают
На минуту отложим детектив. Названия уязвимостей ниже — в отличие от адресов и внутреннего устройства нашей защиты — настоящие. Следы именно этих классов эксплойтов встречались в журналах за время расследования. Не все строки обязаны принадлежать одному исполняемому файлу: GSCAN — наше название поведенчески связанной кампании, а не хеш конкретной программы. Рабочие payload я, разумеется, приводить не буду: статья должна помогать закрыть дверь, а не служить инструкцией по изготовлению отмычки.
|
Что искал сканер |
На что рассчитана проверка |
Что получает атакующий |
Что проверить у себя |
|---|---|---|---|
|
WordPress REST Batch API |
Цепочка wp2shell: SQL-инъекция CVE-2026-60137 и путаница маршрутов REST API CVE-2026-63030 |
В уязвимых версиях две ошибки объединяются в удалённое выполнение кода без авторизации |
WordPress 6.9 должен быть не ниже 6.9.5, ветка 7.0 — не ниже 7.0.2; для 6.8 исправление SQL-инъекции вошло в 6.8.6. После обновления проверить целостность ядра, плагины, неизвестных администраторов и следы посторонних PHP-файлов |
|
|
Старый, но до сих пор массово проверяемый RCE CVE-2017-9841 |
Если уязвимый PHPUnit лежит в доступном из интернета |
Не устанавливать dev-зависимости в production, не размещать |
|
Запросы к GeoServer OGC/WFS |
Критический RCE CVE-2024-36401, связанный с обработкой XPath-выражений в именах свойств |
Выполнение кода без предварительной авторизации на уязвимом GeoServer |
Обновиться до поддерживаемого релиза с исправлением. Простого закрытия административной панели недостаточно: проблема находилась в публично доступной обработке OGC-запросов |
|
Характерные PHP-CGI и |
Семейство инъекций аргументов PHP-CGI, включая CVE-2024-4577, а на совсем старых установках — её предшественников |
В определённых CGI-конфигурациях — от раскрытия исходного кода до удалённого выполнения команд |
Обновить PHP до актуальной поддерживаемой ветки, проверить реальный SAPI и по возможности не публиковать PHP через CGI. Обычный PHP-FPM — другой режим и автоматически уязвимым от одного такого запроса не становится |
|
|
Это не CVE, а поиск случайно опубликованных секретов |
Пароли базы, ключи API, токены облака, salts и адреса внутренних сервисов |
Закрыть dot-файлы на уровне веб-сервера, вынести document root в отдельный публичный каталог и немедленно ротировать любой секрет, который хотя бы однажды отвечал из интернета кодом |
|
Известные имена |
Проверка не уязвимости, а уже состоявшегося взлома |
Повторный вход на ранее заражённый сайт |
Ответ |
Но не всякий путь однозначно указывает на CVE. По одному запросу к /cgi-bin/ нельзя честно объявить, какую именно дыру проверяли: нужны параметры, метод, заголовки и форма payload. Путь eval-stdin.php или серия обращений к WordPress Batch API гораздо специфичнее. Поэтому часть строк мы помечаем как подтверждённый fingerprint, а часть — лишь как семейство проверяемых уязвимостей.
Это различие кажется академическим ровно до первого отчёта, в котором любой 404 торжественно объявляют “успешной атакой”. Запрос эксплойта доказывает намерение. Успешную эксплуатацию доказывают уже другие вещи: необычные процессы, задержки базы, новые файлы, изменённые учётные записи и исходящие соединения.
Журнал показывает, что в дверь стреляли. Пробила ли пуля замок, выясняется уже при осмотре комнаты.
География без геополитики
Тут руки сами тянутся открыть карту мира, покрасить несколько стран в красный цвет и торжественно объявить их родиной киберзла. Очень популярный жанр аналитики: красивый, понятный и почти всегда лживый.
Страна в серверном журнале — это не национальность атакующего. Чаще это место регистрации сети, облачный регион, VPN-шлюз, заражённый сервер или дешёвый прокси, выбранный программой автоматически. Человек, управляющий атакой, может находиться где угодно. Ниже показана только география наблюдавшейся инфраструктуры.
Мы взяли семидневный срез центрального потока и оставили только классифицированные события: попытки эксплуатации и сканирования, обращения к административным путям, атаки на авторизацию и подтверждённых нежелательных краулеров. Обычные запросы в расчёт не вошли. Мы также исключили события, заблокированные одной лишь страновой политикой: географическое правило может остановить запрос, но не превращает его автоматически в атаку.
Получилось около 229 тысяч событий почти от 5 тысяч сетевых префиксов. Примерно 35% сетей не удалось надёжно привязать к стране; они остались честной категорией “неизвестно”, а не были распределены по карте для красоты.
Среди сетей с определённой географией верх таблицы выглядел так. Числа округлены, чтобы не превращать публикацию в копию оперативного отчёта.
|
Страна регистрации наблюдаемой сети |
Классифицированные события |
Уникальные сетевые префиксы |
|---|---|---|
|
США |
около 102 000 |
около 1 250 |
|
Австрия |
около 19 000 |
около 20 |
|
Германия |
около 18 600 |
около 180 |
|
Великобритания |
около 8 500 |
около 150 |
|
Франция |
около 6 400 |
около 120 |
|
Турция |
около 5 800 |
около 20 |
|
Сингапур |
около 5 500 |
около 160 |
|
Китай |
около 2 100 |
около 120 |
|
Россия |
около 1 900 |
около 265 |
Два столбца рассказали совершенно разные истории.
По общему числу событий лидировали США: примерно половина всего трафика, для которого удалось определить страну. Причина прозаическая: там сосредоточена огромная доля облачной и хостинговой инфраструктуры. Австрию наверх вытянуло небольшое число чрезвычайно шумных сетей. Россия, напротив, дала сравнительно мало запросов, но много отдельных сетевых префиксов.
Если ранжировать не по громкости, а по ширине распределения, после США шли Россия, Германия, Сингапур, Великобритания, Франция и Китай. Один рейтинг показывает, где стояли самые разговорчивые машины. Второй — насколько широко оператор разложил свою инфраструктуру.
Блокировать страну по такой таблице — всё равно что запретить все автомобили из города, где однажды нашли машину грабителей. Для расследования важнее сочетание ASN, типа сети, повторяемости сценария, охвата сайтов и технического fingerprint. География — хороший усилитель доказательства и ужасная замена доказательству.
В течение одной последовательности запросов источник успевал представиться:
-
ClaudeBot;
-
GPTBot;
-
OAI-SearchBot;
-
PerplexityBot;
-
ByteSpider;
-
DeepSeekBot;
-
Facebook crawler.
Картина получалась грандиозная. Все крупнейшие поисковые, социальные и ИИ-компании якобы одновременно собрались у одного сайта и дружно решили проверить старый путь к веб-шеллу.
Либо наступила самая странная корпоративная синергия в истории, либо кто-то печатал чужие удостоверения на домашнем принтере.
11. Почему маска GSCAN не сработала
User-Agent — это не паспорт. Это имя, которое клиент произносит при входе.
Если я зайду в банк и скажу: “Добрый день, я Центральный банк”, охранник не обязан немедленно уступить мне кабинет председателя. С роботами работает тот же принцип.
Официальный краулер подтверждается не одной подписью, а согласованной идентичностью: происхождением сети, устойчивым техническим профилем и поведением, совместимым с заявленной задачей. Поисковый индексатор читает страницы. Он не должен в следующую секунду проверять выполнение команды через старый CGI, а затем переодеваться в конкурирующего ИИ-бота.
Фильтр увидел:
-
общий источник или связанную инфраструктуру;
-
последовательный перебор эксплойтов;
-
несовместимые идентичности в одной кампании;
-
одинаковые технические маркеры под разными именами;
-
синхронное повторение сценария на несвязанных сайтах.
Несколько облачных диапазонов сложились в один граф атаки. Точные признаки объединения и границы кампании мы не публикуем. Источники заблокировали, а поддельные имена известных роботов из оправдания превратились в дополнительную улику.
GSCAN совершил ошибку плохого шпиона. Он взял семь прекрасных паспортов, но менял их, не выходя из комнаты.
12. Главный вывод
Один журнал обмануть легко.
Можно подделать Referer. Можно скопировать User-Agent. Можно запросить несколько картинок. Можно пройти по внутренним ссылкам в правильном порядке. Можно снизить скорость, менять адреса и приходить ночью, когда администратор занят важной задачей — спит.
Но интернет-сессия — это не набор реквизитов. Это причинный процесс.
Настоящий браузер открывает страницу, потому что до неё дошёл пользователь. Ресурсы запрашиваются, потому что браузер разобрал HTML. Следующая страница появляется, потому что человек увидел ссылку и сделал действие. Даже когда часть цепочки скрыта кэшем или блокировщиком, остальные части обычно не начинают противоречить друг другу на десятках адресов и множестве несвязанных сайтов.
Бот может аккуратно подделать каждый отдельный факт. Гораздо труднее подделать отношения между фактами.
Поведенческий фильтр не спрашивает: “Этот IP плохой?” Его интересует другое: “Может ли рассказанная им история быть правдой?”
Человек часто ведёт себя хаотично, но его хаос связан одной жизнью.
Распределённый бот ведёт себя организованно, но его человеческая легенда распадается на части.
Эпилог. Единственный последовательный участник
В начале расследования главным подозреваемым был наш собственный фильтр.
Он собирался осудить посетителя, который приходил из поисковиков, переходил по статьям, загружал изображения и не интересовался уязвимостями. Всё выглядело так, будто машина стала слишком подозрительной и скоро начнёт блокировать людей за то, что они не загрузили favicon.
Мы попытались оправдать человека.
И выяснили, что оправдывать некого.
Поисковые переходы были декорациями. Навигация — сценарием. Ресурсы — археологическим перебором старых картинок. Браузеры — масками, которые переходили от одного адреса к другому.
Человек был выдуман ботнетом.
А единственным участником всей истории, который с самого начала вёл себя последовательно, оказался фильтр.
Не ищите меня в Максе. Свяжитесь со мной в Linkedin: https://www.linkedin.com/in/avetisyan/
ссылка на оригинал статьи https://habr.com/ru/articles/1065790/