How to Securing Web Socket

이 글에서는 실시간 양방향 통신을 가능하게 하는 WebSocket의 보안 취약점과 이를 방어하기 위한 핵심적인 방법론을 다룹니다. WebSocket은 HTTP와 달리 한번 연결이 수립되면 계속해서 데이터를 주고받을 수 있어 효율적이지만, 이 특성 때문에 새로운 보안 위협에 노출될 수 있습니다.

Core Principles of WebSocket Security

WebSocket 보안을 강화하기 위해선 다음의 핵심 원칙들을 반드시 고려해야 합니다. 각 항목은 잠재적인 공격 벡터를 차단하는 데 중요한 역할을 합니다.

원칙설명
Encrypt Communication (WSS)항상 wss:// 프로토콜을 사용하여 통신을 암호화해야 합니다. ws://는 평문으로 데이터를 전송하여 스니핑 공격에 취약하지만, wss://는 TLS(Transport Layer Security)를 통해 데이터를 암호화하여 중간자(MITM) 공격으로부터 통신을 보호합니다.
Validate Origin HeadersCross-Site WebSocket Hijacking (CSWSH) 공격을 방어하기 위해 서버는 WebSocket 핸드셰이크 요청 시 Origin 헤더를 반드시 검증해야 합니다. 허용된 도메인 목록(Allowlist)을 만들어, 해당 목록에 없는 출처로부터의 연결 요청은 거부해야 합니다.
Implement Robust AuthenticationWebSocket 프로토콜 자체에는 인증 메커니즘이 없습니다. 따라서 연결 초기 단계인 HTTP 핸드셰이크 과정에서 사용자를 인증해야 합니다. 쿠키, 세션, JWT(JSON Web Token) 등 검증된 방식을 활용할 수 있습니다.
Sanitize and Validate All Inputs클라이언트로부터 수신하는 모든 데이터는 신뢰할 수 없는 것으로 간주해야 합니다. XSS(Cross-Site Scripting), SQL Injection 등과 같은 Injection 공격을 막기 위해 서버 측에서 모든 메시지에 대해 강력한 유효성 검사 및 데이터 새니타이제이션(Sanitization)을 수행해야 합니다.

Common Vulnerabilities and Mitigations

WebSocket 환경에서 자주 발견되는 주요 취약점과 그에 대한 대응 방안은 다음과 같습니다.

Cross-Site WebSocket Hijacking (CSWSH)

CSWSH는 공격자가 사용자의 브라우저를 이용해 비인가 WebSocket 연결을 수립하도록 유도하는 공격입니다. 사용자가 인증된 세션(예: 쿠키)을 가지고 있을 경우, 공격자의 악성 페이지는 사용자의 권한으로 서버와 통신할 수 있게 됩니다.

  • Mitigation: 앞서 언급했듯, 서버는 핸드셰이크 요청의 Origin 헤더를 엄격하게 검증하여 허용된 출처에서 온 요청만 수락해야 합니다.
// Example: Origin header validation in Node.js (ws library)
const allowedOrigins = ['https://hahwul.com', 'https://sub.hahwul.com'];

wsServer.on('request', (request) => {
  const origin = request.origin;
  if (!allowedOrigins.includes(origin)) {
    // Reject the connection
    request.reject();
    console.log(`Connection from origin ${origin} rejected.`);
    return;
  }
  // ... accept connection
});

Data Injection Attacks (XSS, SQLi)

WebSocket을 통해 전달되는 데이터가 적절히 처리되지 않으면 서버나 다른 클라이언트에게 악의적인 영향을 미칠 수 있습니다. 예를 들어, 채팅 애플리케이션에서 한 사용자가 보낸 스크립트가 다른 사용자들의 브라우저에서 실행되면 XSS 공격이 성립됩니다.

  • Mitigation: 서버는 클라이언트로부터 받은 모든 메시지의 내용을 검증하고, 데이터베이스 쿼리나 HTML 출력에 사용하기 전에 반드시 이스케이프(Escape) 또는 새니타이즈(Sanitize) 해야 합니다. 신뢰할 수 없는 데이터를 다룰 때는 항상 주의를 기울여야 합니다.

Denial-of-Service (DoS)

공격자는 대량의 연결을 생성하거나, 처리 비용이 높은 메시지를 대량으로 전송하여 서버의 리소스를 고갈시킬 수 있습니다. 이는 정상적인 사용자의 서비스 이용을 방해합니다.

  • Mitigation:
    • Rate Limiting: 특정 IP 주소나 사용자 계정당 연결 수 및 메시지 전송률을 제한합니다.
    • Message Size Limits: 비정상적으로 큰 크기의 메시지를 차단하여 버퍼 오버플로우나 리소스 낭비를 방지합니다.

Authentication and Authorization

WebSocket은 상태를 유지하는(Stateful) 프로토콜이지만, 인증은 상태가 없는(Stateless) HTTP 핸드셰이크를 통해 이루어져야 합니다.

 sequenceDiagram
    participant Client
    participant Server

    Client->>Server: HTTP GET /chat (Upgrade: websocket)
    Note over Server: 1. Verify Origin Header
    Note over Server: 2. Authenticate User (e.g., via Cookie/JWT)
    alt Authentication/Origin Check Fails
        Server-->>Client: HTTP 401/403 Unauthorized
    else Authentication & Origin OK
        Server-->>Client: HTTP 101 Switching Protocols
        Note over Client,Server: WebSocket Connection Established
        Client-->>Server: WebSocket Message (JSON)
        Note over Server: 3. Validate & Sanitize Message
        Server-->>Client: WebSocket Message (Broadcast)
    end

일반적으로 사용되는 인증 방식은 다음과 같습니다.

  1. Cookie-based Authentication: 웹 애플리케이션과 동일한 도메인에서 WebSocket을 운영하는 경우, 브라우저는 자동으로 인증 쿠키를 전송합니다. 서버는 이 쿠키를 검증하여 사용자를 식별할 수 있습니다.
  2. Token-based Authentication: 클라이언트는 로그인 시 발급받은 Token(예: JWT)을 쿼리 파라미터나 첫 번째 WebSocket 메시지를 통해 서버로 전송합니다. 서버는 이 Token의 유효성을 검증한 후 연결을 허용합니다.
// Token via query parameter
const socket = new WebSocket('wss://api.hahwul.com/ws?token=YOUR_JWT_TOKEN');

// Token as the first message
socket.onopen = () => {
  socket.send(JSON.stringify({ type: 'auth', token: 'YOUR_JWT_TOKEN' }));
};

Token을 쿼리 파라미터로 전송하는 방식은 구현이 간단하지만, 서버 로그에 Token이 남을 수 있다는 단점이 있습니다. 반면, 연결 후 첫 메시지로 전송하는 방식은 더 안전하지만, 인증 전까지 다른 메시지를 처리하지 않도록 로직을 구현해야 합니다.

Conclusion

WebSocket은 현대적인 웹 애플리케이션의 필수 요소로 자리 잡았지만, 그 편리함 이면에는 반드시 고려해야 할 보안 문제들이 존재합니다. 이 글에서 다룬 wss:// 사용 의무화, Origin 헤더 검증, 입력 데이터 처리, 그리고 견고한 인증 메커니즘 적용은 안전한 WebSocket 환경을 구축하기 위한 최소한의 요구사항입니다. Red team 관점에서 볼 때, 이러한 기본 방어책이 없는 시스템은 쉽게 공격의 표적이 될 수 있습니다. 따라서 개발 초기 단계부터 보안을 염두에 두고 설계하는 것이 매우 중요합니다.