SSE(Server Sent Event)

๐Ÿ” Introduction

SSE(Server-Sent Event)๋Š” Server Push ๊ธฐ์ˆ ๋กœ ์›น ์†Œ์บฃ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์„œ๋ฒ„์™€ Javascript๊ฐ€ ์„œ๋กœ ํ†ต์‹ ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์›น์†Œ์ผ“์˜ ๊ฒฝ์šฐ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, SSE๋Š” ์„œ๋ฒ„โ†’ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ›๋Š” ์š”์ฒญ๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ˆœํžˆ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ Push๋ฅผ ๋ฐ›์•„์•ผํ•˜๋Š” ๊ฒฝ์šฐ SSE๊ฐ€ ๊ฐ€์žฅ ๊ฐ„ํŽธํ•œ ๊ธฐ์ˆ ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์„ฑ๋Šฅ์ด๋‚˜ ๊ธฐ์ˆ ์ ์ธ ๋ถ€๋ถ„์—์„œ ์ด์ ๋ณด๋‹จ ๋‹จ์ ์ด ๋งŽ์•„์„œ ๋Œ€๋ถ€๋ถ„์˜ ์„œ๋น„์Šค์—์„  WebSocket ๋˜๋Š” Ajax ๋ฐฉ์‹์„ ๋งŽ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Event Straem Format

Basic and content-type

SSE๋Š” text/event-stream ํƒ€์ž…๊ณผ plain text response๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ line ๋ณ„๋กœ data: ๋ผ๋Š” ๊ตฌ๋ถ„์ž๋ฅผ ํ†ตํ•ด์„œ stream์„ ์ „์†กํ•˜๊ณ  ๊ตฌ๋ณ„ํ•ฉ๋‹ˆ๋‹ค.

1
data: message\n\n

Multiline

1
2
data: first line\n
data: second line\n\n

JSON

1
2
3
4
data: {\n
data: "user": "hahwul",\n
data: "id": 12345\n
data: }\n\n

์ด ๋•Œ SSE๋ฅผ ๋ฐ›๋Š” Client์—์„  ์•„๋ž˜์™€ ๊ฐ™์ด eventListener๋กœ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ JSON.parse ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

1
2
3
4
source.addEventListener('message', function(e) {
  var data = JSON.parse(e.data);
  console.log(data.id, data.msg);
}, false);

Event name

event: ๊ตฌ๋ถ„์ž๋ฅผ ํ†ตํ•ด eventListner๋กœ ์ „๋‹ฌํ•  ์ด๋ฒคํŠธ ์ด๋ฆ„์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1
2
3
4
event: userlogon\n
data: {"username": "hahwul"}\n\n
event: update\n
data: {"username": "hahwul", "tool": "zap"}\n\n
1
2
3
4
5
6
7
8
9
source.addEventListener('userlogon', function(e) {
  var data = JSON.parse(e.data);
  console.log('login:'+data.username);
}, false);

source.addEventListener('update', function(e) {
  var data = JSON.parse(e.data);
  console.log('update:' + data);
}, false);

๐Ÿ—ก Offensive techniques

How to Testing

SSE๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €์˜ Console, Debugger๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ SSE๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ๋งŒ ํ•˜๊ธฐ ๋–„๋ฌธ์— EventHandler ์ดํ›„ ๋กœ์ง์ด ์ฃผ์š” ๋ถ„์„ ๊ตฌ๊ฐ„์ด ๋ฉ๋‹ˆ๋‹ค.

ZAP์—์„œ๋Š” Server-Sent Events Addon ํ†ตํ•ด์„œ Recv๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํŠธ๋ž˜ํ‚นํ•˜๊ณ  ๋ถ„์„ํ•˜๋Š”๋ฐ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SSE History in ZAP

Testing Method

SSE๋Š” ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ๋งŒ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ณต๊ฒฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฒ”์œ„๊ฐ€ ๋„“์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ SSE์— ์—ฐ๊ฒฐ๋˜๋Š” ์„œ๋ฒ„๊ฐ€ ํ•ญ์ƒ ์‹ ๋ขฐ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋‹ค๋Š” ๋ณด์žฅ์€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ DOM์— ๋ฐ˜์˜๋˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋กœ์ง์— ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์„ ์ด์šฉํ•œ ๊ณต๊ฒฉ๋“ค์ด ์ฃผ๋ฅผ ์ด๋ฃจ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

DOM XSS

SSE๋ฅผ ํ†ตํ•ด ๋ฐ›์€ ๋ฐ์ดํ„ฐ์—์„œ DOM XSS๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ DOM XSS์™€ ๋™์ผํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์œ„์น˜๊ฐ€ SSE๋ผ๋Š” ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

1
2
3
4
5
evtSource.onmessage = function(e) {
  var logObject = document.getElementById("logs");
  logObject.innerHTML = "message: " + e.data;
  eventList.appendChild(newElement);
}

์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๋Š” ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๊ฐ€ logObject์˜ innerHTML๋กœ ๋“ค์–ด๊ฐ€๊ธฐ ๋–„๋ฌธ์— DOM ๊ธฐ๋ฐ˜์˜ XXS๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Weak Origin Check

SSE๋Š” WebSocket๊ณผ ๋‹ค๋ฅด๊ฒŒ Client ๋‹จ์—์„œ ์ง์ ‘ Origin ๊ฒ€์ฆ์„ ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๋‹น์—ฐํžˆ Browser๋กœ ๋ถ€ํ„ฐ ์ œ๊ณต๋˜๋Š” SOP์˜ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด์•ผํ•˜๋ฉฐ, ์ฝ”๋“œ์— ๋”ฐ๋ผ์„œ Origin ๊ฒ€์ฆ์„ ์šฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Etc

์ด์™ธ์—๋„ Browser๋‹จ ๋ณด์•ˆ ์ทจ์•ฝ์ ์— ๋™์ผํ•˜๊ฒŒ ์˜ํ–ฅ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์†Œ์Šค๊ฐ€ ์„œ๋ฒ„๋ผ๋Š” ์ฐจ์ด์ ๋งŒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ›ก Defensive techniques

์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฌด์กฐ๊ฑด ์‹ ๋ขฐํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ SSE๋กœ ์—ฐ๊ฒฐ๋˜๋Š” ์„œ๋ฒ„๊ฐ€ ์‹ ๋ขฐ ๊ตฌ๊ฐ„์— ์žˆ๋Š” ์„œ๋ฒ„๋ผ๊ณ  ํ•˜๋”๋ผ๋„, ๊ฐ€๊ธ‰์ ์ด๋ฉด FE๋‹จ์—์„œ SSE๋กœ ์ธํ•œ ์ด์Šˆ๋Š” ์ œํ•œ๋  ์ˆ˜ ์žˆ๋„๋ก ๋ณ„๋„์˜ ๋ฐฉ์–ด๋กœ์ง์ด ์žˆ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๐Ÿ•น Tools

๐Ÿ“Œ References

Licensed under CC BY-NC-SA 4.0