Защита от XSS при работе с ключами в памяти

XSS-уязвимости в JavaScript-криптографии превращают даже корректно реализованные алгоритмы в компрометируемые системы, поскольку атакующий получает доступ не к «алгоритму», а к среде выполнения, где ключи уже находятся в расшифрованном виде. В контексте использования Stanford JavaScript Crypto Library (SJCL) это означает, что основной риск сосредоточен не в криптографических примитивах, а в жизненном цикле ключевого материала внутри памяти браузера.

При XSS-атаках злоумышленник получает возможность исполнять произвольный JavaScript-код в контексте доверенного приложения. Это автоматически разрушает границу безопасности, потому что:

  • весь heap JavaScript становится доступен для чтения через инструменты выполнения кода;
  • любые переменные, содержащие ключи, могут быть извлечены напрямую;
  • можно перехватывать вызовы функций шифрования/дешифрования;
  • возможно внедрение логирующих и перехватывающих обёрток (hooking) на уровне API.

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

Особенности хранения ключей в SJCL

SJCL использует несколько типов представления данных:

  • sjcl.bitArray — основной формат бинарных данных;
  • строки (UTF-8/hex/base64 представления);
  • объекты JavaScript, содержащие промежуточные состояния;
  • кэшированные результаты PBKDF2 и других KDF.

Критическая особенность заключается в том, что JavaScript не предоставляет гарантированного контроля над физическим расположением данных в памяти и не позволяет надежно очищать строки после использования. Даже если разработчик «перезапишет» переменную, это не гарантирует удаления всех копий данных, созданных движком (V8, SpiderMonkey и др.).

Проблема неизменяемых строк и утечек через копирование

Ключевой риск возникает при использовании строк:

  • строки в JavaScript неизменяемы;
  • любое преобразование (например, конкатенация или декодирование) создаёт новую копию;
  • старые версии могут оставаться в памяти до сборки мусора;
  • сборщик мусора не предназначен для security wiping.

Это означает, что даже корректная очистка переменной вида:

key = null;

не гарантирует удаления ключа из памяти.

В SJCL предпочтительно использовать bitArray, так как он хотя бы допускает частичное перезаписывание содержимого.

Утечки через XSS-поверхности доступа

После успешной инъекции скрипта атакующий получает несколько каналов доступа к ключам:

Перехват API SJCL

Можно переопределить функции:

  • sjcl.encrypt
  • sjcl.decrypt
  • sjcl.misc.pbkdf2

и извлекать входные параметры до обработки.

Сканирование памяти объектов

JavaScript позволяет обходить глобальные пространства:

  • window
  • замыкания (через перехваченные ссылки)
  • DOM-объекты с привязанными данными

Любой объект, содержащий ключ, становится доступным через обход графа объектов.

Логирование и побочные эффекты

Атака часто использует:

  • переопределение console.log;
  • hook fetch / XMLHttpRequest;
  • подмену обработчиков событий.

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

Неправильные модели хранения ключей

Наиболее опасные практики:

Глобальное хранение ключей

window.masterKey = sjcl.codec.hex.toBits(hexKey);

Такой подход делает ключ доступным из любой XSS-инъекции через window.masterKey.

Долгоживущие ключи в singleton-объектах

Любой singleton, содержащий ключ, становится постоянной точкой компрометации.

Кэширование ключей без контроля времени жизни

Если ключ остаётся в памяти дольше одной операции — он уже потенциально украден при XSS.

Минимизация времени жизни ключа

Основной принцип защиты — сокращение окна существования ключевого материала в памяти.

Правильная модель:

  • ключ создаётся;
  • используется один раз;
  • немедленно уничтожается (перезапись массива);
  • ссылка удаляется.

Пример работы с bitArray:

let key = sjcl.random.randomWords(8);

let encrypted = sjcl.encrypt(key, "data");

// использование завершено
for (let i = 0; i < key.length; i++) {
    key[i] = 0;
}
key = null;

Хотя это не гарантирует полное удаление всех копий, это снижает вероятность утечки.

Изоляция криптографических операций

Сильное снижение риска достигается архитектурными методами:

Web Worker изоляция

Ключи можно ограничить воркером:

  • UI не имеет доступа к ключу;
  • воркер уничтожается после операции;
  • память воркера очищается при завершении.

Однако XSS внутри воркера всё ещё возможен, если сам код воркера скомпрометирован.

Отсутствие хранения ключей в DOM-слое

DOM — одна из самых частых точек XSS-эксплуатации. Любая привязка ключей к:

  • data-атрибутам;
  • input fields;
  • скрытым элементам

является прямым риском.

Ограничение поверхности XSS как криптографическая мера

Хотя SJCL не занимается безопасностью приложения, на практике криптографическая устойчивость напрямую зависит от XSS-защиты:

  • строгий Content Security Policy (CSP);
  • запрет inline-scripts;
  • отказ от eval и динамического Function;
  • sanitization всех входных данных;
  • изоляция сторонних библиотек.

Без этого любые меры по защите памяти становятся вторичными.

Проблема сериализации ключей

SJCL часто используется с JSON-представлением:

sjcl.encrypt(password, data)

Результат включает:

  • ciphertext;
  • salt;
  • iv;
  • iter count.

При неправильной обработке возможно:

  • сохранение промежуточных объектов;
  • кэширование расшифрованного состояния;
  • повторное использование объектов с ключами.

Важно избегать хранения расшифрованных структур в состоянии приложения.

Косвенные утечки через отладочные механизмы

Опасность создают:

  • DevTools консоль;
  • глобальные state stores (Redux/Vuex);
  • source maps в продакшене.

XSS может:

  • читать state store;
  • перехватывать actions;
  • получать доступ к временным ключам.

Проблема доверия к библиотеке против доверия к среде

SJCL корректно реализует криптографию на уровне алгоритмов, но:

  • не может защитить от runtime-инъекций;
  • не контролирует память JS-движка;
  • не изолирует execution context.

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

Практика безопасного обращения с ключами

На уровне архитектуры:

  • ключи не должны быть долгоживущими объектами;
  • не должны находиться в глобальной области видимости;
  • должны создаваться только в момент использования;
  • должны храниться в минимально возможной форме (bitArray предпочтительнее строк);
  • должны быть удалены сразу после операции.

На уровне приложения:

  • исключение XSS как класса уязвимостей является обязательным условием;
  • криптография не компенсирует компрометацию исполнения кода.

Ограничения JavaScript как среды для секретов

Даже при идеальной реализации SJCL остаются фундаментальные ограничения:

  • невозможность гарантированного zeroization памяти;
  • отсутствие контроля над копиями объектов;
  • отсутствие аппаратной изоляции ключей;
  • прозрачность runtime для любого внедрённого кода.

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