Инъекции через локали

Локализация в JavaScript через Intl API опирается на стандарты BCP 47 и данные CLDR, где ключевым параметром выступает строка локали. Эта строка влияет на формат чисел, дат, валют, единиц измерения, сортировку и отображение языковых вариантов. Несмотря на кажущуюся безобидность, локаль становится точкой входа для класса уязвимостей, связанных с подменой форматирования и косвенными инъекциями через интернационализацию.

Практически все основные конструкторы Intl принимают строку или массив строк локалей:

  • Intl.NumberFormat
  • Intl.DateTimeFormat
  • Intl.Collator
  • Intl.RelativeTimeFormat
  • Intl.ListFormat
  • Intl.DisplayNames

Каждый из них допускает передачу пользовательского значения:

new Intl.DateTimeFormat(userLocale)
new Intl.NumberFormat(userLocale)

Если значение локали формируется на основе внешнего ввода (параметры URL, заголовки HTTP, профиль пользователя, данные из БД), появляется возможность вмешательства в поведение форматирования.

Нормативная модель локали и допустимые отклонения

BCP 47 определяет строгий синтаксис языковых тегов:

  • базовый язык: en, ru, de
  • регион: en-US, ru-KZ
  • скрипт: zh-Hans, sr-Cyrl
  • варианты и расширения: sl-rozaj, en-US-u-ca-gregory

Однако реализация в JavaScript допускает более гибкое поведение:

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

Эта гибкость создаёт возможность «смещения смысла» локали, когда входная строка не вызывает ошибку, но приводит к неожиданному результату форматирования.

Инъекции через расширения Unicode Extension

Наиболее значимая зона риска — Unicode extension sequences (-u-), которые позволяют изменять параметры форматирования:

en-US-u-nu-latn-ca-buddhist

Расширения могут влиять на:

  • календарь (ca)
  • систему чисел (nu)
  • порядок сортировки (co)
  • часовой формат (hc)

При неконтролируемом вводе локали возможны сценарии, где злоумышленно сформированная строка изменяет отображение данных без изменения бизнес-логики.

Пример:

new Intl.NumberFormat("ru-RU-u-nu-roman").format(2024)

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

Подмена семантики через региональные вариации

Региональный код влияет не только на формат, но и на культурные правила:

  • en-US vs en-GB (валюта, порядок дат)
  • fr-FR vs fr-CA (разделители, орфография в некоторых форматах)
  • de-DE vs de-AT

При использовании локали из пользовательского профиля без валидации возможно:

  • изменение валютного символа
  • изменение позиции валюты относительно числа
  • изменение десятичного разделителя
new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" })

и

new Intl.NumberFormat("en-US", { style: "currency", currency: "EUR" })

дают различное визуальное представление одной и той же суммы.

Если локаль подставляется динамически, это превращается в канал управления визуальной интерпретацией финансовых данных.

Инъекция через невалидные и «почти валидные» теги

Intl API не всегда жёстко отклоняет некорректные строки. Многие значения проходят через механизм best-fit fallback:

  • en_US → интерпретируется как en-US
  • ru-KZ-variant-x-private → частично игнорируется
  • x-private → пользовательские расширения

Эта особенность приводит к тому, что:

  • синтаксически «грязные» строки не вызывают ошибок
  • часть данных отбрасывается, часть влияет на результат

Пример:

new Intl.DateTimeFormat("en-US-x-internal-hack")

Результат остаётся валидным форматтером, но исходная строка теряет предсказуемость.

Подмена через массив локалей

Все конструкторы Intl принимают массив локалей:

new Intl.NumberFormat(["xx-INVALID", "ru-RU", "en"])

Алгоритм выбора:

  1. перебор массива слева направо
  2. выбор первой поддерживаемой локали
  3. fallback на системную

Если входной массив формируется извне, появляется возможность:

  • навязать приоритетную локаль
  • вызвать неожиданный fallback
  • добиться различий между средами (server vs browser)

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

Инъекции через цепочки форматирования

Локаль влияет на результат форматирования, который затем может попадать в другие системы:

  • HTML-рендеринг
  • логирование
  • экспорт в CSV
  • генерация PDF

При этом возникает цепочка:

  1. пользователь задаёт локаль
  2. Intl форматирует данные
  3. результат интерпретируется другой системой

Если форматированный текст воспринимается как структурированный (например, CSV или шаблон), возможны вторичные инъекции.

Пример сценария:

  • 1 000,00 (de-DE)
  • 1,000.00 (en-US)

При парсинге без учёта локали возможна некорректная интерпретация числа.

Коллизии с нормализацией Unicode

Некоторые локали используют разные формы Unicode-нормализации и символы:

  • неразрывные пробелы
  • узкие пробелы
  • специфические разделители групп разрядов
new Intl.NumberFormat("fr-FR").format(1000000)

может вернуть:

1 000 000

где разделитель — U+202F (narrow no-break space)

При копировании или сравнении строк это приводит к:

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

Подмена через fallback-цепочки

Intl использует механизм fallback:

  • точная локаль
  • более общая локаль
  • базовый язык
  • системная локаль

Пример:

zh-Hans-CN-x-private
→ zh-Hans-CN
→ zh-Hans
→ zh
→ default

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

Манипуляция сортировкой через Intl.Collator

Intl.Collator особенно чувствителен к локали:

new Intl.Collator("de").compare("ä", "z")

и

new Intl.Collator("sv").compare("ä", "z")

дают разные результаты порядка сортировки.

Если локаль задаётся извне, возможны сценарии:

  • изменение порядка отображения списков
  • скрытие элементов при фильтрации
  • нарушение бизнес-логики сортировки (например, рейтинги, очереди)

Особенно критично при использовании sensitivity, numeric, caseFirst.

Косвенные инъекции через формат дат

Intl.DateTimeFormat использует локальные правила:

  • порядок день/месяц/год
  • названия месяцев
  • формат времени 12/24
  • разделители
new Intl.DateTimeFormat("en-US")
new Intl.DateTimeFormat("en-GB")

одна и та же дата превращается в разные строки:

  • 05/27/2026
  • 27/05/2026

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

Защитные модели обработки локалей

Безопасная работа с Intl строится вокруг принципов контроля входных данных:

Ограничение допустимых локалей

Использование allowlist:

  • фиксированный список поддерживаемых локалей
  • игнорирование произвольных BCP 47 строк

Нормализация и валидация

Проверка через:

Intl.getCanonicalLocales(locale)

позволяет привести строку к каноническому виду и выявить некорректные значения.

Разделение пользовательского и системного контекста

  • локаль интерфейса может быть пользовательской
  • локаль бизнес-логики должна быть фиксированной

Это предотвращает влияние локали на вычисления, где важна однозначность.

Запрет передачи локали в критические вычисления

Intl должен использоваться только для представления данных, но не для принятия решений:

  • сортировка в UI допустима
  • сортировка в алгоритмах обработки данных — источник нестабильности

Особенности серверных окружений

Node.js и браузеры используют разные версии ICU (International Components for Unicode). Это приводит к:

  • различию в поддерживаемых локалях
  • различию в fallback-цепочках
  • различию в форматировании

Если локаль не фиксирована, результат Intl может отличаться между средами, создавая эффект «логической инъекции через окружение».

Инъекция через форматирование как побочный канал

Формально Intl не выполняет код и не интерпретирует шаблоны. Однако локаль влияет на представление данных, которое может:

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

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