기본 구조

1
<a href="https://example.com">링크</a>`
  1. 태그: 요소 하나를 만드는 명령. <...>로 감쌈 (<a><img><iframe>)
  2. 속성: 태그에 붙는 설정값. 이름=값 꼴 (href="..."src="...")
  3. URL: 자원의 주소 문자열. 보통 속성 값으로 들어감 (https://...javascript:...)
    포함 관계: 태그 안에 속성, 속성 값 안에 URL

주입 컨텍스트

입력이 페이지 어디에 나타나는지부터 확인 → 위치가 정하는 것 세 가지

  1. 태그 탈출: 속성값 "..." 안이면 ">로 먼저 닫고 나오기, 본문이면 바로 태그 작성
  2. 디코딩: HTML 파서가 읽는 위치만 엔티티를 글자로 디코딩(&lpar;() <script> 안은 불가
  3. 실행 방법: URL 자리면 javascript:, 본문이면 <img onerror>
컨텍스트예시 자리발화/탈출
HTML 본문<div>입력</div>태그 직접 주입 (<script>, <img onerror>)
태그 속성값<input value="입력">"> 로 속성·태그 깨고 새 태그, 또는 이벤트 핸들러 속성이면 그 자체로
스크립트 내부<script>x="입력"</script>문자열·태그 닫고("; 또는 </script>) 코드 주입
URL 자리<a href="입력">javascript: 스킴

태그 / 속성

  • 태그: 요소 생성 명령 (<img>, <iframe>, <svg> 등)
  • 속성: 태그 설정값 (src=, href=, on*=)
  • 인용 3가지: "...", '...', 인용 없음. 인용 없는 속성은 공백에서 끝남 → <img src=x onerror=...> 가능
  • 대소문자 무시: <ScRiPt> = <script> → 필터가 소문자 기준이면 우회점
  • 닫는 태그 불필요한 것 많음 (<img>, <br>, <input>)
  • 속성 구분자 유연: 공백·탭·줄바꿈·슬래시 다 가능 → <img/src=x/onerror=...>

JS 실행

브라우저가 HTML 안에서 JS를 발화시키는 통로는 셋:

입구형태발화 시점비고
script 태그<script>코드</script>파서가 만나는 즉시내부는 JS 엔진 담당 → 엔티티 해독 안 됨
이벤트 핸들러onerror= onload onclick해당 이벤트 발생 시속성 자리라 엔티티 풀림
URL 스킴href/src="javascript:코드"그 URL로 이동 시script/on 안 거침

자동 실행

봇/관리자는 페이지 열람 → 클릭/입력 없이 터지는 벡터

벡터발동 조건
<iframe src=...>로드되며 자동
<svg onload=...>렌더 시 자동
<img src=x onerror=...>잘못된 src로 에러 유발
<body onload=...>문서 로드 시
<video><source onerror=...>소스 로드 실패 시
<a href=...>클릭 필요 → 자동 아님

파싱 주체: HTML 파서 vs JS 엔진

같은 글자라도 누가 읽느냐로 해독 여부가 갈림

  • HTML 파서가 읽는 자리(본문/속성값) → 엔티티 해독함
  • JS 엔진이 직행하는 자리(<script> 내부) → 엔티티 해독 안 함
  • 이벤트 핸들러 속성: HTML 파서가 속성값 먼저 디코딩한 뒤 JS로 넘김 → &lpar;가 속성 자리선 (로 풀려 우회 성립 / <script> 안에선 JS 직행이라 그대로 깨져 우회 불가

HTML 엔티티

특수문자를 &이름;으로 적는 표기 → 필터는 &lpar;(로 못 보고 통과 → 브라우저는 (로 되돌림

종류결과
네임드&lt; &gt; &quot;< > "
네임드(괄호)&lpar; &rpar;( )
네임드(공백)&Tab; &NewLine;탭, 줄바꿈
숫자&#40;(
16진&#x28;(

네임드는 &+이름, 숫자/16진은 &#으로 시작 → 필터가 &#만 막으면 네임드로 우회

URL 컨텍스트 특성

스킴 실행: javascript:코드는 주소 이동 대신 코드 실행 → data:text/html,...는 그 내용을 페이지로 띄움

공백 자동 삭제: 브라우저가 URL을 읽기 전에 그 안의 탭/줄바꿈을 전부 지움 → 막힌 단어 중간에 탭 → 필터는 단어가 끊겨서 식별 불가 → 브라우저는 탭을 지워 원래 단어로 복원

javascript: 뒤는 전체가 URL이라 → 공백 삭제가 코드 안 단어에도 똑같이 적용

1
docu&Tab;ment.cookie   →   document.cookie

괄호 없이 호출

( )가 막힐 때 함수 실행:

  • 백틱: alert`1` 함수 뒤 백틱이 괄호 대신 호출, 순수 JS
  • 엔티티: alert&lpar;1&rpar;: 파서가 ( )로 복원(속성만)
  • toString: 객체를 문자열화(obj+'')하면 obj.toString이 자동 호출
    → 괄호 없이 호출 → 필터는 글자만 막고 동작은 못 막음