Логотип ШифрыШифры
ГлавнаяЖурналЛотыУзел ИИВойти
ГлавнаяЖурналЛотыУзел ИИ
Войти
Логотип ШифрыШифры2К26Версияъ1
EmailMAX
Назад к логу

ИИ нашёл то, что годами пропускали хакеры – и взломал файловый сервер в несколько запросов

18 октября 2026 г.
#атаки#ИИ#шифрование

Три вызова Math.random() при старте процесса. Результат – строка из 30 символов, которой подписывается каждая сессионная кука файлового сервера Rejetto HFS. Разработчик оставил в коде комментарий: «randomness at start gives some extra security». Пять слов, в которых сжата суть уязвимости CVE-2026-61500 с оценкой CVSS 9.3: Math.random() в V8 – не случайность, а xorshift128+, генератор с полностью обратимым состоянием.

Исследователи кибербезопасности из Horizon3 годами натыкались на этот класс – нешифровой PRNG на месте криптографического – и каждый раз проходили мимо. Не потому что баг невидим, а потому что доказательство эксплуатируемости означает садиться за математику: обращать генератор, сцеплять утечку с ключом подписи, собирать солвер. ИИ-модель Anthropic Mythos, работающая в исследовательском пайплайне Horizon3 с июля 2026, за один проход нашла и утечку, и цепочку – и написала рабочий Z3-солвер. 12 HTTP-запросов – и ключ подписи у атакующего. Horizon3 прямо говорит: они не помнят случая, когда SMT-солвер применялся к криптографическому дефекту реального приложения для обхода аутентификации.

Файловый сервер, у которого уже был CVE в списке CISA

Rejetto HFS – проект для раздачи файлов по HTTP с минимальной настройкой. Один исполняемый файл, веб-интерфейс, запуск без конфигурации. Предыдущее поколение (HFS 2.x на Delphi) попало в CISA KEV с CVE-2024-23692 – неаутентифицированная инъекция шаблонов с исполнением произвольного кода. Проект полностью переписали на TypeScript: Node.js, фреймворк Koa, архитектура с нуля. Версии 3.0.0–3.2.0 – те, в которых живёт новая уязвимость.

Horizon3 участвует в Anthropic Project Glasswing – инициативе по поиску уязвимостей в критически важном ПО с помощью ИИ-моделей. Собственная задача Horizon3 в рамках проекта – находить уязвимости, которые злоумышленники с высокой вероятностью найдут и будут массово эксплуатировать. Пайплайн компании запускает несколько специализированных агентов параллельно, каждый ищет свой класс уязвимостей в кодовой базе. Агент криптографического анализа, управляемый моделью Mythos, нашёл в HFS 3.x не одну уязвимость – цепочку.

Ключ от всех сессий из трёх Math.random()

Вся аутентификация HFS стоит на подписи сессионных кук. Фреймворк Koa при старте получает массив ключей для библиотеки keygrip, которая вычисляет HMAC каждой куки. Откуда берётся ключ:

const keys = process.env.COOKIE_SIGN_KEYS?.split(',')
  || [randomId(30)] // randomness at start gives some extra security
export const app = new Koa({ keys })

Переменная COOKIE_SIGN_KEYS в кодовой базе HFS не устанавливается нигде – поиск по репозиторию подтверждает это. Фолбэк срабатывает всегда: ключом подписи становится randomId(30).

Функция randomId берёт Math.random(), конвертирует в base36 через toString(36), вырезает подстроку из 10 символов и рекурсивно склеивает фрагменты до нужной длины. randomId(30) потребляет ровно 3 последовательных вызова Math.random() при старте процесса – и ключ, которым сервер заверяет каждую сессию на всё время жизни процесса, готов.

Нешифровой генератор на месте криптографического – ошибка, которая дорого обходилась и раньше: предсказуемый RNG стоил Coldcard 1 367 биткоинов. Здесь он стоит admin-доступа и исполнения произвольного кода. crypto.randomBytes() – штатный модуль Node.js для криптографической случайности – давал бы на этом месте необратимый результат при нулевой дополнительной сложности: замена одного вызова на другой, одна строка, ноль новых зависимостей. Комментарий разработчика – «randomness at start gives some extra security» – доказывает, что намерение было защитным. Инструмент – нет.

52 бита в каждой куке – и пароль для этого не нужен

Ключ подписи собран из Math.random(). Чтобы его восстановить, атакующему нужны наблюдения – другие выходные значения того же генератора. HFS их раздаёт.

SRP (Secure Remote Password) – протокол аутентификации, встроенный в HFS. Первый шаг хендшейка, обработчик loginSrp1, генерирует идентификатор сессии:

