Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
AfterLife — Лаборатория по обнаружению сохранения отзыва: когда сброс пароля проходит успешно, но атакующий не уходит. Воспроизводит баг условного отзыва Strapi CVE-2026-22706, его исправление, набор из трёх правил обнаружения и наивное правило, которое его пропускает. | Kitploit
Инструменты/GitHubGitHub/het-p301204/afterlife
Оборонительные ИнструментыАнализ уязвимостейВеб-безопасностьАутентификацияОбучение и ОбразованиеRed TeamingРеагирование на ИнцидентыЛаборатории и Практика
GitHubhet-p301204/afterlife

AfterLife

Лаборатория по обнаружению сохранения отзыва: когда сброс пароля проходит успешно, но атакующий не уходит. Воспроизводит баг условного отзыва Strapi CVE-2026-22706, его исправление, набор из трёх правил обнаружения и наивное правило, которое его пропускает.

12 ч 41 мин назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Репозиторий
Поделиться

AFTERLIFE

Лаборатория обнаружения сохранения отзыва

Когда сброс пароля проходит успешно, но атакующий так и не уходит.

Локальная red/blue-лаборатория для одного класса уязвимостей: учётные данные, которые переживают событие, которое должно было их уничтожить. Она поставляет эксплойт, первопричину, исправление, пакет обнаружения из трёх правил, аудит состояния для того, что правила структурно не могут увидеть, криминалистическую консоль — и правило обнаружения, которое не работает, сохранённое в репозитории, чтобы продемонстрировать его отказ.

CI tests python rules OWASP CWE

license

Быстрый старт · Находка · Почему наивное обнаружение не работает · Пакет правил · Консоль · Исправление · Матрица · Тесты · Документация


Одна и та же атака против обеих реализаций. Одна полоса на учётные данные, от выдачи до смерти, сгруппированные в линии. Пунктирная линия — смена пароля. В уязвимом режиме пять полос пересекают её и продолжаются; в исправленном режиме каждая полоса в украденной линии останавливается на ней.

Одна и та же атака. Одни и те же запросы. Одно отличие. Сгенерировано scripts/figures.py из той же полезной нагрузки, которую рисует консоль — перегенерируется и сравнивается в CI, поэтому рисунок не может разойтись с кодом.


Действие по локализации, которое не локализует```text

09:00 alice logs in ┐ refresh credential rt-001 │ the attacker steals rt-001 ┘

10:00 alice changes her password ← the one thing a victim can do alone HTTP 200 · password changed · fresh session issued

10:00:03 attacker: POST /refresh rt-002 → HTTP 200 new access credential at-004, issued 10:00:03

10:01:00 attacker: GET /me at-004 → HTTP 200 {"username": "alice", "authenticated": true}

root@kitploit:~
Ни ошибки. Ни аномалии. Ни одной неудачной аутентификации, которую можно было бы посчитать. Учётные данные доступа атакующего существуют *всего три секунды* и были выпущены сервером по запросу после сброса.

Вот и вся уязвимость:```python
def _revoke_for_security_change(self, user, kind, device_id):
    if device_id:
        revoke_credentials(user, device_id=device_id)   # ← the finding

Прочитайте это так, как это сделал бы рецензент. Логика отзыва прямо здесь. Она вызывает нужную функцию с нужной областью действия. Окружающий её эндпоинт обновляет хеш пароля, возвращает 200 и выдаёт новую сессию — всё наблюдаемое поведение корректной смены пароля присутствует.

А если вызывающая сторона опускает device_id, ничего не отзывается, и эндпоинт всё равно сообщает об успехе.

Это не гипотетический случай. Это CVE-2026-22706 в Strapi ≤ 5.33.2, где шаг инвалидации refresh-токена был обусловлен передаваемым вызывающей стороной deviceId. Оценка 2.1, Low. Обсуждается ниже.


Почему это важно

Оценка низкая, потому что у атакующего уже был доступ — это входное условие, и эта ошибка не даёт ничего нового. Что она даёт — это длительность, и делает она это, ломая единственный контроль, которым жертва может управлять сама.

  • Каждый сценарий реагирования на захват аккаунта начинается со слов сбросьте пароль. Каждый продукт говорит пользователю одно и то же.
  • Когда это молча не срабатывает, жертве сообщают, что проблема решена, и она перестаёт искать — что отключает сигнал обнаружения, наиболее важный при захвате аккаунта: то, что пользователь замечает.
  • Окно сохранения — это время жизни refresh-учётных данных: 30 дней по умолчанию, продлеваемое бесконечно путём ротации. test_persistence_lasts_as_long_as_the_refresh_credential проходит семь дней смоделированного времени, чтобы показать это.
  • Нет второго действия, которое пользователь может предпринять. Вторая смена пароля делает ровно столько же, сколько и первая.

Ошибка с низкой оценкой в пути локализации стоит дороже, чем ошибка с низкой оценкой в пути функциональности, потому что цена платится во время инцидента, когда никто не читает рекомендации.


Наивный детектор

Правило, которое вы пишете первым:```text IF credential.issued_at < credential_change.timestamp: ALERT

root@kitploit:~
Это не глупое правило. Оно дешёвое, требует одного поля, читается как определение
проблемы и **верно для простого случая** — злоумышленник, использующий
украденный *access*-токен после сброса, будет пойман.
`test_the_naive_rule_catches_the_simple_case` подтверждает, что оно работает.

Оба правила задают один и тот же вопрос об одном и том же учётном данных. Они читают разные
поля, чтобы ответить на него, и эти два поля противоречат друг другу:

![Две строки, по одной на поле. credential.issued_at показывает 10:00:03, три секунды после изменения, и заключает, что учётные данные выглядят чистыми. lineage.root_issued_at показывает 09:00:00, за час до изменения, и срабатывает тревога.](https://raw.githubusercontent.com/het-p301204/afterlife/main/docs/figures/two-fields.svg)

**Три секунды после или за час до — одни и те же учётные данные, в один и тот же
момент.** Наивное правило спрашивает поле, которое контролирует злоумышленник, и один
`POST /refresh` его сбрасывает.

Запустите его по журналу запросов, где этот запрос на самом деле и пишется,
потому что журналы запросов — это то, что у вас есть:```text
NAIVE DETECTOR (NAIVE-001), over the request log

  Result: NO ALERT

  Six requests were served to the attacker after the reset. Every one of them
  carried at-004, minted at 10:00:03 -- three seconds *after* the password
  change. By its own timestamp it is the newest credential on the account.

Было бы легко на этом остановиться, но это было бы нечестно, поэтому в лаборатории также применяется самая справедливая версия наивного правила — расширенная для отслеживания также эндпоинта обновления:```text NAIVE DETECTOR, widened to include POST /refresh

1 alert at 2026-09-11T10:00:03.000Z: the credential presented to /refresh was rt-002, issued 2026-09-11T09:30:00.000Z. Then it goes blind. 6 events follow that hop and it flags none of them, because every credential from there on carries a post-reset timestamp. Its incident covers 1 accepted request; the lineage rule's covers all of them.

And look at what the alert names: NAIVE-001 revoke rt-002 -- rotated away and already dead at 10:00:03 AFTERLIFE-001 revoke lin-001 -- the live thing every future credential descends from

root@kitploit:~
Вот в чём разница, которая имеет значение в три часа ночи. Наивное правило ловит единственное мгновение, когда цепочка пересекает границу, а затем теряет след на оставшиеся 29 дней — и учётные данные, которые оно называет, *уже были ротированы и отозваны сервером*. Их отзыв ничего не даёт. `lin-001` — это объект, который нужно уничтожить.

**И наивное правило не было обделено телеметрией.** Оно использует тот же поток событий, тот же индекс происхождения, тот же допуск, ту же дедупликацию и то же ограниченное состояние. Оно переопределяет один метод:```python
class Correlator:                                  # AFTERLIFE-001
    def _age_reference(self, facts):
        return facts.root_issued_at

class NaiveCorrelator(Correlator):                 # NAIVE-001
    def _age_reference(self, facts):
        return facts.issued_at

root_issued_at находится в индексе, который уже используется. test_the_naive_rule_had_the_data_it_needed доказывает это. Ошибка в сравнении, а не в логировании.


Пакет правил

Три правила по одному журналу. Они отвечают на разные вопросы и срабатывают в том порядке, в котором инцидент действительно разворачивается.

правилосерьёзностьотвечает на вопроссрабатывает
AFTERLIFE-002Повторное использование обновлённых учётных данныхCRITICAL / MEDIUMбыли ли они украдены?при повторном использовании
AFTERLIFE-003Неполный отзыв при изменении в системе безопасностиHIGH / LOWсработала ли локализация?при изменении — атакующий не нужен
AFTERLIFE-001Использование цепочки учётных данных после отзываHIGHиспользовалась ли устаревшая цепочка?при первом принятии
RULE PACK

10:00:00 HIGH AFTERLIFE-003 Incomplete revocation at a security change 10:00:03 HIGH AFTERLIFE-001 Post-revocation credential lineage use

root@kitploit:~
**Этот порядок — самое полезное в этом репозитории.** AFTERLIFE-003
срабатывает в момент, когда изменение вступает в силу, за три секунды до того, как атакующий
что-либо коснётся, потому что необходимые ему доказательства уже полны: журнал говорит, какие
линии были активны на входе, и не говорит, что они были отозваны.

