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

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

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

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

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

Категории

Все категории
Loading categories
Instatic-Stored-XSS-CVE-2026-103931 — Proof-of-concept и анализ хранимого XSS в URL-фильтре isSafeUrl() Instatic, где ведущие управляющие символы C0 обходят блокировку схемы javascript:. | Kitploit
Инструменты/GitHubGitHub/overgrowncarrot1/instatic-stored-xss-cve-2026-103931
Статический анализАнализ уязвимостейАнализ КодаЭксплуатацияЭксплуатация веб-приложенийВеб-безопасность
GitHubovergrowncarrot1/instatic-stored-xss-cve-2026-103931

Instatic-Stored-XSS-CVE-2026-103931

Proof-of-concept и анализ хранимого XSS в URL-фильтре isSafeUrl() Instatic, где ведущие управляющие символы C0 обходят блокировку схемы javascript:.

Репозиторий
1 день назадЕщё не проверено

Популярное

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

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

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

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

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

Обход фильтра URL-схем в isSafeUrl() с помощью ведущих управляющих символов C0

Затронуто: Instatic v0.0.13 / коммит 63ad5d6 (и все более ранние ревизии, содержащие src/core/html-sanitize/index.ts) Компонент: src/core/html-sanitize/index.ts → isSafeUrl() / safeUrl() Класс: CWE-79 (хранимая XSS) через CWE-184 (неполный список запрещённых входных данных)

Краткое описание

isSafeUrl() — единственная точка контроля, которая блокирует URL-адреса javascript:, vbscript: и data: во всём издателе. Она нормализует входные данные с помощью .replace(/[\t\n\r]/g, '').trim() перед проверкой префикса схемы.

Парсер URL по стандарту WHATWG удаляет все ведущие управляющие символы C0 (U+0000–U+001F) и пробел перед чтением схемы. JavaScript-метод String.prototype.trim() удаляет только U+0009, U+000A, U+000B, U+000C, U+000D, U+0020 и пробелы Unicode — он оставляет U+0000–U+0008 и U+000E–U+001F на месте.

Таким образом, URL с префиксом, например, U+0001, признаётся защитой безопасным, тогда как каждый браузер разбирает и выполняет его как схему javascript:.

Доказательство концепции

Против немодифицированного src/core/html-sanitize/index.ts:

payload           : "\x01javascript:alert(document.domain)"
isSafeUrl()       : true          <-- guard reports "safe"
WHATWG URL scheme : javascript:   <-- what the browser actually runs
safeUrl() output  : "\x01javascript:alert(document.domain)"   (NOT collapsed to "#")

27 из 32 управляющих символов C0 обходят проверку. U+0000 нейтрализуется при разборе HTML-атрибута (NUL → U+FFFD), оставляя 26 надёжно эксплуатируемых префиксов (U+0001–U+0008, U+000E–U+001F). Тот же обход обходит фильтры vbscript: и data:.

Существующий набор тестов (src/__tests__/publisher/utils.test.ts) покрывает приведение регистра и встроенные табуляции (java\tscript:), но никогда — ведущий управляющий символ, поэтому это не было обнаружено.

Сквозной путь

base.link объявляет href: { type: 'url' }, а LinkPropsSchema типизирует его как неограниченный Type.String({ default: '#' }) — проверка URL во время записи отсутствует. Следовательно:

  1. escapeProps() направляет свойства type: 'url' | 'image' | 'media' в isSafeUrl(value) ? value : '#' — полезная нагрузка проходит в исходном виде и намеренно без HTML-экранирования.
  2. render() из base.link выдаёт `<a href="https://github.com/overgrowncarrot1/instatic-stored-xss-cve-2026-103931/blob/main/%24%7BsafeUrl%28props.href%29%7D" …>`. safeUrl() повторно проверяет с помощью того же сломанного isSafeUrl(), затем escapeHtml() — который экранирует только & < > " ' и не трогает управляющие символы.
  3. Полезная нагрузка попадает дословно в атрибут href: <a href="https://github.com/overgrowncarrot1/instatic-stored-xss-cve-2026-103931/blob/main/%5Cx01javascript%3Aalert%28document.domain%29" target="_self">Click me</a>

Затронутые точки вывода

Все они проходят через один и тот же isSafeUrl():

Точка выводаФайл
Каждое свойство модуля url / image / media (ссылка href, кнопка href, изображение src, видео src/poster, форма action, форма redirectUrl)src/core/publisher/escapeProps.ts:108
Произвольные пользовательские HTML-атрибуты на любом узлеsrc/core/htmlAttributes/attributes.ts:66
Markdown-ссылки/изображения href и srcsrc/core/markdown/renderMarkdown.ts:75
faviconUrl сайтаsrc/core/publisher/render.ts:322
Вызов safeUrl() в каждом базовом модулеsrc/modules/base/utils/escape.ts

