Fuzzing-тестирование парсеров токенов

Fuzzing-тестирование (fuzz testing) — это метод автоматической генерации большого количества случайных или полуслучайных входных данных для выявления ошибок в обработке. В контексте библиотеки jose для JavaScript особое внимание уделяется парсерам и валидаторам токенов: JWT (JSON Web Token), JWS (JSON Web Signature) и JWE (JSON Web Encryption).

Основные задачи fuzzing-тестирования:

  • выявление уязвимостей в парсинге токенов
  • обнаружение некорректной обработки повреждённых или нестандартных входных данных
  • проверка устойчивости к атакам (например, malformed tokens, oversized payloads)
  • выявление проблем с безопасностью: обход валидации, ошибки в декодировании base64url и JSON

Особенности структуры токенов в jose

Парсеры библиотеки jose работают с чётко определёнными форматами:

  • JWT: header.payload.signature
  • JWS: подпись с использованием алгоритмов (RS256, ES256 и др.)
  • JWE: зашифрованные токены с несколькими сегментами

Каждый сегмент кодируется в base64url и должен корректно декодироваться в JSON.

Fuzzing направлен на нарушение этих предположений:

  • повреждение структуры (неправильное количество сегментов)
  • нарушение кодировки (невалидный base64url)
  • вставка некорректного JSON
  • подмена алгоритмов и заголовков

Интеграция fuzzing в тестовую среду

Для JavaScript-проектов с jose часто используются:

  • jest или vitest для тестов
  • библиотеки fuzzing: fast-check, jsfuzz, fuzzilli (для движков JS)

Пример базовой интеграции с fast-check:

import fc from 'fast-check'
import { jwtVerify } from 'jose'

test('fuzz JWT parsing', async () => {
  await fc.assert(
    fc.asyncProperty(fc.string(), async (input) => {
      try {
        await jwtVerify(input, new TextEncoder().encode('secret'))
      } catch (e) {
        // Ожидаем, что ошибки возможны, но не должно быть крашей
        expect(e).toBeInstanceOf(Error)
      }
    })
  )
})

Ключевой принцип: любые входные данные допустимы, но система не должна аварийно завершаться.


Типы fuzzing-атак на парсеры токенов

1. Структурный fuzzing

Проверяется устойчивость к нарушению формата:

const malformedTokens = [
  '',
  'abc',
  'a.b',
  'a.b.c.d',
  '....',
]

Цель — убедиться, что:

  • парсер корректно выбрасывает ошибки
  • отсутствуют необработанные исключения

2. Fuzzing base64url-декодирования

Тестируются ошибки декодирования:

fc.string().map(str => str.replace(/=/g, '')) // удаление padding

Проверяются:

  • некорректные символы (+, /, %)
  • обрезанные строки
  • чрезмерно длинные сегменты

3. JSON fuzzing

После декодирования токена происходит парсинг JSON:

const invalidJSON = [
  '{"alg":}',
  '{"alg":"HS256",}',
  '{alg:"HS256"}',
]

Задача:

  • убедиться, что JSON.parse не приводит к уязвимостям
  • ошибки корректно обрабатываются

4. Fuzzing заголовков (header)

Особенно важен параметр alg:

const headers = [
  { alg: 'none' },
  { alg: '' },
  { alg: 'INVALID' },
  { alg: 'HS256', typ: null },
]

Проверяется:

  • невозможность подмены алгоритма
  • защита от alg: none атак
  • корректная валидация допустимых алгоритмов

5. Payload fuzzing

Payload может содержать произвольные данные:

fc.record({
  sub: fc.string(),
  exp: fc.integer(),
  admin: fc.boolean(),
  data: fc.anything()
})

Проверяются:

  • переполнение памяти
  • обработка нестандартных типов
  • корректность проверки временных меток (exp, nbf)

6. Signature fuzzing

Подпись может быть:

  • отсутствующей
  • слишком короткой
  • случайной строкой
const token = `${header}.${payload}.invalidsignature`

Ожидаемое поведение:

  • строгая проверка подписи
  • отсутствие fallback-механизмов

Stateful fuzzing

Более продвинутый подход — моделирование последовательности операций:

  1. Генерация токена
  2. Мутация
  3. Повторная проверка
fc.asyncProperty(fc.string(), async (secret) => {
  const token = await new SignJWT({ foo: 'bar' })
    .setProtectedHeader({ alg: 'HS256' })
    .sign(new TextEncoder().encode(secret))

  const mutated = token.replace(/\./g, '..')

  await expect(jwtVerify(mutated, new TextEncoder().encode(secret)))
    .rejects.toThrow()
})

Coverage-guided fuzzing

Используется для повышения эффективности тестов:

  • анализ покрытия кода
  • генерация входных данных, достигающих новых веток

Инструменты:

  • c8 или nyc для покрытия
  • интеграция с fuzzing-движками

Цель — достичь:

  • всех веток обработки ошибок
  • редких edge-case сценариев

Обнаружение уязвимостей

Fuzzing позволяет выявить:

Переполнение буфера

При работе с длинными строками base64

DoS-атаки

Через:

  • огромные payload’ы
  • deeply nested JSON

Некорректную валидацию

Например:

  • пропуск проверки exp
  • игнорирование aud или iss

Логические ошибки

Например:

  • принятие токена без подписи
  • fallback на небезопасные алгоритмы

Минимизация и воспроизводимость

После обнаружения ошибки важно:

  1. Сохранить входные данные
  2. Минимизировать их (shrink)

fast-check автоматически делает это:

fc.assert(property, { verbose: true })

Результат — минимальный пример, вызывающий ошибку.


Практики безопасного fuzzing

  • изоляция тестов (sandbox)
  • ограничение времени выполнения
  • контроль потребления памяти
  • логирование всех падений

Интеграция в CI/CD

Fuzzing может выполняться:

  • как часть nightly-сборок
  • при релизах
  • в security pipeline

Пример:

npm run fuzz

С автоматическим:

  • сохранением crash-репортов
  • уведомлением команды

Дифференциальное тестирование

Сравнение поведения jose с другими библиотеками:

  • jsonwebtoken
  • node-jose

Если одна библиотека принимает токен, а другая — нет, это повод для анализа.


Генерация мутаций токенов

Примеры мутаций:

  • изменение одного символа
  • удаление сегмента
  • перестановка частей
  • инъекция бинарных данных
function mutate(token) {
  return token.slice(0, -1) + String.fromCharCode(Math.random() * 128)
}

Тестирование JWE

Особенно сложный случай:

  • множество сегментов
  • различные алгоритмы шифрования

Fuzzing проверяет:

  • корректность расшифровки
  • обработку неверных ключей
  • устойчивость к повреждённым ciphertext

Инструменты автоматизации

  • fast-check — property-based fuzzing
  • AFL (через обёртки)
  • libFuzzer (через WASM)
  • jazzer.js — coverage-guided fuzzing для JS

Метрики качества fuzzing

  • code coverage
  • количество уникальных ошибок
  • время до первого crash
  • глубина обхода кода

Частые ошибки при реализации

  • игнорирование исключений
  • отсутствие проверки результатов
  • слишком узкие генераторы данных
  • отсутствие shrink-механизма

Повышение эффективности

  • комбинирование ручных и автоматических тестов
  • использование seed-данных (реальные токены)
  • фокус на критических участках: парсинг, криптография

Роль fuzzing в безопасности JWT

Fuzzing — один из ключевых инструментов для:

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

Особенно важен в:

  • API-сервисах
  • OAuth/OpenID реализациях
  • микросервисной архитектуре