FlawOpen

flawopen.com/Teardowns/cve-2026-84973-jupyterlab-snapshot-checkout-toctou

● CVE-2026-84973 · Высокий
Исследования · FlawOpen

CVE-2026-84973: TOCTOU-гонка в snapshot workflow JupyterLab

Проверка времени с точностью до секунды могла позволить snapshot workflow получить и выполнить неавторизованный commit pull request с правами задания. Обновите update-snapshots-checkout до версии 1.0.0 или новее.

💡 Простыми словами (ELI5)

Рецензент одобряет конкретную посылку, но на пункте выдачи сверяют только секунду на часах. Если посылку подменить в ту же секунду, замена выглядит одобренной. Action сравнивала время с точностью до секунды и принимала равные значения, поэтому обновление pull request могло пройти одновременно с комментарием сопровождающего.

Ключевые понятия и термины

TOCTOU
Time-of-check to time-of-use: решение о безопасности принимается для ресурса, который изменяется до его использования.
Head SHA pull request
Неизменяемый идентификатор commit на вершине ветки pull request. Workflow должен выполнять только точно одобренный commit.
Права токена GitHub Actions
Операции, разрешённые GITHUB_TOKEN задания, например чтение или изменение содержимого репозитория и pull request.
CWE-367 / CWE-829
В advisory гонка времени отнесена к CWE-367, а выполнение функциональности, контролируемой pull request, — к CWE-829.

Связи со стандартами

  • Ссылка CWE для слабого места, описанного в этом руководстве: CWE-367 CWE-829

Анализ первопричины

До версии 1.0.0 update-snapshots-checkout получала pushed_at pull request и created_at запускающего комментария, переводила их в секунды Unix и отклоняла запуск только если pushed_at строго больше. Оба значения API имеют точность до секунды. Поэтому отправка изменений и разрешённый комментарий в одну секунду могли дать равные значения и пройти проверку. Затем action сохраняла текущий head SHA и проверяла его после checkout; это подтверждало лишь соответствие checkout только что полученному SHA, но не то, что сопровождающий разрешил именно этот commit. В исправлении upstream -gt заменён на -ge, чтобы отклонять равенство и более поздние отправки. Риск возникает при использовании уязвимой версии action и последующем выполнении содержимого pull request; последствия ограничены правами и учётными данными workflow.

Пошаговый сценарий атаки

Шаг 1

Доверенный сопровождающий запускает workflow

Создаётся pull request, а OWNER, COLLABORATOR или MEMBER публикует комментарий, запускающий workflow обновления snapshots.

Шаг 2

Изменение атакующего совпадает по времени с комментарием

Участник обновляет ветку в ту же секунду. Из-за секундной точности API значения времени Unix могут совпасть.

Шаг 3

Строгое сравнение принимает равные значения

Уязвимая action отклоняет запуск только когда pushed_at больше created_at. Равенство проходит условие, и сохраняется новый head SHA.

Шаг 4

Workflow получает этот commit и может его выполнить

Checkout совпадает с сохранённым SHA и проходит последующую проверку. Если следующие шаги запускают actions или скрипты из pull request, они получают права и учётные данные задания.

Шаг 5

Обновите action и ограничьте права

Обновите до 1.0.0 или новее. Закрепляйте actions за неизменяемыми SHA, выдавайте GITHUB_TOKEN только необходимые права и не передавайте секреты заданиям с недоверенным кодом pull request.

Исходный код: Уязвимый vs Защищённый вариант

Это точное однострочное исправление upstream: -ge отклоняет равенство при доступной точности API. Оно закрывает описанный обход в ту же секунду. Тем не менее разрешение следует связывать с неизменяемым SHA и минимизировать права: порядок времени не заменяет неизменяемую проверку авторизации. В upstream diff диагностическое сообщение не меняется: в нём сказано «после», хотя теперь отклоняется и равенство.

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
    echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
    exit 1
fi
БЕЗОПАСНЫЙ ИСПРАВЛЕННЫЙ ВАРИАНТ
if [[ $(date -d "$pushed_at" +%s) -ge $(date -d "$COMMENT_AT" +%s) ]]; then
    echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
    exit 1
fi

Чек-лист по защите системы для инженеров

Источники