Воздействие

Повышение привилегий от низкопривилегированного редактора до полной компрометации администратора.

Встроенная роль Client обладает site.content.edit, чего достаточно для установки href ссылки или пользовательского HTML-атрибута. Внедрённый URL затем отображается в холсте редактора администратора, который представляет собой iframe srcdoc — того же источника, что и /admin.

server/securityHeaders.ts:69-72 устанавливает только frame-ancestors 'none'; base-uri 'self'; object-src 'none' для /admin, с явным комментарием в коде, что политика script-src намеренно ещё не задана. Без script-src ничто не мешает URL javascript: выполняться в источнике администратора.

Когда Owner или Admin открывает затронутую страницу в редакторе и активирует элемент, полезная нагрузка выполняется в том же источнике, что и SPA администратора. Сессионный cookie имеет флаг HttpOnly, поэтому его нельзя прочитать напрямую — но полезная нагрузка может управлять API администратора от имени жертвы (создать учётную запись владельца, прочитать секреты или установить плагин, серверная точка входа которого ведёт к выполнению кода).

На опубликованном сайте воздействие уже: cspPlan.ts устанавливает script-src 'none' (или 'self'), что блокирует URL javascript:. Однако server/publish/frontendInjections.ts:377 ослабляет это до 'self' 'unsafe-inline' для любой страницы, содержащей встроенный скрипт, а 'unsafe-inline' снова разрешает URL javascript: — так что XSS на опубликованном сайте достижима на таких страницах.

Обратите внимание, что docstring в attributes.ts уже определяет именно эту модель угроз («на опубликованном сайте И, что серьёзнее, внутри холста редактора администратора (того же источника, что и /admin)») — защита просто не реализует её полностью.

Проверка

Подтверждено в браузере (Chromium, iframe srcdoc, воспроизводящие CSP каждого источника, отображающие побайтовый вывод собственного safeUrl() репозитория):

Воспроизведённый источникПрименённая CSPРезультат
Холст редактора администратора (/admin)нет — как выдаёт securityHeaders.tsjavascript: выполнен
Опубликованная страница (базовая)script-src 'none' — как выдаёт cspPlan.tsзаблокировано (script-src-elem)
Опубликованная страница со встроенным скриптомscript-src 'self' 'unsafe-inline' — как выдаёт frontendInjections.ts:377см. примечание

Первые две строки — наблюдаемые результаты. Выполнение в источнике администратора сообщило document.domain как источник обслуживания, подтверждая выполнение в том же источнике, а не в непрозрачном контексте.

Оговорка о точности: тестовый стенд применяет каждую политику через <meta http-equiv>, тогда как Instatic отправляет её как HTTP-заголовок ответа. Они эквивалентны для принудительного применения script-src, но воспроизведение на живом экземпляре bun run dev дало бы подлинные заголовки.

Исправление

Удаляйте весь диапазон C0 + пробел вместо того, чтобы полагаться на trim(). Именно это делает собственный regex isJavaScriptProtocol из React с его префиксом ^[\u0000-\u001F ]* — полезная перекрёстная проверка того, что это известный класс обходов в реальном мире, а не теоретический.

--- a/src/core/html-sanitize/index.ts
+++ b/src/core/html-sanitize/index.ts
@@ -30,7 +30,11 @@ export function escapeHtml(value: unknown): string {
   * normalisation browsers apply during URL parsing.
   */
  export function isSafeUrl(url: string): boolean {
-  const normalized = url.replace(/[\t\n\r]/g, '').trim().toLowerCase()
+  const normalized = String(url ?? '')
+    .replace(/[\t\n\r]/g, '')
+    .replace(/^[\u0000-\u0020]+/, '')
+    .replace(/[\u0000-\u0020]+$/, '')
+    .toLowerCase()
    return (
      !normalized.startsWith('javascript:') &&
      !normalized.startsWith('vbscript:') &&

Проверено на исправленном файле: полезная нагрузка блокируется, safeUrl() сворачивает её в #, 0 из 99 опасных URL с префиксом управляющего символа/пробела остаются принятыми, и все 14 существующих тестовых случаев isSafeUrl ведут себя идентично.

Предлагаемый регрессионный тест

it('blocks javascript: behind a leading C0 control character', () => {
  for (let i = 0x00; i <= 0x1f; i++) {
    expect(isSafeUrl(String.fromCharCode(i) + 'javascript:alert(1)')).toBe(false)
  }
})
Скачать инструмент