const sid = Math.random()
ctx.session.loggingIn = { username, pubKey, sid }

Значение sid – число с плавающей точкой IEEE-754 double precision, 52 бита мантиссы. Оно записывается в объект сессии, а сессии в HFS хранятся не на сервере – в куках клиента. koa-session по умолчанию держит сессию целиком в куке без серверного хранилища: сериализация JSON, кодирование base64, целостность через HMAC от keygrip. Шифрования нет: содержимое куки – открытый текст для любого, у кого она есть. Клиент декодирует base64, читает JSON и получает точное значение Math.random(), вышедшее из генератора.

Для вызова loginSrp1 достаточно знать имя пользователя – пароль на первом шаге SRP не требуется. Учётная запись admin существует по умолчанию, а её наличие подтверждается через user enumeration oracle – отдельную уязвимость, найденную и раскрытую вместе с основной. Переменная admin_net в конфигурации по умолчанию – пустая строка: сетевых ограничений на admin-доступ нет.

Каждый запрос loginSrp1 – одно наблюдение Math.random(). Привилегии не нужны, доступ к серверу не нужен, пароль не нужен.

Math.random() для идентификатора сессии – вторая проектная ошибка в той же цепочке. crypto.randomUUID() на этом месте давал бы непрозрачный токен, не связанный с состоянием генератора: ответ сервера не сообщал бы о внутренней механике процесса ничего. Math.random() полной точностью IEEE-754 отдаёт 52 бита состояния в каждом ответе, без аутентификации. Первая ошибка без второй не эксплуатируема: ключ предсказуем, но наблюдения взять неоткуда. Вторая без первой безвредна: генератор утекает, но утечка ни к чему не ведёт. Обе вместе – полная цепочка от анонимного запроса до root.

xorshift128+ под капотом V8 – обратимый по построению

V8 – JavaScript-движок Node.js – реализует Math.random() алгоритмом xorshift128+. Внутреннее состояние – два 64-битных целых числа (state0 и state1). На каждом шаге алгоритм выполняет три побитовых операции – два XOR со сдвигом и сложение обоих слов – и выдаёт результат. Все операции линейны над GF(2), полем из двух элементов, где каждый бит рассматривается независимо. Отсюда ключевое свойство: каждый шаг алгебраически обратим. Зная достаточно выходных значений, состояние восстанавливается аналитически.

xorshift128+ – хороший генератор, если от него не требуют того, чего он не обещал: непредсказуемости. V8 выбрал его за скорость и длину периода, не за криптографическую стойкость. У генератора нет претензий на криптографическую случайность – это претензия разработчика HFS.

Нюанс первый – буфер. V8 генерирует числа не по одному, а пачками по 64. Заполненный кэш потребляется в обратном порядке: первый вызов Math.random() после заполнения отдаёт 64-е число, второй – 63-е. Когда буфер заканчивается, генератор выполняет 64 детерминированных шага вперёд и заполняет его заново. Для атакующего это означает, что значения, наблюдённые подряд, не обязательно сгенерированы подряд: они могут принадлежать одному буферу (в обратном порядке относительно генерации) или соседним (с разрывом между ними). Каждый шаг и переход детерминирован и обратим – это вопрос правильного окна.

Нюанс второй – потеря бит. Когда V8 преобразует 64-битный выход xorshift128+ в IEEE-754 double (V8 ToDouble), он берёт старшие 53 бита и масштабирует в диапазон [0, 1). 11 младших бит отбрасываются – они ненаблюдаемы. На каждом наблюдении атакующему приходится перебирать 2 048 вариантов этих бит. Но перебор не полный: кандидатное состояние из первого числа проверяется вторым наблюдением мгновенно, и ложные ветки отсекаются на первом же расхождении.

Z3-солвер и 2.4 секунды до ключа

Z3 – SMT-солвер (Satisfiability Modulo Theories) от Microsoft Research. Механизм, который принимает систему ограничений и находит значения переменных, при которых все ограничения выполняются одновременно. Алгебраическая формула, логическое условие, битовый вектор – Z3 работает со всеми. В контексте xorshift128+: state0 и state1 – неизвестные 64-битные векторы, уравнения генератора – ограничения, наблюдённые doubles – выходные значения. Солвер находит решение или доказывает его отсутствие.

Mythos указал на Z3 как на стандартный инструмент для этой задачи. Horizon3 отмечает, что без модели их первым подходом к атаке на генератор был бы перебор – а модель сразу выбрала алгебраический путь, которого исследователи не припомнят ни в одном прецеденте атаки на криптографический дефект реального приложения.

