12 бит в секунду из соседнего воркера: как удалённый таймер обошёл защиту Cloudflare Workers

17 августа в arXiv выложили препринт «Remote-Timer-as-a-Service: Efficient Microarchitectural Leakage in the Cloud with Remote Timers». Мартин Шварцль, Хаочэн Сяо, Альберт Педерсен, Сэм Эйнсворт и Найджел Топхэм вытащили JWT из памяти чужого воркера на Cloudflare Workers — со скоростью 12 бит в секунду и точностью 99,16 %. Cloudflare отвечает, что защиту доработали ещё в 2025 году. Но интереснее не сам факт утечки, а вопрос: как вообще меряют наносекунды на платформе, где часов в рантайме нет по умолчанию.

Один процесс на тысячу арендаторов

Workers не даёт каждому клиенту отдельный процесс. Документация Cloudflare объясняет это прямым текстом: «if Cloudflare had to run a separate process for every guest, the number of tenants Cloudflare could support would be drastically reduced». Вместо процессов — изоляты V8: границу между арендаторами держит не ядро операционной системы, а сам движок JavaScript.

Info:

Изолят V8 — независимый экземпляр движка со своей кучей и своим глобальным объектом. Внутри одного процесса их могут быть тысячи. Для JavaScript-кода они полностью разделены. Для процессора — это одна и та же физическая память и общие кеши.

Spectre ломает ровно эту модель: спекулятивное выполнение ничего не знает про изоляты, оно знает про адреса. Cloudflare дополнительно режет машины на «кордоны» — раскладывает арендаторов разных тарифов по разным экземплярам рантайма, но соседи внутри кордона остаются.

Таймер, которого нет

Любая атака по кешу упирается в измерение времени, и именно здесь Cloudflare закрутила гайки: Date.now() не двигается во время выполнения кода — значение обновляется только после операции ввода-вывода. Потоков нет, SharedArrayBuffer нет, самодельный счётчик из гонки двух потоков не собрать.

Ответ авторов — вынести часы за пределы платформы: время меряет сервер атакующего по сетевым ответам, а разницу в один бит перед этим раздувают микроархитектурным усилением до величины, заметной даже через сеть. По разбору The Hacker News, роль удалённого таймера играет обмен по WebSocket, а изолят держат живым между запросами через Durable Objects; в самом абстракте эти детали не раскрыты.

Детектор, который научились не будить

Забавная деталь: защиту, которую пробили, придумала та же группа. В 2021 году Шварцль с соавторами показали утечку 120 бит в час и предложили Dynamic Process Isolation — переселять в отдельный процесс только тех воркеров, у кого счётчики производительности выглядят подозрительно (заявленная доля ложных срабатываний — 0,61 %). Новая работа констатирует: «the production implementation of DyPrIs was insufficient».

Разрыв в скорости стоит перевести в понятные единицы. 12 бит/с — это 43 200 бит в час, в 360 раз больше прошлого результата. Токен на 300 байт (2400 бит) при такой скорости утекает примерно за три с половиной минуты вместо двадцати часов — это уже не лабораторный курьёз, а рабочее окно. Пересчёт мой, в самой работе такой оценки нет.

Что закрыли и почему это не финал

По абстракту препринта, после согласованного раскрытия Cloudflare выкатила три вещи: ограничения V8 Sandbox, доработанное обнаружение в DyPrIs и аппаратную изоляцию кучи арендатора через memory protection keys. Часть этого описана в блоге компании «Safe in the sandbox» от 25 сентября 2025 года. Заявление, что признаков эксплуатации за три года не видно, — это позиция вендора, независимо её никто не проверял.

Warning:

Цикл замкнулся второй раз: атака → защита → обход защиты. Сама Cloudflare пишет в документации: «Spectre does not have an official solution. Not even when using heavyweight virtual machines». Считать вопрос закрытым нельзя ни на одной мультиарендной платформе.

Практический вывод отсюда — не про Cloudflare. Языковая изоляция дешевле процессной, и её выбирают везде, где важна плотность: общий хостинг, чужие бессерверные рантаймы, ваш собственный сервис, исполняющий пользовательский JavaScript в одном процессе Node. Если в вашей модели угроз есть сосед по железу, граница проходит не там, где кажется по коду.

Success:

Что можно сделать прямо сейчас, не дожидаясь следующей работы:

  • посмотреть реальное время жизни своих токенов — чем оно короче, тем меньше окно для медленной утечки;
  • не держать долгоживущие ключи в памяти edge-функций: запрашивать по требованию и не кешировать в глобальной области изолята;
  • для действительно секретных вычислений выбирать окружение с процессной или аппаратной границей, а не с языковой.
JWT='eyJhbGciOi...'   # свой токен
python3 - <<'EOF'
import base64, json, os
t = os.environ["JWT"].split(".")[1]
p = json.loads(base64.urlsafe_b64decode(t + "=" * (-len(t) % 4)))
print("iat", p.get("iat"), "exp", p.get("exp"), "→ живёт", p["exp"] - p["iat"], "с")
EOF

Экспортируйте JWT перед запуском (export JWT='...'). Если разница между exp и iat измеряется сутками, у любой медленной утечки в вашем случае бесконечное окно; если минутами — украденный токен успевает протухнуть раньше, чем его дочитают.

Смежные разборы на форуме: автовставка скриптов Cloudflare на статичный сайт и пять дней с живым токеном в GitHub Actions.

Источники

Question:

А вы вообще считаете соседей по железу частью своей модели угроз? Или до сих пор исходите из того, что «облако как-нибудь разберётся»? Интересно, есть ли здесь те, кто из-за таких историй сознательно ушёл с бессерверных платформ на свои VPS.