Ему не нужна ни жертва, ни эксплуатация. Он сообщит о дефекте при первом
сбросе пароля, который выполнит любой пользователь, — что делает его тем самым, который вы запускаете в staging,
где нет атакующего, которого нужно ждать. AFTERLIFE-001 сообщает вам, что вторжение
продолжается; AFTERLIFE-003 сообщает вам, что ваш контроль сдерживания сломан.

![Две карточки оповещений. AFTERLIFE-003 в 10:00:00 сообщает вердикт no_containment, watermark не записан, область none и lin-001 выживший. AFTERLIFE-001 в 10:00:03 сообщает учётные данные rt-002 с root, выданные за час до изменения.](https://raw.githubusercontent.com/het-p301204/afterlife/main/docs/figures/findings-vulnerable.svg)

`application recorded watermark: no` — это поле, которое указывает специалисту по реагированию на инциденты на *дефект*, а не на симптом.

### Watermark, который используют правила, — не тот, о котором сообщает приложение

Событие изменения учётных данных несёт поле `revocation_watermark` — значение
`credentials_valid_after`, которое записало приложение. **В уязвимой
реализации оно равно `null`, потому что приложение его никогда не записывало.** Это и есть
баг.

Таким образом, правило, привязанное к этому полю, было бы слепо именно к тому случаю, для перехвата которого оно существует. Правила привязываются к собственному `timestamp` события, который верен независимо от того, выполнило ли приложение свою работу, и сообщают об отсутствующем поле как о доказательстве.

### Свойства```text
  deduplication        6 accepted requests on the stale lineage -> 1 alert
  event order          shuffled stream -> same alert  (1 alert)
  duplicate telemetry  log replayed twice -> 1 alert  (24 duplicate events discarded)
  false positives      the fixed implementation's log -> 0 alerts
  bounded state        caps at 256 activity/user, 2000 users, 20000 credentials

Независимость от порядка — это не «в основном работает»: test_6b_every_permutation_of_the_critical_events_detects прогоняет все 24 порядка четырёх значимых событий и требует ровно одного оповещения от каждого. Дедупликация выполняется по ключу (user_id, credential_change_event_id, lineage_id), с подавлением по event_id перед ней, чтобы воспроизведённый файл не мог завысить счётчик.

Полные карточки правил, необходимая телеметрия и инструкции по реагированию: docs/detection.md.


Консоль

Поверхность для чтения, отвечающая на единственный вопрос, вокруг которого построена лаборатория. Не дашборд с количеством оповещений — а реестр смертности. Одна полоса на каждый учётный данные, от выпуска до смерти, сгруппированные по происхождению, из которого они происходят, со сменой пароля, нарисованной как линия, на которой всё до неё должно было закончиться.

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

Одна намеренная инверсия: тепло означает жив, а после линии тепло — это неправильно. В большинстве интерфейсов безопасности красный означает, что произошла ошибка. Здесь ничего не ошибается — каждый запрос в уязвимом прогоне возвращает 200. Поэтому цвет следует за смертностью: холодный — это учётные данные, которые умерли, когда им было велено, тёплый — те, что ещё дышат, а за гильотиной то, что всё ещё тёплое, и есть вся находка. (BLACKOUT установил ту же конвенцию, где 200 был красным.)

Тонкие вертикальные штрихи — это генеалогия: дочерние учётные данные, созданные из родительских в этот момент. rt-001 → rt-002 → rt-004 спускается вниз и вправо через всё окно, и в уязвимом режиме продолжает шагать после отсечения — а это и есть картина происхождения, создающего новые учётные данные по другую сторону от собственного события вымирания.```bash python scripts/lab.py console

root@kitploit:~
записывает **[`docs/console-preview.html`](https://github.com/het-p301204/afterlife/blob/main/docs/console-preview.html)** — автономный файл размером 74 КиБ с обоими запусками, встроенными внутрь. Ни сервера, ни сети, ни шрифтов для загрузки; откройте его прямо из файловой системы. Или запустите живую версию:```bash
python -m console

Перетащите ползунок назад за 10:00:00 и вперёд через него: находки появляются, когда журнал их заслуживает, AFTERLIFE-003 при изменении и AFTERLIFE-001 три секунды спустя. Обоснование дизайна, включая то, что было вырезано, находится в docs/console-design.md.

Консоль по построению доступна только для чтения. Она повторно запускает завершённые сценарии и отрисовывает их; она не может переключить реализацию, перевести часы или отозвать что-либо. test_the_console_has_no_control_surface утверждает, что единственный не-GET маршрут — это /api/rebuild. Поверхность для чтения, которая может изменить то, что она читает, — это то, чему нельзя доверять.


Архитектура

Шесть пакетов, один класс безопасности, никакой инфраструктуры.```text app/ the lab application config.py mode selection; defaults to fixed, deliberately store.py SQLite credential state + the security-change audit trail tokens.py minting and decoding; a JWT is a signed pointer to a row auth.py login / refresh / change-password + THE BUG + the audit main.py six endpoints

common/ clock.py a rewindable UTC clock, so an hour of history costs nothing events.py the event vocabulary, shared by app and detector telemetry.py JSONL emission with credential-name redaction

detector/ strictly downstream: reads a log file, decides nothing base.py the alert shape, replay suppression, bounded state lineage.py rebuilds a credential's ancestry from issuance events ledger.py which lineages are alive, per user rules.py AFTERLIFE-001 persistence reuse.py AFTERLIFE-002 theft containment.py AFTERLIFE-003 the defect itself naive.py NAIVE-001, kept in order to be demonstrated failing audit.py the state scan the rules structurally cannot do engine.py the pack: one shared index, one stream, ranked alerts tail.py byte-offset JSONL tailer

console/ the mortality register payload.py one run, shaped for drawing static/ ~1400 lines of vanilla HTML/CSS/JS, no build step

scripts/ lab.py the demonstration scenarios.py nine scenarios x both implementations report.py the incident report preview.py bake the offline console figures.py render the console to SVG for this README

tests/ 290 tests

root@kitploit:~
Приложение работает **внутри процесса** под управлением демо, консоли и тестов. Никаких
портов, никакого dev-сервера, никакого Docker. Часы зафиксированы, поэтому каждый запуск
детерминирован, а закоммиченная телеметрия, консоль, графики и отчёты побайтово
идентичны между запусками — что CI проверяет с помощью `git diff --exit-code`.

### Принудительное применение на стороне сервера

Учётные данные — это **строка**, а не строка символов. JWT, который держит клиент, несёт `cid` —
ключ для поиска — и именно строка решает, жив ли учётный доступ.

Срок действия проверяется по `expires_at` строки, а не по утверждению `exp`, которое
предъявил держатель. Оба временных утверждения передаются в PyJWT с `verify=False`,
что здесь означает *«сервер проверяет это сам»*, и [`app/auth.py`](https://github.com/het-p301204/afterlife/blob/main/app/auth.py)
делает это при каждом запросе. Клиент никогда не получает права голоса в вопросе о том,
действителен ли его собственный учётный доступ.

---

## Хронология атаки```mermaid
flowchart TD
    A["Account compromised<br/><i>phishing · XSS · stolen backup</i>"] --> B["Attacker holds refresh credential rt-001<br/>lineage lin-001, root sess-001 @ 09:00"]
    B --> C["Legitimate password change @ 10:00<br/>HTTP 200 · password hash updated"]
    C --> D{"device_id supplied?"}
    D -->|"yes"| E["revoke_credentials(user, device_id)<br/>rt-001 revoked"]
    D -->|"no — the exploit"| F["nothing revoked<br/>no watermark written"]
    E --> G["Attacker refresh → 401<br/><b>contained</b>"]
    F --> AF3["<b>AFTERLIFE-003 · HIGH</b> @ 10:00:00<br/>containment did not run<br/><i>no attacker action required</i>"]
    F --> H["POST /refresh rt-002 → 200<br/>mints at-004 @ 10:00:03"]
    H --> I["GET /me at-004 → 200<br/>well-formed · correctly signed · <b>no anomaly</b>"]
    I --> J["at-004.issued_at > watermark<br/>NAIVE-001: no alert"]
    I --> K["lineage lin-001 root @ 09:00 < watermark<br/><b>AFTERLIFE-001 · HIGH</b>"]
    F --> L["attacker holds and never spends<br/>AFTERLIFE-001 silent — correctly<br/><b>state audit: dormant survivor</b>"]

    style F fill:#7f1d1d,color:#fff
    style H fill:#7f1d1d,color:#fff
    style I fill:#7f1d1d,color:#fff
    style J fill:#78350f,color:#fff
    style K fill:#14532d,color:#fff
    style AF3 fill:#14532d,color:#fff
    style L fill:#1e3a5f,color:#fff
    style E fill:#14532d,color:#fff
    style G fill:#14532d,color:#fff

Ветвь цепочки обновления в середине — это самое главное. Атакующий не использует старые учётные данные. Он использует старую линию происхождения, и эта линия создаёт что-то новое по требованию.


Линия происхождения токенов

Сессия — это корень линии происхождения. Всё, что создано под ней, наследует этот корень навсегда.```text sess-001 session lineage lin-001 root sess-001 issued 09:00:00 │ ├── rt-001 refresh parent sess-001 root_issued_at 09:00:00 │ └── at-001 access parent rt-001 root_issued_at 09:00:00 │ ├── rt-002 refresh parent rt-001 root_issued_at 09:00:00 ← 09:30 rotation │ └── at-002 access parent rt-002 root_issued_at 09:00:00 │ └── rt-004 refresh parent rt-002 root_issued_at 09:00:00 ← 10:00:03 rotation └── at-004 access parent rt-004 root_issued_at 09:00:00 issued_at 10:00:03

root@kitploit:~
`at-004` существует три секунды. Его происхождение — час. Оба факта верны,
и только один из них виден в журнале запросов.

Два инварианта скрепляют это, и оба протестированы:

1. **Только аутентификация создаёт происхождение.** `Store.open_session` — единственное
   место, где генерируется `lineage_id`, и он делает сессию своим собственным
   `root_credential_id`.
2. **Выпуск копирует корень вниз.** `TokenService.mint` читает поля происхождения
   из родителя, а не пересчитывает их, поэтому учётные данные не могут получить
   более свежее происхождение, чем логин, от которого они происходят.

Токен, предъявленный держателем, намеренно **не** несёт свой корень происхождения. Если бы
нёс, держатель мог бы солгать о нём.

---

## Аудит состояния

AFTERLIFE-001 срабатывает, когда устаревшее происхождение *используется*. Это правильный триггер для
правила обнаружения, и он оставляет дыру: **устаревшее происхождение, к которому никто не прикасался, невидимо для него.** Атакующий, который крадёт учётные данные, наблюдает, как сброс не удаётся,
а затем ждёт, не производит никакой активности для корреляции.

Поэтому лаборатория также задаёт вопрос, который правило не может задать: не *что произошло*, а *что
всё ещё живо*.```text
  scenario: dormant-survivor   (the attacker holds the credential and never spends it)

  AFTERLIFE-001   silent   — correct; nothing was accepted
  AFTERLIFE-003   HIGH     — the change revoked nothing
  state audit     1 stale lineage, 1 dormant
  • --no-verify — пропустить проверку TLS-сертификата (не рекомендуется)
  • --timeout — таймаут запроса в секундах (по умолчанию: 10)
  • --retries — количество повторных попыток при неудачных запросах (по умолчанию: 3)
  • --proxy — прокси-сервер для использования при выполнении запросов
  • --headers — дополнительные заголовки для включения в запросы
  • --user-agent — строка User-Agent для использования в запросах
  • --follow-redirects — следовать перенаправлениям HTTP
  • --max-redirects — максимальное количество перенаправлений для отслеживания (по умолчанию: 5)
  • --verbose — включить подробный вывод
  • --quiet — подавить весь вывод, кроме ошибок
  • --output — выходной файл для записи результатов
  • --format — формат вывода (json, yaml, csv, table)
  • --config — файл конфигурации для загрузки настроек
  • --log-level — уровень логирования (debug, info, warn, error)
  • --log-file — файл для записи логов
  • --version — показать информацию о версии и выйти
  • --help — показать справочное сообщение и выйти

Примеры

root@kitploit:~
# Базовое сканирование
python3 main.py --target example.com

# Сканирование с пользовательскими параметрами
python3 main.py --target example.com --threads 20 --timeout 30

# Сканирование с выводом в формате JSON
python3 main.py --target example.com --format json --output results.json

# Сканирование с использованием файла конфигурации
python3 main.py --config config.yaml

# Сканирование с подробным выводом
python3 main.py --target example.com --verbose

# Сканирование с использованием прокси
python3 main.py --target example.com --proxy http://127.0.0.1:8080

# Сканирование с пользовательскими заголовками
python3 main.py --target example.com --headers "Authorization: Bearer token"

# Сканирование с пользовательским User-Agent
python3 main.py --target example.com --user-agent "Mozilla/5.0"

# Сканирование с отслеживанием перенаправлений
python3 main.py --target example.com --follow-redirects --max-redirects 10

# Сканирование с логированием в файл
python3 main.py --target example.com --log-level debug --log-file scan.log

Конфигурация

Инструмент поддерживает файл конфигурации в формате YAML. Пример:

root@kitploit:~
target: example.com
threads: 10
timeout: 10
retries: 3
proxy: http://127.0.0.1:8080
headers:
  Authorization: Bearer token
user_agent: Mozilla/5.0
follow_redirects: true
max_redirects: 5
verbose: false
quiet: false
output: results.json
format: json
log_level: info
log_file: scan.log

Вывод

Инструмент поддерживает несколько форматов вывода:

  • json — вывод в формате JSON
  • yaml — вывод в формате YAML
  • csv — вывод в формате CSV
  • table — вывод в виде таблицы

Пример вывода в формате JSON:

root@kitploit:~
{
  "target": "example.com",
  "scan_time": "2024-01-01T00:00:00Z",
  "results": [
    {
      "url": "https://example.com",
      "status_code": 200,
      "content_length": 1234,
      "content_type": "text/html",
      "server": "nginx",
      "technologies": ["nginx", "php"],
      "vulnerabilities": []
    }
  ]
}

Лицензия

Этот проект лицензирован в соответствии с лицензией MIT — подробности см. в файле LICENSE.

Благодарности

  • [Список благодарностей]

Контакты

  • [Контактная информация]

Отказ от ответственности

Этот инструмент предназначен только для образовательных целей. Используйте его ответственно и только на системах, на которые у вас есть разрешение. Авторы не несут ответственности за любой ущерб, причинённый использованием этого инструмента.```bash python scripts/lab.py audit

root@kitploit:~
| `--no-color` | Отключить цветной вывод |
| `--debug` | Включить режим отладки |
| `--verbose` | Включить подробный вывод |
| `--silent` | Подавить весь вывод |
| `--json` | Выводить результаты в формате JSON |
| `--csv` | Выводить результаты в формате CSV |
| `--html` | Выводить результаты в формате HTML |
| `--markdown` | Выводить результаты в формате Markdown |
| `--output <file>` | Записать вывод в файл |
| `--config <file>` | Использовать указанный файл конфигурации |
| `--threads <n>` | Количество потоков (по умолчанию: 10) |
| `--timeout <n>` | Тайм-аут в секундах (по умолчанию: 30) |
| `--retries <n>` | Количество повторных попыток (по умолчанию: 3) |
| `--proxy <url>` | Использовать прокси |
| `--user-agent <string>` | Установить User-Agent |
| `--cookie <string>` | Установить Cookie |
| `--header <string>` | Установить пользовательский заголовок |
| `--rate-limit <n>` | Ограничить запросы в секунду |
| `--random-agent` | Использовать случайный User-Agent |
| `--follow-redirects` | Следовать перенаправлениям |
| `--no-verify-ssl` | Отключить проверку SSL |
| `--insecure` | Разрешить небезопасные соединения |
| `--force` | Принудительное выполнение |
| `--quiet` | Тихий режим |
| `--version` | Показать версию |
| `--help` | Показать справку |```text
VULNERABLE
  server state   1 lineage(s) outlived the change   (0 dormant)
  telemetry      1 lineage(s)   -- the same question, asked of the log instead of the database
    lin-001 root 2026-09-11T09:00:00.000Z  7 credentials  in use

FIXED
  server state   clean
  telemetry      clean

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

Аудит состояния сервера нуждается в таблице security_changes, которую пишут оба режима, потому что запись о том, что изменение произошло, — это отдельная обязанность от реагирования на него — и уязвимая реализация выполняет ровно одну из двух.


Матрица сценариев

Девять сценариев против обеих реализаций. python scripts/scenarios.py проверяет свои два инварианта и завершается с ненулевым кодом, если любой из них нарушен.

сценарийрежимсобытияхудшийсработавшие правилаустаревшиеспящие
legitimate-onlyуязвимый19LOWAFTERLIFE-00300
legitimate-onlyисправленный19–нет00
stolen-refreshуязвимый22HIGHAFTERLIFE-001, AFTERLIFE-00310
stolen-refreshисправленный23–нет00
stolen-refresh-with-device-idуязвимый19LOWAFTERLIFE-00300
stolen-refresh-with-device-idисправленный19–нет00
multi-deviceуязвимый29HIGHAFTERLIFE-001, AFTERLIFE-00320
multi-deviceисправленный29–нет00
refresh-reuseуязвимый11MEDIUMAFTERLIFE-00200
refresh-reuseисправленный15MEDIUMAFTERLIFE-00200

Четыре строки несут аргумент:

  • legitimate-only вообще не содержит злоумышленника. Исправление не производит ничего; уязвимая реализация производит LOW. Это не ложное срабатывание — это enumeration_only: отзыв сработал на этот раз, путём перечисления, без водяного знака, чтобы покрыть учётные данные, которые сервер забыл. Пакет разделяет две реализации при отсутствии злоумышленника.
  • dormant-survivor — это слепое пятно и его ответ в одной строке: AFTERLIFE-001 молчит, AFTERLIFE-003 HIGH, аудит dormant: 1.
  • multi-device — это место, где рабочая ветвь уязвимой реализации всё равно терпит неудачу — отзыв ограничен ноутбуком, два других устройства не тронуты.
  • expired-lineage — это контроль ложных срабатываний: 40-дневная сессия, чьи учётные данные истекли сами по себе, не является выжившим путём персистентности и не сообщается как таковой.

И центральное утверждение матрицы, проверяемое скриптом и test_the_fix_never_produces_a_control_failure_finding: исправление не вызывает находку об отказе контроля ни в одном сценарии. AFTERLIFE-002 допускается — он сообщает о краже, а не об отказе контроля, и у корректной реализации всё ещё есть кражи, о которых нужно сообщать.


Ложные срабатывания

Правило, которое срабатывает при каждом сбросе пароля, будет заглушено в течение недели, а заглушённое правило хуже отсутствия правила — это правило, о котором все думают, что оно работает.

случайрезультатпочему
Смена пароля, затем использование сессии-заменынет оповещениякорень новой линии находится на водяном знаке или после него
Сброс, затем немедленный просмотр (/me, /profile, /settings)нет оповещенияодна свежая линия, один свежий корень
Телефон, ноутбук и планшет, все корректно ротированынет оповещениякаждый вход — это своя линия
Устройство, вошедшее после изменения, работающее рядом со злоумышленникомнет оповещениясвежий корень — и устаревшая линия всё равно оповещает, в одиночку
Устаревшие учётные данные, которые были отклоненынет оповещенияresult: failure исключается; это доказательство для защиты
Расхождение часов до 2с между компонентаминет оповещениязадокументированный допуск
Неудачная смена паролянет оповещенияне якорь
Активность до изменениянет оповещенияпроверка временного порядка
Ротированные учётные данные (reason: rotated)не считается смертьюучётные данные израсходованы, а не линия убита
40-дневная сессия, чьи учётные данные истеклине выжившийистечение отслеживается по линии
Первая смена пароля на совершенно новом аккаунтенет оповещенияничего не было активно на входе
Ротация refresh-токена из устаревшей линииОПОВЕЩЕНИЕсвежая метка времени, устаревшее происхождение — это и есть находка

Каждая строка имеет тест в tests/test_detector_afterlife001.py и tests/test_detector_rulepack.py.

О 2-секундном допуске. Каждая метка времени в этой лаборатории исходит из одного процесса и одних часов, поэтому честный допуск равен нулю. 2с — это то, что нужно реалистичному двуххостовому развёртыванию под NTP, и это на три порядка меньше, чем разрывы, которые производит эта атака — всё назначение refresh-учётных данных в том, чтобы быть долгоживущими. Допуск — это окно, в котором действительно устаревшая линия игнорируется, поэтому он намеренно оставлен маленьким: test_12b_a_gap_beyond_the_tolerance_does_alert фиксирует, что 2.1с всё ещё срабатывает.


Исправление```python

THE FIX. Two mechanisms, deliberately redundant.

self.store.set_watermark(user.user_id, moment) # the guarantee revoked = self.store.revoke_credentials(user.user_id, ...) # defence in depth

root@kitploit:~
Ни один из них не зависит от `device_id`. Он фиксируется как контекст и не влияет на
радиус поражения.

**Водяной знак — это архитектурная гарантия.** Одна временная метка на пользователя,
`credentials_valid_after`, сравнивается при каждом запросе как с моментом выпуска самого
credential'а, *так и* с корнем его цепочки наследования:```python
watermark = user.credentials_valid_after
if credential.issued_at      < watermark:  reject  # the obvious case
if credential.root_issued_at < watermark:  reject  # the refresh chain

Второе сравнение — это то, что требует усилий, чтобы сделать правильно, и то, что наивная реализация упускает — как при принудительном применении, так и при обнаружении.

Явный отзыв — это эшелонированная защита, и доказательство. revoked_at и revocation_reason — это то, что читает специалист по реагированию на инциденты, а события auth.token.revoked — это то, что доказывает, что локализация произошла.

test_the_watermark_alone_rejects_a_stale_credential устанавливает watermark без отзыва чего-либо и требует, чтобы устаревшие учётные данные были отклонены — что устанавливает, какой из двух является несущим, и какое свойство сохраняется для учётных данных, о выдаче которых сервер забыл.

Та же атака, против исправления

Консоль в исправленном режиме: вердикт гласит «Ничего, выданное до изменения, не работает после него», счётчики все зелёные, каждая полоса в lin-001 обрывается на гильотине, а панель находок сообщает об отсутствии находок при пяти пройденных проверках.

Ни один запрос не изменился. Одна функция — да.

Не только пароли

Триггер — это не «пароль изменился». Это «что-то изменилось, что делает ранее выданные учётные данные не заслуживающими доверия». Все четыре маршрута используют _revoke_for_security_change, поэтому исправление и баг применяются одинаково к:

изменениепочему учётные данные становятся не заслуживающими доверия
смена / сброс паролясекрет, с которым была установлена сессия, исчез
регистрация или изменение MFAфакторы, с которыми была установлена сессия, не являются факторами аккаунта
смена роли / повышение привилегийучётные данные были выпущены при другой авторизации
восстановление аккаунтапо построению, аккаунт мог мгновение назад быть в чужих руках
входне отзывает другие — новая линия происхождения, а не утверждение, что старые не заслуживают доверия

Протестировано для каждого вида, в обоих режимах, в tests/test_privilege_changes.py — включая не-требование для входа, задокументированное, чтобы следующий читатель не «исправил» его.


Архитектурные компромиссы

Четыре способа заставить учётные данные перестать работать. AFTERLIFE реализует A + C, и порядок имеет значение.

механизмчто даётчего стоит
Awatermark отзыва на пользователяодна запись отзывает всё, включая учётные данные, о выдаче которых сервер забыл; O(1) хранение и проверкасерверное состояние на пути чтения; семантика временных меток должна быть точно правильной; ничего не говорит об учётных данных, выданных после watermark
Bкороткоживущий access + отзываемый refreshограничивает ущерб от access-токена без состояния на пути чтенияукраденный access-токен действителен до истечения срока; сторона refresh всё ещё нуждается в состоянии — это тот паттерн, внутри которого живёт CVE
Cявный denylistточен; понятен специалисту по реагированию на инциденты; производит телеметрию, доказывающую локализациюсостояние растёт и требует очистки; отзывает только то, что вы не забыли перечислить — а это тот запрос, который баг выполнил неправильно
Dполностью stateless JWTвообще нет состояния на пути чтенияотзыва не существует. TTL и ротация ключей — единственные рычаги

О D: JWT не являются по своей природе небезопасными. Напряжение более узкое и заслуживает точной формулировки — stateless-проверка и немедленный серверный отзыв взаимоисключающи. Вы не можете решить «эти учётные данные больше не действительны» без обращения к чему-то, что это знает, а обращение к этому и делает систему stateful. Ошибка — принять JWT ради их stateless-ности, а затем всё равно нуждаться в отзыве, что делает каждый продукт при первой краже ноутбука. То, что вы получаете в итоге, — это подписанный указатель на серверное состояние — что и реализует app/tokens.py намеренно, потому что прийти к этому нарочно дешевле, чем прийти к этому посреди инцидента.

Почему 2.1 Low — не то, о чём стоит спорить

CVE-2026-22706 оценивается в 2.1, Low (CVSS v4.0 AV:N/AC:H/AT:N/PR:H/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N), и оценка защитима: PR:H, потому что атакующий уже должен обладать действительными refresh- учётными данными, AC:H, потому что получение их требует предварительной компрометации, и VC:N/VI:L, потому что уязвимость не даёт доступа, которого у атакующего уже не было. CVSS измеряет маргинальное воздействие уязвимости, а маргинальное воздействие — это длительность доступа, а не его охват. При таких входных данных 2.1 корректно вытекает из формулы.

Что CVSS не моделирует, так это то, что отказывающий контроль — это действие по локализации — поэтому полезный вывод касается маршрутизации триажа, а не числа. Low на пути локализации заслуживает внимания, которого Low на пути функции не заслуживает, потому что цена платится во время инцидента. Полное обсуждение: docs/tradeoffs.md.


Тесты```text

290 passed

root@kitploit:~
| файл | что он фиксирует |
|---|---|
| [`test_fixed_mode.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_fixed_mode.py) | **набор регрессионных тестов, который никогда не должен становиться красным** — ни один учётный данные, выданные до события отзыва, не принимаются после него, включая учётные данные, происходящие от устаревшей цепочки |
| [`test_vulnerable_mode.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_vulnerable_mode.py) | уязвимость **существует**, детерминирована, длится 30 дней и вызвана условным оператором — при этом контроль `device_id` доказывает, что путь отзыва является опциональным, а не мёртвым |
| [`test_lineage.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_lineage.py) | цепочка обновления сохраняет одну линию происхождения и один корень; только вход создаёт линию происхождения |
| [`test_detector_afterlife001.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_detector_afterlife001.py) | 7 случаев обнаружения, 9 случаев ложного срабатывания, 2 слепые зоны, все 24 порядка событий, ограниченное состояние, некорректный ввод |
| [`test_detector_rulepack.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_detector_rulepack.py) | AFTERLIFE-002 и AFTERLIFE-003 — каждый вердикт, окно отсрочки, ротация-не-смерть, исключение истечения срока, и порядок работы движка |
| [`test_naive_detector.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_naive_detector.py) | NAIVE-001 продолжает падать **именно так, как утверждает этот README** — включая то, что у него были нужные данные |
| [`test_audit_and_console.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_audit_and_console.py) | дремлющий выживший, которого не видит ни одно правило; согласие обоих источников аудита; консоль не вычисляет собственный вердикт; офлайн-предпросмотр ничего не загружает; каждая цифра в этом README в допустимых пределах, без стилевых таблиц и побайтово стабильна |
| [`test_scenarios.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_scenarios.py) | строки матрицы, которые несут аргумент |
| [`test_privilege_changes.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_privilege_changes.py) | MFA, смена роли и восстановление учётной записи, оба режима |
| [`test_telemetry.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_telemetry.py) | контракт редактирования и поиск в логе буквальных строк bearer, которые были выданы |
| [`test_app.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_app.py) | подделанные подписи, неверные типы учётных данных, истечение срока из состояния сервера |
| [`test_infrastructure.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_infrastructure.py) | конфигурация, часы, частичные строки tailer, оба CLI, каждая демонстрационная команда |

Самый сильный тест в наборе —
`test_no_bearer_string_or_password_ever_reaches_the_log`: он запускает всю атаку
и затем ищет в файле лога фактические учётные данные, которые выдало приложение,
плюс оба пароля, плюс ключ подписи. Не имена полей — а значения.

---

## Слепые зоны

Изложены прямо, потому что их сокрытие сделало бы лабораторию нечестной. У каждой есть тест.

> **Если приложение не генерирует события изменения учётных данных, пакет не может
> надёжно сопоставить активность после сброса.** Нет якоря, поэтому нет ничего,
> «после» чего могла бы быть активность — и это первым заглушает AFTERLIFE-003,
> то есть правило, которое сообщило бы вам, что контроль сломан.
>
> **Если приложение не сохраняет метаданные выдачи/линии происхождения, детектор
> не может определить, произошёл ли свежий токен доступа от более старого
> учётного данных.** Учётные данные, созданные три секунды назад, неотличимы от
> учётных данных, созданных три секунды назад месячной цепочкой.

Это **не опциональные предпочтения логирования. Это требования безопасности
обнаружения.** Отказ от `auth.token.issued` для уменьшения объёма логов не делает
логирование дешевле; он отключает обнаружение.

Неразрешимая активность **подсчитывается**, а не молча отбрасывается —
`stats()["activity_with_unresolved_lineage"]` не равно нулю всякий раз, когда
детектору задавали вопросы об учётных данных, которые телеметрия никогда не
описывала. Правило, которое молчит, потому что ничего не случилось, и правило,
которое молчит, потому что оно слепо, выглядят одинаково снаружи, и это число —
разница между ними.

Также верно, и также задокументировано:

* **Детектор не может отклонить запрос.** Он читает лог. Он сообщает вам, что
  локализация не удалась; он не локализует.
* **Аудит — это сканирование, а не обнаружение.** Он закрывает пробел с
  дремлющими выжившими, но он запускается, когда кто-то его запускает. Он не
  может оповещать.
* **Исправление не помогает с учётными данными, украденными *после* сброса.**
  Локализация — это момент времени, а не свойство.
* **AFTERLIFE-003 проверяет наблюдаемую локализацию, а не архитектурную
  полноту.** Отзыв с областью действия устройства, который случайно покрывает
  всё, получает находку LOW, а не HIGH. Баг всё ещё там; он не укусил на том
  аккаунте. Тест может спросить «а как насчёт двух устройств?»; лог может
  сообщить только о том, что произошло.
* **Другая персистентность полностью переживает смену пароля** — OAuth-гранты,
  API-ключи, правила пересылки почты, контакты для восстановления.

Полное обсуждение: **[docs/limitations.md](https://github.com/het-p301204/afterlife/blob/main/docs/limitations.md)**.

---

## Запуск лаборатории```bash
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate

pip install -r requirements.txt -r requirements-dev.txt

pytest -q

Затем, по порядку:```bash python scripts/lab.py vulnerable

root@kitploit:~
| | |
|---|---|
| `-h`, `--help` | Показать справочное сообщение и выйти |
| `-v`, `--version` | Показать версию программы и выйти |
| `-c`, `--config` | Путь к файлу конфигурации |
| `-o`, `--output` | Путь к выходному файлу |
| `-f`, `--format` | Формат вывода (json, yaml, xml) |
| `-q`, `--quiet` | Тихий режим, подавлять вывод |
| `-d`, `--debug` | Включить отладочный вывод |```bash
python scripts/lab.py detect

| -s | Silent mode. Only print the final result. | | -v | Verbose mode. Print detailed progress information. | | -o <file> | Write output to the specified file. | | -t <type> | Specify the output type. | | -h | Show help message. |```bash python scripts/lab.py fixed

root@kitploit:~
## Сценарии использования

- **Аудит безопасности**: сканирование внутренних приложений на наличие уязвимостей
- **Bug bounty**: быстрое тестирование целей в рамках программы
- **CI/CD интеграция**: автоматизированное сканирование в пайплайнах развёртывания
- **Обучение**: понимание распространённых векторов веб-атак

## Установка

### Из исходного кода

```bash
git clone https://github.com/example/vulnscan.git
cd vulnscan
pip install -r requirements.txt

Через pip

root@kitploit:~
pip install vulnscan

Быстрый старт

root@kitploit:~
# Базовое сканирование
vulnscan --url https://example.com

# Сканирование с аутентификацией
vulnscan --url https://example.com --cookie "session=abc123"

# Сканирование нескольких целей
vulnscan --file targets.txt --threads 10

Конфигурация

Создайте файл config.yaml в рабочей директории:

root@kitploit:~
scan:
  timeout: 30
  threads: 5
  user_agent: "VulnScan/1.0"

modules:
  sqli: true
  xss: true
  ssrf: true
  lfi: false

output:
  format: json
  verbose: false

Модули

МодульОписаниеСтатус
sqliОбнаружение SQL-инъекцийСтабильный
xssОбнаружение межсайтового скриптингаСтабильный
ssrfОбнаружение подделки серверных запросовБета
lfiОбнаружение локального включения файловЭкспериментальный

Лицензия

Данный проект распространяется под лицензией MIT. Подробности см. в файле LICENSE.```bash python scripts/lab.py audit

root@kitploit:~
Или всю историю сразу, с уроком в конце:```bash
python scripts/lab.py all

И картинка:```bash python scripts/lab.py console

root@kitploit:~
<details>
<summary><b>Всё остальное</b> — CLI детектора, матрица, отчёт, графики, серверы</summary>

<br>

Воспроизведите зафиксированные доказательства через пакет. Запуск приложения не требуется —
детектор строго ниже по потоку, и это доказательство:```bash
python -m detector --once --timeline --events evidence/vulnerable-persistence.jsonl

По одному правилу за раз, либо наивное, либо сканирование состояний:```bash python -m detector --once --rule AFTERLIFE-003 --events evidence/vulnerable-persistence.jsonl

root@kitploit:~
| `--no-color` | Отключить цветной вывод |
| `--debug` | Включить отладочный вывод |
| `--verbose` | Включить подробный вывод |
| `--silent` | Включить тихий режим |
| `--version` | Показать версию программы и выход |
| `--update` | Обновить программу до последней версии |
| `--help` | Показать справочное сообщение и выход |```bash
python -m detector --once --naive --events evidence/vulnerable-persistence.jsonl

| --no-ssl | Отключить проверку SSL-сертификата. | | --no-redirect | Отключить автоматическое следование перенаправлениям. | | --no-cookies | Отключить использование файлов cookie. | | --no-cache | Отключить кэширование. | | --no-proxy | Отключить использование прокси. | | --no-auth | Отключить аутентификацию. | | --no-verify | Отключить проверку. | | --no-update | Отключить обновление. | | --no-banner | Отключить отображение баннера. | | --no-color | Отключить цветной вывод. | | --no-log | Отключить ведение журнала. | | --no-debug | Отключить отладочный вывод. | | --no-verbose | Отключить подробный вывод. | | --no-quiet | Отключить тихий режим. | | --no-silent | Отключить беззвучный режим. | | --no-interactive | Отключить интерактивный режим. | | --no-batch | Отключить пакетный режим. | | --no-parallel | Отключить параллельный режим. | | --no-threads | Отключить использование потоков. | | --no-processes | Отключить использование процессов. | | --no-timeout | Отключить тайм-аут. | | --no-retry | Отключить повторные попытки. | | --no-delay | Отключить задержку. | | --no-sleep | Отключить паузу. | | --no-wait | Отключить ожидание. | | --no-block | Отключить блокировку. | | --no-lock | Отключить блокировку. | | --no-mutex | Отключить мьютекс. | | --no-semaphore | Отключить семафор. | | --no-queue | Отключить очередь. | | --no-buffer | Отключить буферизацию. | | --no-stream | Отключить потоковую передачу. | | --no-chunk | Отключить разбиение на фрагменты. | | --no-split | Отключить разделение. | | --no-merge | Отключить объединение. | | --no-concat | Отключить конкатенацию. | | --no-append | Отключить добавление. | | --no-prepend | Отключить добавление в начало. | | --no-insert | Отключить вставку. | | --no-delete | Отключить удаление. | | --no-remove | Отключить удаление. | | --no-clear | Отключить очистку. | | --no-reset | Отключить сброс. | | --no-restore | Отключить восстановление. | | --no-backup | Отключить резервное копирование. | | --no-restore | Отключить восстановление. | | --no-save | Отключить сохранение. | | --no-load | Отключить загрузку. | | --no-import | Отключить импорт. | | --no-export | Отключить экспорт. | | --no-download | Отключить загрузку. | | --no-upload | Отключить выгрузку. | | --no-sync | Отключить синхронизацию. | | --no-async | Отключить асинхронность. | | --no-await | Отключить ожидание. | | --no-yield | Отключить уступку. | | --no-return | Отключить возврат. | | --no-break | Отключить прерывание. | | --no-continue | Отключить продолжение. | | --no-pass | Отключить пропуск. | | --no-raise | Отключить возбуждение исключения. | | --no-assert | Отключить утверждение. | | --no-check | Отключить проверку. | | --no-validate | Отключить валидацию. | | --no-sanitize | Отключить санитизацию. | | --no-escape | Отключить экранирование. | | --no-quote | Отключить заключение в кавычки. | | --no-encode | Отключить кодирование. | | --no-decode | Отключить декодирование. | | --no-encrypt | Отключить шифрование. | | --no-decrypt | Отключить дешифрование. | | --no-hash | Отключить хеширование. | | --no-sign | Отключить подпись. | | --no-verify | Отключить проверку. | | --no-trust | Отключить доверие. | | --no-cert | Отключить сертификат. | | --no-key | Отключить ключ. | | --no-password | Отключить пароль. | | --no-token | Отключить токен. | | --no-secret | Отключить секрет. | | --no-credential | Отключить учётные данные. | | --no-auth | Отключить аутентификацию. | | --no-authorize | Отключить авторизацию. | | --no-permission | Отключить разрешение. | | --no-role | Отключить роль. | | --no-policy | Отключить политику. | | --no-rule | Отключить правило. | | --no-filter | Отключить фильтр. | | --no-match | Отключить сопоставление. | | --no-regex | Отключить регулярное выражение. | | --no-glob | Отключить шаблон. | | --no-wildcard | Отключить подстановочный знак. | | --no-pattern | Отключить шаблон. | | --no-search | Отключить поиск. | | --no-replace | Отключить замену. | | --no-substitute | Отключить подстановку. | | --no-transform | Отключить преобразование. | | --no-convert | Отключить конвертацию. | | --no-format | Отключить форматирование. | | --no-parse | Отключить разбор. | | --no-serialize | Отключить сериализацию. | | --no-deserialize | Отключить десериализацию. | | --no-marshal | Отключить маршализацию. | | --no-unmarshal | Отключить демаршализацию. | | --no-stringify | Отключить преобразование в строку. | | --no-parse | Отключить разбор. | | --no-print | Отключить вывод. | | --no-display | Отключить отображение. | | --no-render | Отключить рендеринг. | | --no-draw | Отключить отрисовку. | | --no-paint | Отключить рисование. | | --no-style | Отключить стиль. | | --no-theme | Отключить тему. | | --no-font | Отключить шрифт. | | --no-size | Отключить размер. | | --no-scale | Отключить масштабирование. | | --no-zoom | Отключить масштабирование. | | --no-rotate | Отключить поворот. | | --no-flip | Отключить отражение. | | --no-crop | Отключить обрезку. | | --no-resize | Отключить изменение размера. | | --no-thumbnail | Отключить создание миниатюр. | | --no-preview | Отключить предпросмотр. | | --no-view | Отключить просмотр. | | --no-show | Отключить показ. | | --no-hide | Отключить скрытие. | | --no-toggle | Отключить переключение. | | --no-switch | Отключить переключение. | | --no-select | Отключить выбор. | | --no-deselect | Отключить снятие выбора. | | --no-check | Отключить проверку. | | --no-uncheck | Отключить снятие отметки. | | --no-enable | Отключить включение. | | --no-disable | Отключить отключение. | | --no-activate | Отключить активацию. | | --no-deactivate | Отключить деактивацию. | | --no-start | Отключить запуск. | | --no-stop | Отключить остановку. | | --no-pause | Отключить паузу. | | --no-resume | Отключить возобновление. | | --no-restart | Отключить перезапуск. | | --no-reload | Отключить перезагрузку. | | --no-refresh | Отключить обновление. | | --no-update | Отключить обновление. | | --no-upgrade | Отключить обновление. | | --no-downgrade | Отключить понижение версии. | | --no-install | Отключить установку. | | --no-uninstall | Отключить удаление. | | --no-reinstall | Отключить переустановку. | | --no-build | Отключить сборку. | | --no-compile | Отключить компиляцию. | | --no-link | Отключить компоновку. | | --no-pack | Отключить упаковку. | | --no-unpack | Отключить распаковку. | | --no-zip | Отключить архивацию. | | --no-unzip | Отключить распаковку. | | --no-tar | Отключить архивацию. | | --no-untar | Отключить распаковку. | | --no-gzip | Отключить сжатие. | | --no-gunzip | Отключить распаковку. | | --no-compress | Отключить сжатие. | | --no-decompress | Отключить распаковку. | | --no-archive | Отключить архивацию. | | --no-extract | Отключить извлечение. | | --no-package | Отключить упаковку. | | --no-publish | Отключить публикацию. | | --no-deploy | Отключить развёртывание. | | --no-release | Отключить выпуск. | | --no-distribute | Отключить распространение. | | --no-share | Отключить общий доступ. | | --no-send | Отключить отправку. | | --no-receive | Отключить получение. | | --no-recv | Отключить получение. | | --no-transmit | Отключить передачу. | | --no-broadcast | Отключить широковещательную передачу. | | --no-multicast | Отключить многоадресную передачу. | | --no-unicast | Отключить одноадресную передачу. | | --no-anycast | Отключить anycast. | | --no-connect | Отключить подключение. | | --no-disconnect | Отключить отключение. | | --no-reconnect | Отключить переподключение. | | --no-bind | Отключить привязку. | | --no-listen | Отключить прослушивание. | | --no-accept | Отключить принятие. | | --no-reject | Отключить отклонение. | | --no-close | Отключить закрытие. | | --no-open | Отключить открытие. | | --no-read | Отключить чтение. | | --no-write | Отключить запись. | | --no-seek | Отключить позиционирование. | | --no-tell | Отключить определение позиции. | | --no-flush | Отключить сброс буфера. | | --no-truncate | Отключить усечение. | | --no-append | Отключить добавление. | | --no-rename | Отключить переименование. | | --no-move | Отключить перемещение. | | --no-copy | Отключить копирование. | | --no-paste | Отключить вставку. | | --no-cut | Отключить вырезание. | | --no-undo | Отключить отмену. | | --no-redo | Отключить повтор. | | --no-revert | Отключить возврат. | | --no-commit | Отключить фиксацию. | | --no-rollback | Отключить откат. | | --no-push | Отключить отправку. | | --no-pull | Отключить получение. | | --no-fetch | Отключить извлечение. | | --no-clone | Отключить клонирование. | | --no-fork | Отключить форк. | | --no-branch | Отключить ветвление. | | --no-tag | Отключить тегирование. | | --no-merge | Отключить слияние. | | --no-rebase | Отключить перебазирование. | | --no-cherry-pick | Отключить cherry-pick. | | --no-stash | Отключить откладывание. | | --no-pop | Отключить извлечение. | | --no-apply | Отключить применение. | | --no-patch | Отключить патч. | | --no-diff | Отключить сравнение. | | --no-blame | Отключить blame. | | --no-log | Отключить журнал. | | --no-status | Отключить статус. | | --no-show | Отключить показ. | | --no-config | Отключить конфигурацию. | | --no-init | Отключить инициализацию. | | --no-clean | Отключить очистку. | | --no-reset | Отключить сброс. | | --no-checkout | Отключить переключение. | | --no-switch | Отключить переключение. | | --no-restore | Отключить восстановление. | | --no-rm | Отключить удаление. | | --no-mv | Отключить перемещение. | | --no-cp | Отключить копирование. | | --no-mkdir | Отключить создание каталога. | | --no-rmdir | Отключить удаление каталога. | | --no-touch | Отключить создание файла. | | --no-ln | Отключить создание ссылки. | | --no-chmod | Отключить изменение прав. | | --no-chown | Отключить изменение владельца. | | --no-chgrp | Отключить изменение группы. | | --no-umask | Отключить маску прав. | | --no-stat | Отключить статистику. | | --no-file | Отключить файл. | | --no-dir | Отключить каталог. | | --no-path | Отключить путь. | | --no-name | Отключить имя. | | --no-ext | Отключить расширение. | | --no-base | Отключить базовое имя. | | --no-dirname | Отключить имя каталога. | | --no-basename | Отключить базовое имя. | | --no-realpath | Отключить реальный путь. | | --no-abspath | Отключить абсолютный путь. | | --no-relpath | Отключить относительный путь. | | --no-normalize | Отключить нормализацию. | | --no-canonical | Отключить канонизацию. | | --no-resolve | Отключить разрешение. | | --no-lookup | Отключить поиск. | | --no-query | Отключить запрос. | | --no-search | Отключить поиск. | | --no-find | Отключить поиск. | | --no-locate | Отключить поиск. | | --no-which | Отключить поиск. | | --no-where | Отключить поиск. | | --no-type | Отключить тип. | | --no-kind | Отключить вид. | | --no-class | Отключить класс. | | --no-category | Отключить категорию. | | --no-group | Отключить группу. | | --no-tag | Отключить тег. | | --no-label | Отключить метку. | | --no-annotate | Отключить аннотацию. | | --no-comment | Отключить комментарий. | | --no-note | Отключить заметку. | | --no-desc | Отключить описание. | | --no-title | Отключить заголовок. | | --no-subject | Отключить тему. | | --no-body | Отключить тело. | | --no-header | Отключить заголовок. | | --no-footer | Отключить нижний колонтитул. | | --no-meta | Отключить метаданные. | | --no-data | Отключить данные. | | --no-info | Отключить информацию. | | --no-detail | Отключить детали. | | --no-summary | Отключить сводку. | | --no-report | Отключить отчёт. | | --no-output | Отключить вывод. | | --no-input | Отключить ввод. | | --no-stdin | Отключить стандартный ввод. | | --no-stdout | Отключить стандартный вывод. | | --no-stderr | Отключить стандартный вывод ошибок. | | --no-pipe | Отключить конвейер. | | --no-redirect | Отключить перенаправление. | | --no-tee | Отключить разветвление. | | --no-xargs | Отключить xargs. | | --no-parallel | Отключить параллельный режим. | | --no-sequential | Отключить последовательный режим. | | --no-serial | Отключить последовательный режим. | | --no-concurrent | Отключить параллельный режим. | | --no-simultaneous | Отключить одновременный режим. | | --no-sync | Отключить синхронизацию. | | --no-async | Отключить асинхронность. | | --no-blocking | Отключить блокировку. | | --no-nonblocking | Отключить неблокирующий режим. | | --no-wait | Отключить ожидание. | | --no-nowait | Отключить режим без ожидания. | | --no-timeout | Отключить тайм-аут. | | --no-deadline | Отключить крайний срок. | | --no-expire | Отключить истечение срока. | | --no-ttl | Отключить TTL. | | --no-lease | Отключить аренду. | | --no-lock | Отключить блокировку. | | --no-unlock | Отключить разблокировку. | | --no-mutex | Отключить мьютекс. | | --no-semaphore | Отключить семафор. | | --no-barrier | Отключить барьер. | | --no-latch | Отключить защёлку. | | --no-cond | Отключить условие. | | --no-signal | Отключить сигнал. | | --no-event | Отключить событие. | | --no-callback | Отключить обратный вызов. | | --no-hook | Отключить хук. | | --no-trigger | Отключить триггер. | | --no-handler | Отключить обработчик. | | --no-listener | Отключить слушатель. | | --no-observer | Отключить наблюдатель. | | --no-subscriber | Отключить подписчика. | | --no-publisher | Отключить издателя. | | --no-producer | Отключить производителя. | | --no-consumer | Отключить потребителя. | | --no-broker | Отключить брокер. | | --no-queue | Отключить очередь. | | --no-topic | Отключить тему. | | --no-channel | Отключить канал. | | --no-stream | Отключить поток. | | --no-buffer | Отключить буфер. | | --no-cache | Отключить кэш. | | --no-pool | Отключить пул. | | --no-connection | Отключить соединение. | | --no-session | Отключить сессию. | | --no-transaction | Отключить транзакцию. | | --no-commit | Отключить фиксацию. | | --no-rollback | Отключить откат. | | --no-savepoint | Отключить точку сохранения. | | --no-checkpoint | Отключить контрольную точку. | | --no-snapshot | Отключить снимок. | | --no-backup | Отключить резервное копирование. | | --no-restore | Отключить восстановление. | | --no-recover | Отключить восстановление. | | --no-repair | Отключить исправление. | | --no-fix | Отключить исправление. | | --no-heal | Отключить самовосстановление. | | --no-migrate | Отключить миграцию. | | --no-upgrade | Отключить обновление. | | --no-downgrade | Отключить понижение версии. | | --no-convert | Отключить конвертацию. | | --no-transform | Отключить преобразование. | | --no-map | Отключить отображение. | | --no-reduce | Отключить свёртку. | | --no-filter | Отключить фильтрацию. | | --no-fold | Отключить свёртку. | | --no-scan | Отключить сканирование. | | --no-iterate | Отключить итерацию. | | --no-loop | Отключить цикл. | | --no-recurse | Отключить рекурсию. | | --no-traverse | Отключить обход. | | --no-walk | Отключить обход. | | --no-visit | Отключить посещение. | | --no-explore | Отключить исследование. | | --no-discover | Отключить обнаружение. | | --no-enumerate | Отключить перечисление. | | --no-list | Отключить список. | | --no-enum | Отключить перечисление. | | --no-count | Отключить подсчёт. | | --no-sum | Отключить суммирование. | | --no-avg | Отключить вычисление среднего. | | --no-min | Отключить минимум. | | --no-max | Отключить максимум. | | --no-sort | Отключить сортировку. | | --no-order | Отключить упорядочивание. | | --no-rank | Отключить ранжирование. | | --no-group | Отключить группировку. | | --no-uniq | Отключить уникальность. | | --no-dedup | Отключить дедупликацию. | | --no-distinct | Отключить различение. | | --no-merge | Отключить слияние. | | --no-join | Отключить соединение. | | --no-split | Отключить разделение. | | --no-partition | Отключить секционирование. | | --no-chunk | Отключить разбиение на фрагменты. | | --no-batch | Отключить пакетную обработку. | | --no-bulk | Отключить массовую обработку. | | --no-mass | Отключить массовую обработку. | | --no-burst | Отключить пакетную обработку. | | --no-throttle | Отключить ограничение скорости. | | --no-rate | Отключить ограничение скорости. | | --no-limit | Отключить ограничение. | | --no-quota | Отключить квоту. | | --no-cap | Отключить ограничение. | | --no-ceiling | Отключить верхний предел. | | --no-floor | Отключить нижний предел. | | --no-bound | Отключить границу. | | --no-range | Отключить диапазон. | | --no-scope | Отключить область видимости. | | --no-context | Отключить контекст. | | --no-env | Отключить окружение. | | --no-var | Отключить переменную. | | --no-const | Отключить константу. | | --no-let | Отключить let. | | --no-def | Отключить определение. | | --no-declare | Отключить объявление. | | --no-assign | Отключить присваивание. | | --no-bind | Отключить привязку. | | --no-alias | Отключить псевдоним. | | --no-ref | Отключить ссылку. | | --no-ptr | Отключить указатель. | | --no-deref | Отключить разыменование. | | --no-address | Отключить адрес. | | --no-offset | Отключить смещение. | | --no-index | Отключить индекс. | | --no-key | Отключить ключ. | | --no-value | Отключить значение. | | --no-pair | Отключить пару. | | --no-entry | Отключить запись. | | --no-item | Отключить элемент. | | --no-element | Отключить элемент. | | --no-node | Отключить узел. | | --no-edge | Отключить ребро. | | --no-vertex | Отключить вершину. | | --no-graph | Отключить граф. | | --no-tree | Отключить дерево. | | --no-list | Отключить список. | | --no-array | Отключить массив. | | --no-vector | Отключить вектор. | | --no-matrix | Отключить матрицу. | | --no-set | Отключить множество. | | --no-map | Отключить карту. | | --no-dict | Отключить словарь. | | --no-hash | Отключить хеш. | | --no-table | Отключить таблицу. | | --no-schema | Отключить схему. | | --no-model | Отключить модель. | | --no-entity | Отключить сущность. | | --no-object | Отключить объект. | | --no-instance | Отключить экземпляр. | | --no-class | Отключить класс. | | --no-struct | Отключить структуру. | | --no-union | Отключить объединение. | | --no-enum | Отключить перечисление. | | --no-interface | Отключить интерфейс. | | --no-trait | Отключить трейт. | | --no-mixin | Отключить примесь. | | --no-protocol | Отключить протокол. | | --no-abstract | Отключить абстракцию. | | --no-virtual | Отключить виртуальность. | | --no-override | Отключить переопределение. | | --no-overload | Отключить перегрузку. | | --no-extend | Отключить расширение. | | --no-implement | Отключить реализацию. | | --no-inherit | Отключить наследование. | | --no-derive | Отключить наследование. | | --no-compose | Отключить композицию. | | --no-delegate | Отключить делегирование. | | --no-proxy | Отключить прокси. | | --no-facade | Отключить фасад. | | --no-adapter | Отключить адаптер. | | --no-bridge | Отключить мост. | | --no-factory | Отключить фабрику. | | --no-builder | Отключить строитель. | | --no-singleton | Отключить синглтон. | | --no-prototype | Отключить прототип. | | --no-observer | Отключить наблюдатель. | | --no-strategy | Отключить стратегию. | | --no-command | Отключить команду. | | --no-state | Отключить состояние. | | --no-visitor | Отключить посетителя. | | --no-mediator | Отключить посредника. | | --no-memento | Отключить снимок. | | --no-iterator | Отключить итератор. | | --no-generator | Отключить генератор. | | --no-coroutine | Отключить корутину. | | --no-fiber | Отключить файбер. | | --no-thread | Отключить поток. | | --no-process | Отключить процесс. | | --no-task | Отключить задачу. | | --no-job | Отключить задание. | | --no-worker | Отключить воркер. | | --no-pool | Отключить пул. | | --no-executor | Отключить исполнитель. | | --no-scheduler | Отключить планировщик. | | --no-dispatcher | Отключить диспетчер. | | --no-runner | Отключить исполнитель. | | --no-agent | Отключить агент. | | --no-daemon | Отключить демон. | | --no-service | Отключить службу. | | --no-server | Отключить сервер. | | --no-client | Отключить клиент. | | --no-peer | Отключить пир. | | --no-host | Отключить хост. | | --no-node | Отключить узел. | | --no-cluster | Отключить кластер. | | --no-swarm | Отключить рой. | | --no-mesh | Отключить сетку. | | --no-grid | Отключить сетку. | | --no-cloud | Отключить облако. | | --no-edge | Отключить периферию. | | --no-fog | Отключить туман. | | --no-mist | Отключить туман. | | --no-dew | Отключить росу. | | --no-frost | Отключить мороз. | | --no-ice | Отключить лёд. | | --no-snow | Отключить снег. | | --no-rain | Отключить дождь. | | --no-storm | Отключить шторм. | | --no-thunder | Отключить гром. | | --no-lightning | Отключить молнию. | | --no-wind | Отключить ветер. | | ````bash python -m detector --audit --events evidence/vulnerable-persistence.jsonl

root@kitploit:~
CLI завершается с кодом `1`, когда что-то находит, поэтому работает как проверка в CI без
разбора своего вывода.

Девять сценариев для обеих реализаций, с самопроверяющимися инвариантами:```bash
python scripts/scenarios.py

Отчёт об инциденте, который был бы передан специалисту по реагированию, и цифры в этом README:```bash python scripts/report.py

root@kitploit:~
Проверяет, содержит ли строка подозрительные символы, которые могут указывать на попытку инъекции команд.```bash
python scripts/figures.py

Живая консоль и лабораторный API вручную — оба только через loopback:```bash python -m console

root@kitploit:~
| `--no-color` | Отключить цветной вывод |
| `--debug` | Включить режим отладки |
| `--verbose` | Включить подробный вывод |
| `--silent` | Включить тихий режим |
| `--version` | Показать версию программы и выйти |
| `--help` | Показать это справочное сообщение и выйти |```bash
python -m app --mode vulnerable --port 9101

Восстановите зафиксированную телеметрию (фиксированные часы, побайтово идентичная между запусками):```bash python scripts/lab.py evidence

root@kitploit:~
Task runners, одни и те же цели в любом случае:```bash
make demo

| --config | Path to a YAML config file (default: config.yaml) | | --output | Output directory for reports (default: ./reports) | | --format | Report format: json, html, or sarif (default: json) | | --verbose | Enable verbose logging | | --quiet | Suppress all non-error output |

Configuration File

The scanner reads its configuration from a YAML file. A minimal example:

root@kitploit:~
targets:
  - https://example.com
  - https://api.example.com

checks:
  - headers
  - tls
  - cors

timeout: 30
concurrency: 10

Exit Codes

CodeMeaning
0No issues found
1Issues detected
2Configuration error
3Network or connection failure

Examples

Scan a single target and output SARIF:

root@kitploit:~
scanner --target https://example.com --format sarif --output ./results

Run with a custom config file and verbose logging:

root@kitploit:~
scanner --config ./custom-config.yaml --verbose

Docker Usage

Build and run the container:

root@kitploit:~
docker build -t scanner:latest .
docker run --rm -v $(pwd)/reports:/app/reports scanner:latest --target https://example.com

Contributing

Contributions are welcome. Please open an issue before submitting a pull request for major changes.

  1. Fork the repository.
  2. Create a feature branch (git checkout -b feature/my-feature).
  3. Commit your changes (git commit -am 'Add new feature').
  4. Push to the branch (git push origin feature/my-feature).
  5. Open a Pull Request.

License

This project is licensed under the MIT License. See the LICENSE file for details.```powershell ./make.ps1 demo

root@kitploit:~
</details>

### Учётная запись лаборатории```text
alice / Password123!      →  changed during the demo to  Correct-Horse-Battery-9!

Ложные, локальные и единственные учётные данные в этом репозитории.


Замечания по безопасности

Этот репозиторий содержит намеренно уязвимый код. app/auth.py:_revoke_for_security_change намеренно не выполняет отзыв, когда AFTERLIFE_MODE=vulnerable.

Уязвимый путь — не режим по умолчанию. AFTERLIFE_MODE по умолчанию равен fixed, и test_the_default_mode_is_the_safe_one это фиксирует — лаборатория, сломанное поведение которой получается при забывчивости настроить хоть что-нибудь, в конечном итоге будет скопирована во что-то реальное.

Небезопасно выставлять в сеть в любом из режимов: bearer-учётные данные возвращаются в телах ответов, GET /lab/credentials выдаёт всё представление сервера о состоянии учётных данных пользователя, GET /lab/audit неаутентифицирован по замыслу, POST /security-change выдаст себе admin, и нигде нет TLS, ограничения частоты запросов, защиты от CSRF или блокировки учётных записей. Всё привязывается к 127.0.0.1, и как python -m app, так и python -m console отказываются от любого другого адреса.

Ни один реальный учётный данные, ключ или секрет не встречается в этом репозитории. Ключ подписи — это буквальная строка afterlife-lab-signing-key-not-a-secret-do-not-reuse; база данных учётных данных по умолчанию использует :memory:; телеметрия заменяет значения, имена ключей которых выглядят как учётные данные, и ссылается на учётные данные по id и отпечатку. Подробности и процесс сообщения об уязвимостях: SECURITY.md.


Документация

docs/detection.mdнабор правил: все три карточки правил, аудит состояния, требуемая телеметрия, ложные срабатывания, обоснование серьёзности, сценарии реагирования
docs/tradeoffs.mdчетыре архитектуры отзыва, напряжение вокруг JWT и обсуждение CVSS
docs/limitations.mdвсе слепые зоны, что исправление не исправляет, что закрывает аудит и собственные компромиссы лаборатории
docs/console-design.mdпочему консоль — это реестр смертности, решения по палитре и шрифтам и что было вырезано
docs/console-preview.htmlконсоль, собранная в один самодостаточный файл
docs/figures/иллюстрации в этом README, сгенерированные из полезной нагрузки
report/AFTERLIFE-report.mdсгенерированный отчёт об инциденте для уязвимого запуска
SECURITY.mdграница только-локального использования, учётные данные лаборатории, контракт редактирования
evidence/очищенная выборочная телеметрия, порождаемые ею оповещения и аудит состояния

Исследовательские ссылки

Проверено по первоисточникам 2026-09-11. VERIFIED означает, что было прочитано собственное уведомление вендора; REPORTED означает, что деталь взята из базы данных уязвимостей, а не от вендора.

Стандарты и классификации

  • OWASP Top 10:2025 — A07 Authentication Failures — описывает ровно этот сбой: приложения, которые не «корректно аннулируют пользовательские сессии или токены аутентификации». Его сопоставление с CWE включает все три приведённые ниже.
  • RFC 9700 — Best Current Practice for OAuth 2.0 Security (BCP 240, январь 2025). Две вещи, на которых держится эта лаборатория: повторное использование ротированного refresh-токена сигнализирует о краже (AFTERLIFE-002), и отзыв должен аннулировать всё семейство токенов, а не только текущий токен — что является тезисом AFTERLIFE-001, кодифицированным как практика IETF.
  • OWASP Session Management Cheat Sheet — аннулирование сессии после изменения учётных данных и настроек безопасности
  • CWE-613: Insufficient Session Expiration — «позволяет злоумышленнику повторно использовать старые учётные данные сессии или идентификаторы сессии для авторизации». Класс, под которым зарегистрированы все три CVE ниже.
  • CWE-287: Improper Authentication
  • CWE-384: Session Fixation — смежное: идентификатор сессии, переживающий изменение состояния аутентификации

Паттерн, который воспроизводит эта лаборатория

  • VERIFIED — GHSA-hvp3-26wx-g2w4 / CVE-2026-22706, Strapi: Password Reset Does Not Revoke Existing Refresh Sessions. @strapi/admin и @strapi/plugin-users-permissions ≤ 5.33.2; исправлено в 5.33.3. CVSS v4.0 2.1 (Low), AV:N/AC:H/AT:N/PR:H/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N. Опубликовано 2026-05-13. В уведомлении вендора указано, что шаг аннулирования refresh-токена был обусловлен переданным вызывающей стороной deviceId, и что патч аннулирует все refresh-токены при каждом изменении и сбросе пароля независимо от того, передан ли deviceId. Именно это условие моделирует _revoke_for_security_change, и именно этот патч реализует исправленный режим. Контекст вендора: Strapi security disclosure, May 2026.

Тот же класс, другие механизмы

  • REPORTED — CVE-2026-1163, parisneo/lollms. Аннулирование сессии при сбросе пароля отсутствует вообще, при стандартном времени жизни сессии 31 день. Простая версия этой ошибки — и та, которую наивный детектор действительно поймал бы.
  • REPORTED — CVE-2026-40934, Jupyter Server ≤ 2.17.0 (исправлено 2.18.0). Секрет подписи cookie сохраняется в статический файл и никогда не ротируется при изменении пароля, поэтому cookie, выданные до сброса, остаются криптографически действительными после него. Третий путь к тому же исходу: учётные данные здесь действительны, потому что ключ никогда не менялся, — а это режим отказа варианта D из docs/tradeoffs.md.

Три продукта, три механизма — условный параметр, отсутствующий вызов, неротированный ключ — один исход: сброс прошёл успешно, а злоумышленник остался.


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

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

Его обнаруживают, потому что цепочка учётных данных, которая должна была умереть при событии безопасности, была принята после него.


MIT · локальная лаборатория · ничто здесь не безопасно развёртывать

Скачать инструмент
dormant-survivorуязвимый15HIGHAFTERLIFE-00311
dormant-survivorисправленный19–нет00
mfa-changeуязвимый18HIGHAFTERLIFE-001, AFTERLIFE-00310
mfa-changeисправленный18–нет00
account-recoveryуязвимый18HIGHAFTERLIFE-001, AFTERLIFE-00310
account-recoveryисправленный18–нет00
expired-lineageуязвимый17LOWAFTERLIFE-00300
expired-lineageисправленный20–нет00