Публичный PoC для CVE-2026-61500 идёт алгебраическим путём: берёт 5 последовательных doubles из 6 запросов loginSrp1, перебирает 11 отброшенных младших бит и обращает рекуррентное соотношение xorshift128+ напрямую. PoC перебирает смещения наблюдений внутри 64-элементного буфера V8 и проверяет варианты ToDouble (shift11 или shift12), пока все наблюдения не воспроизведутся бит-в-бит. Оба подхода – Z3 и алгебраическое обращение – решают одну задачу; первый выразительнее, второй быстрее.

Восстановленное состояние прокручивается назад – через границы буферов, через шаги заполнения – к тем трём вызовам Math.random() при старте процесса. Из трёх doubles детерминированно вычисляются три base36-фрагмента: toString(36), обрезка до 10 символов, замена строчной l на прописную L – и склеиваются в 30-символьный ключ подписи. В кодовой базе HFS единственные потребители Math.random() на стороне сервера – ключ подписи (3 вызова при старте) и sid – пространство поиска стартового смещения минимально.

Верификация бесплатна. Атакующий берёт свою легитимно полученную куку, вычисляет HMAC кандидатным ключом и сравнивает с подписью сервера. Совпадение – ключ верный. В PoC на всё уходит 2.4 секунды.

Mythos увидел цепочку там, где исследователи видели стену

Horizon3 описывает свой опыт прямо: их исследователи неоднократно обнаруживали нешифровой PRNG на месте криптографического и отступали. Первая причина – отсутствие глубокой математической подготовки в криптографии. Вторая – экономика: уязвимость, требующая значительного времени на понимание и разработку эксплойта, не попадает в приоритет.

Mythos снял оба ограничения. Horizon3 выделяет три области, в которых модель превосходит ожидания: научные задачи в области информатики и внутренней механики ОС, математические дистилляции и задачи с длинным горизонтом. Агент криптографического анализа получил исходный код HFS и задачу. Без наводящих промптов, без указания направления. Вывод модели в переводе из отчёта Horizon3:

Когда COOKIE_SIGN_KEYS не установлена, ключ Koa keygrip – randomId(30) (index.ts:41-42), а randomId (cross.ts:334-341) построен на Math.random().toString(36) – V8 xorshift128+, нешифровой и обратимый. Процесс утекает сырые Math.random() удалённым клиентам: loginSrp1 записывает sid = Math.random() (api.auth.ts:78) в ctx.session.loggingIn, а куки koa-session – base64 JSON, клиент получает точный 52-битный double в Set-Cookie. Последовательные doubles позволяют восстановить состояние xorshift128+ (стандартный Z3/алгебраический тулинг); состояние прокручивается назад к выходам randomId(30) при старте, кандидатный ключ верифицируется HMAC-подписью собственной легитимно полученной куки. С восстановленным ключом – подделать {username:'admin', ts:now, allow_session_ip_change:true}, пройдя prepareState, invalidateSessionBefore и проверку IP.

Верифицирующий агент перепроверил каждый тезис по коду. COOKIE_SIGN_KEYS не устанавливается нигде в репозитории. randomId(30) потребляет ровно 3 последовательных Math.random() при старте. Комментарий разработчика подтверждает защитное намерение – исключение «not for security purposes» не работает. sid полной точностью записывается в сессию. Сессии хранятся в куках, не на сервере. Куки подписаны, но не зашифрованы. Единственные серверные потребители Math.random() – ключ и sid – пространство поиска минимально.

Horizon3 подчёркивает отдельно: модель не просто нашла нешифровой PRNG. Mythos одновременно обнаружил утечку через отдельный путь кода, опознал оба факта как одну эксплуатируемую цепочку и определил, что утечка даёт ровно те наблюдения, которые нужны для восстановления состояния генератора. Дополнительных промптов не потребовалось. После этого модель написала полный proof of concept – Z3-солвер с ограничениями, ковку куки и демонстрацию исполнения кода.

Сами исследователи формулируют впечатление так: Mythos с ходу определил, что восстановление криптографического сида жизнеспособно, и понял, как практически атаковать его. Люди, которые редко берутся за вепонизацию криптографических дефектов, увидели, как модель делает это за один проход.

От поддельной куки до root

Полная цепочка эксплуатации – от первого запроса до исполнения кода.

Схема: V8 xorshift128+ порождает ключ подписи при старте и sid при каждом loginSrp1, sid утекает через куку, атакующий восстанавливает состояние генератора и прокручивает его назад к ключу, затем куёт admin-куку и получает RCE
Один поток xorshift128+ порождает и ключ подписи, и sid – утечка sid через куку восстанавливает ключ

