기본 구조
| |
- 태그: 요소 하나를 만드는 명령.
<...>로 감쌈 (<a>,<img>,<iframe>) - 속성: 태그에 붙는 설정값.
이름=값꼴 (href="...",src="...") - URL: 자원의 주소 문자열. 보통 속성 값으로 들어감 (
https://...,javascript:...)
포함 관계: 태그 안에 속성, 속성 값 안에 URL
주입 컨텍스트
입력이 페이지 어디에 나타나는지부터 확인 → 위치가 정하는 것 세 가지
- 태그 탈출: 속성값
"..."안이면">로 먼저 닫고 나오기, 본문이면 바로 태그 작성 - 디코딩: HTML 파서가 읽는 위치만 엔티티를 글자로 디코딩(
(→()<script>안은 불가 - 실행 방법: 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로 넘김 →
(가 속성 자리선(로 풀려 우회 성립 /<script>안에선 JS 직행이라 그대로 깨져 우회 불가
HTML 엔티티
특수문자를 &이름;으로 적는 표기 → 필터는 (를 (로 못 보고 통과 → 브라우저는 (로 되돌림
| 종류 | 예 | 결과 |
|---|---|---|
| 네임드 | < > " | < > " |
| 네임드(괄호) | ( ) | ( ) |
| 네임드(공백) | 	 
 | 탭, 줄바꿈 |
| 숫자 | ( | ( |
| 16진 | ( | ( |
네임드는 &+이름, 숫자/16진은 &#으로 시작 → 필터가 &#만 막으면 네임드로 우회
URL 컨텍스트 특성
스킴 실행: javascript:코드는 주소 이동 대신 코드 실행 → data:text/html,...는 그 내용을 페이지로 띄움
공백 자동 삭제: 브라우저가 URL을 읽기 전에 그 안의 탭/줄바꿈을 전부 지움 → 막힌 단어 중간에 탭 → 필터는 단어가 끊겨서 식별 불가 → 브라우저는 탭을 지워 원래 단어로 복원
javascript: 뒤는 전체가 URL이라 → 공백 삭제가 코드 안 단어에도 똑같이 적용
| |
괄호 없이 호출
( )가 막힐 때 함수 실행:
- 백틱:
alert`1`함수 뒤 백틱이 괄호 대신 호출, 순수 JS - 엔티티:
alert(1): 파서가()로 복원(속성만) - toString: 객체를 문자열화(
obj+'')하면obj.toString이 자동 호출
→ 괄호 없이 호출 → 필터는 글자만 막고 동작은 못 막음