Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Instatic-Stored-XSS-CVE-2026-103931 — Instatic의 isSafeUrl() URL 필터에서 선행 C0 제어 문자가 javascript: 스킴 차단을 우회하는 저장형 XSS의 개념 증명 및 분석. | Kitploit
도구/GitHubGitHub/overgrowncarrot1/instatic-stored-xss-cve-2026-103931
Static AnalysisVulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationWeb Security
GitHubovergrowncarrot1/instatic-stored-xss-cve-2026-103931

Instatic-Stored-XSS-CVE-2026-103931

Instatic의 isSafeUrl() URL 필터에서 선행 C0 제어 문자가 javascript: 스킴 차단을 우회하는 저장형 XSS의 개념 증명 및 분석.

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
저장소 보기
1일 전아직 검토되지 않음
공유

isSafeUrl()의 URL 스킴 필터 우회 — 선행 C0 제어 문자를 통한 우회

영향 받는 버전: Instatic v0.0.13 / 커밋 63ad5d6 (그리고 src/core/html-sanitize/index.ts를 포함하는 모든 이전 리비전) 구성 요소: src/core/html-sanitize/index.ts → isSafeUrl() / safeUrl() 분류: CWE-184(불완전한 금지 입력 목록)를 통한 CWE-79(저장형 XSS)

요약

isSafeUrl()은 전체 퍼블리셔에서 javascript:, vbscript:, data: URL을 차단하는 단일 초크포인트입니다. 이 함수는 스킴 접두사를 검사하기 전에 .replace(/[\t\n\r]/g, '').trim()으로 입력을 정규화합니다.

WHATWG URL 파서는 스킴을 읽기 전에 모든 선행 C0 제어 문자(U+0000–U+001F)와 공백을 제거합니다. JavaScript의 String.prototype.trim()은 U+0009, U+000A, U+000B, U+000C, U+000D, U+0020 및 유니코드 공백만 제거하며, U+0000–U+0008과 U+000E–U+001F는 그대로 남깁니다.

따라서 예를 들어 U+0001이 앞에 붙은 URL은 가드에 의해 안전하다고 보고되지만, 모든 브라우저는 이를 javascript: 스킴으로 파싱하고 실행합니다.

개념 증명

수정되지 않은 src/core/html-sanitize/index.ts에 대해:

payload           : "\x01javascript:alert(document.domain)"
isSafeUrl()       : true          <-- 가드가 "안전"으로 보고
WHATWG URL scheme : javascript:   <-- 브라우저가 실제로 실행하는 것
safeUrl() output  : "\x01javascript:alert(document.domain)"   ("#"으로 축소되지 않음)

32개의 C0 제어 문자 중 27개가 검사를 우회합니다. 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' prop을 isSafeUrl(value) ? value : '#'로 라우팅합니다 — 페이로드는 원시 상태로, 의도적으로 HTML 이스케이프 없이 통과합니다.
  2. base.link의 render()는 `<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 모듈 prop (link href, button href, image src, video src/poster, form action, form redirectUrl)src/core/publisher/escapeProps.ts:108
모든 노드에 임의로 사용자가 설정한 커스텀 HTML 속성src/core/htmlAttributes/attributes.ts:66
Markdown 링크/이미지 href 및 srcsrc/core/markdown/renderMarkdown.ts:75
사이트 faviconUrlsrc/core/publisher/render.ts:322
모든 base 모듈의 safeUrl() 호출src/modules/base/utils/escape.ts

영향

낮은 권한의 편집자에서 전체 관리자 침해로의 권한 상승.

내장된 Client 역할은 site.content.edit을 보유하며, 이는 링크 href 또는 커스텀 HTML 속성을 설정하기에 충분합니다. 주입된 URL은 관리자 편집기 캔버스에서 렌더링되며, 이는 /admin과 동일 출처인 srcdoc iframe입니다.

server/securityHeaders.ts:69-72는 /admin에 frame-ancestors 'none'; base-uri 'self'; object-src 'none'만 설정하며, script-src 정책은 의도적으로 아직 설정하지 않는다는 명시적 코드 주석이 있습니다. script-src가 없으면 javascript: URL이 관리자 출처에서 실행되는 것을 막을 것이 없습니다.

Owner 또는 Admin이 편집기에서 영향 받는 페이지를 열고 요소를 활성화하면, 페이로드는 관리자 SPA와 동일 출처에서 실행됩니다. 세션 쿠키는 HttpOnly이므로 직접 읽을 수는 없지만, 페이로드는 피해자로서 관리자 API를 조작할 수 있습니다(owner 계정 생성, 시크릿 읽기, 또는 서버 진입점이 코드 실행 경로인 플러그인 설치).

게시된 사이트에서는 영향이 더 좁습니다: cspPlan.ts는 script-src 'none'(또는 'self')을 설정하여 javascript: URL을 차단합니다. 그러나 server/publish/frontendInjections.ts:377은 인라인 스크립트를 포함하는 모든 페이지에 대해 이를 'self' 'unsafe-inline'으로 완화하며, 'unsafe-inline'은 javascript: URL을 다시 허용합니다 — 따라서 해당 페이지에서는 게시된 사이트 XSS에 도달할 수 있습니다.

attributes.ts의 docstring은 이미 이 정확한 위협 모델("게시된 사이트에서 그리고 더 심각하게는 관리자 편집기 캔버스 내부(/admin과 동일 출처)에서")을 식별하고 있다는 점에 주목하십시오 — 가드가 이를 완전히 구현하지 않았을 뿐입니다.

검증

브라우저에서 확인됨(Chromium, 각 출처의 CSP를 재현하는 srcdoc iframe, 리포지토리 자체의 safeUrl() 출력을 바이트 단위로 렌더링):

재현된 출처적용된 CSP결과
관리자 편집기 캔버스 (/admin)없음 — securityHeaders.ts가 내보내는 대로javascript: 실행됨
게시된 페이지 (기준)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 인스턴스에서의 재현은 진짜 헤더를 사용하게 될 것입니다.

수정

trim()에 의존하는 대신 전체 C0 + 공백 범위를 제거하십시오. 이것이 바로 React 자체의 isJavaScriptProtocol 정규식이 ^[\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()이 이를 #으로 축소하며, 제어/공백 접두사가 붙은 위험한 URL 99개 중 0개가 여전히 허용되고, 기존 isSafeUrl 테스트 케이스 14개 모두 동일하게 동작합니다.

권장 회귀 테스트

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)
  }
})

심층 방어

/admin에 실제 script-src를 설정하는 것도 고려하십시오(server/securityHeaders.ts에 명시된 지연된 후속 작업). 이는 이 하나의 인스턴스가 아니라 이 전체 버그 클래스를 억제할 것입니다.

도구 다운로드