Атакующий проверяет наличие admin через user enumeration oracle. Дальше собирает наблюдения Math.random(): разбор Horizon3 закладывает 12 обращений к утечке, публичный PoC обходится шестью запросами loginSrp1. Каждый запрос даёт один sid полной точностью IEEE-754. Извлекает doubles из кук: base64, JSON, поле sid. Подаёт наблюдения в солвер и за секунды получает внутреннее состояние xorshift128+. Прокручивает его назад к трём вызовам Math.random() при старте. Реконструирует ключ подписи – 30 символов base36. Верифицирует кандидат HMAC-подписью собственной куки.

Ключ восстановлен. Атакующий формирует сессионный объект:

{
  "username": "admin",
  "ts": 1790000000,
  "allow_session_ip_change": true
}

Поле allow_session_ip_change обходит привязку сессии к IP: при ковке атакующий подписывает куку сам и включает любой флаг. Метка ts – текущее время. invalidateSessionBefore – пустая Map по умолчанию: для admin нет метки принудительного сброса. ctxAdminAccess предоставляет административный доступ по username в состоянии контекста – подпись совпадает, в сессии стоит admin, доступ получен.

Последний шаг – server_code. Административный API HFS позволяет задать произвольный JavaScript через конфигурацию, который сервер исполняет. Атакующий вызывает set_config, записывает свой код – и тот исполняется с привилегиями процесса. В демонстрации PoC – uid=0(root), полный захват.

Терминал: PoC сканирует окна буфера V8, восстанавливает состояние PRNG и ключ подписи, захватывает admin, выполняет команду с uid=0(root)
PoC восстанавливает ключ подписи и получает root за 2.4 секунды

Одна строка в 3.2.1, закрывшая всю цепочку

HFS 3.2.1 – первый исправленный релиз. Ключ подписи больше не собирается из Math.random(): вместо randomId(30) используется непрозрачный UUID. Ответ loginSrp1 в исправленной версии тоже содержит UUID вместо числа Math.random() – PoC, запущенный против 3.2.1, завершается с кодом 3, не добравшись до ковки. Обходная мера – явная установка COOKIE_SIGN_KEYS через переменную окружения: достаточно длинный случайный ключ, и фолбэк на randomId(30) не сработает.

CVE-2026-61500 получила оценку CVSS 9.3 (Critical) по версии 4.0: сетевой вектор, низкая сложность, без привилегий и без взаимодействия пользователя, максимальное воздействие на конфиденциальность, целостность и доступность. CWE-338 – использование криптографически слабого PRNG.

Два бесплатных фикса и барьер, которого больше нет

Обе проектные ошибки в цепочке – тривиальные. crypto.randomBytes() вместо Math.random() для ключа подписи. crypto.randomUUID() вместо Math.random() для sid. Ноль дополнительных строк, ноль новых зависимостей, идентичная семантика, необратимый результат. Цена ошибки: полный захват сервера из 12 HTTP-запросов без аутентификации.

Но тривиальность ошибки – не причина, по которой она жила в коде. Причина – математический барьер между наблюдением «здесь стоит нешифровой PRNG» и рабочим эксплойтом. Барьер из xorshift128+, обращения состояния, SMT-солверов и алгебры над GF(2). Для исследователей Horizon3 – людей с обширным опытом – этот барьер каждый раз оказывался достаточной причиной развернуться.

Mythos прошёл его за один проход. Модель той же линейки, что вскрывала 7 раундов AES, здесь нашла ключ от сервера. Не пометила баг как «потенциально опасный» – построила полную цепочку, написала солвер и продемонстрировала исполнение кода.

Horizon3 формулирует следствие прямо: классы уязвимостей, которые злоумышленники сочтут пригодными для вепонизации, изменятся. Раньше типичной уязвимостью, эксплуатируемой массово, был простой обход аутентификации с готовым исполнением кода. Теперь более сложные и менее надёжные классы багов станут доступны – автоматизация превращает каждый из них в эксплойт с предсказуемыми затратами. Модели 2025 года находили уязвимости, но требовали серьёзной инженерии пайплайна: контекстно-зависимый промптинг, фильтрация релевантных файлов и функций, специализированные системы. Текущее поколение моделей сделало многое из этого ненужным – стандартный пайплайн с промптом «найди уязвимости» даёт результат для проектов, не проходивших аудит.

Для проектов, которые аудит проходили, Horizon3 ставит вопросы следующего порядка: насколько легко злоумышленник автоматизирует построение ROP-цепочек между архитектурами, и насколько элегантное решение для вепонизации бага работы с памятью способен создать агент. Исследователи видят в себе ту часть процесса, которую модель пока не заменяет: инженерное суждение о том, куда вложить усилие – какой проект стоит специализированного пайплайна, а какой достаточно прогнать за ночь.

Разбор уязвимости и proof of concept опубликованы Horizon3.

Автор: hacker@shifry.local