使用wsrepl简化WebSocket渗透测试

本文介绍了Doyensec开发的wsrepl工具,这是一个专为WebSocket渗透测试设计的交互式REPL工具,支持自动化插件开发,提供完整的协议透明度,解决了传统工具在实时通信测试中的局限性。

使用wsrepl简化WebSocket渗透测试

18 Jul 2023 - 作者:Andrew Konstantinov

在这个即时满足的时代,WebSocket已成为Web应用程序的心跳。它们是数据流和双向通信中默默无闻的英雄,实时提供一切,因为显然,等待已经过时。

然而,当 tasked with pentesting these WebSockets,感觉就像在钢丝上骑独轮车 juggling flaming torches!现有工具虽然在其特定领域很熟练,但就像不匹配的拼图碎片——它们不太合适,让你去 bridging the gaps。因此,你发现自己从一个工具切换到另一个,试图同时管理它们,并希望有一个更 streamlined approach。

这就是 https://github.com/doyensec/wsrepl 来救援的地方。这个工具是Doyensec安全工具的最新 addition,旨在简化基于WebSocket的应用程序的审计。wsrepl通过提供一个用户友好的交互式REPL界面,同时方便地易于自动化, strike a much needed balance。有了wsrepl,我们 aim to turn the tide in websocket pentesting,提供一个既高效又直观的工具。

Doyensec挑战

曾经,我们接手了一个客户的 engagement,其Web应用程序 heavily relied on WebSockets for soft real-time communication。这不是一个 easy feat。客户有一个 robust bug bounty policy,并且之前进行过多次 pentests。因此,我们 fully aware that the lowest hanging fruits were probably plucked。尽管如此,作为真正的Doyensec warriors(‘doyen’ - Merriam-Webster描述为 ‘a person considered to be knowledgeable or uniquely skilled as a result of long experience in some field of endeavor’),我们 prepared to dig deeper for potential vulnerabilities。

我们的主要 obstacle 是应用程序的 custom protocol for data streaming。Pentesters中的 conventional wisdom 表明,最具挑战性的目标往往是最被忽视的。有趣,不是吗?

寻找完美工具

Pentesting WebSockets的 immediate go-to tool 通常是Burp Suite。虽然它是web pentesting中的 heavyweight,我们发现它的WebSockets实现 mirrored HTTP requests a little bit too closely,这在 near-realtime communications中 didn’t sit well。

当然,它确实提供了一个 neat way to get an interactive WS session,但有点 tedious - navigating through ‘Upgrade: websocket’, hopping between ‘Repeater’, ‘Websocket’, ‘New WebSocket’, filling in details, and altering HTTP/2 to HTTP/1.1。结果是一个 decent REPL,但过程?Not so much。

不要误会,Burp确实有它的 advantages。Pentesters already have it open most times, it highlights JSON or XML, and integrates well with existing extensions。尽管如此,当你必须 automate custom authentication schemes, protocols, connection tracking, or data serialization schemes时,它 falls short。

其他工具,如 websocketking.com 和 hoppscotch.io/realtime/websocket,提供易于使用且美观的图形客户端在浏览器中。然而,它们 lack comprehensive options for automation。像 websocket-harness 和 WSSiP 这样的工具 bridge the gap between HTTP and WebSockets,这可能有用,但 again, they don’t offer an interactive REPL for manual inspection of traffic。

最后,我们 landed on websocat,一个 netcat inspired command line WebSockets client,最接近我们心目中的工具。虽然它确实提供了一系列 features,但它 predominantly geared towards debugging WebSocket servers, not pentesting。

wsrepl: WebSockets Pentesting英雄

Enter wsrepl, born out of necessity as our answer to the challenges we faced。它不仅仅是另一个 pentesting tool,而是一个 agile solution that sits comfortably in the middle - offering an interactive REPL experience while also providing simple and convenient path to automation。

使用Python的 fantastic TUI framework Textual 构建,它 enables an accessible experience that is easy to navigate both by mouse and keyboard。那只是 scratching the surface though。它与 curl’s arguments 的 interoperability enables a fluid transition from the Upgrade request in Burp to wsrepl。所有需要的是通过 ‘Copy as curl command’ menu option 复制一个请求,并将 curl 替换为 wsrepl。

在表面上,wsrepl就像任何其他工具,显示传入和传出的流量,并添加了发送新消息的选项。然而,真正的魔法在于细节。它 leaves nothing to guesswork。每个十六进制 opcode 都按照 RFC 6455 显示,这个功能可能 potentially save you from many unnecessary debugging hours。

一个学到的教训

这里有一个 anecdote 来说明这一点。在我们与WebSockets的 engagement 开始时,我并不 thoroughly familiar with the WebSocket RFC,并基于Burp显示的内容 built my understanding。然而,Burp只显示文本消息, obscuring message opcodes and autonomously handling pings without revealing them in the UI。这种 partial visibility led to some misconceptions about how WebSockets operate。我们测试的服务的开发者 seemingly had the same misunderstanding,因为他们 implemented ping traffic using 0x1 - text type messages。这 caused confusion and wasted time when my scripts kept losing the connection,即使流量 appeared to align with my Burp observations。

为了避免类似的 pitfalls,wsrepl designed to give you the whole picture, without any hidden corners。以下是RFC6544中定义的WebSocket opcodes的 quick rundown,你可以在wsrepl中看到:

Opcode Description
0x0 Continuation Frame
0x1 Text Frame
0x2 Binary Frame
0x8 Connection Close
0x9 Ping
0xA Pong (must carry the same payload as the corresponding Ping frame)

与主要使用0x1类型消息的大多数WebSocket协议相反,wsrepl accompanies all messages with their opcodes,确保 full transparency。我们 intentionally made the decision not to conceal ping traffic by default,尽管你可以选择使用 –hide-ping-pong 选项隐藏它们。

此外,wsrepl引入了发送 ‘fake’ ping messages 的独特能力,使用0x1消息帧。Payloads 可以使用选项 –ping-0x1-payload 和 –pong-0x1-payload 定义,间隔由 –ping-0x1-interval 控制。它还支持客户端诱导的ping消息(协议级别,0x9),即使通常这是由服务器完成的:–ping-interval。

还值得注意的是,wsrepl incorporates an automatic reconnection feature in case of disconnects。 Coupled with granular ping control,这些功能 empower you to initiate long-lasting and stable WebSocket connections,这已被证明对执行某些攻击有用。

使用wsrepl简化自动化

此外,wsrepl crafted with a primary goal in mind: enabling you to quickly transition into WebSocket automation。要做到这一点,你只需要编写一个Python插件,这非常 straightforward,并且与Burp不同,感觉 quintessentially pythonic。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from wsrepl import Plugin

MESSAGES = [
    "hello",
    "world"
]

class Demo(Plugin):
    """Demo plugin that sends a static list of messages to the server."""
    def init(self):
        self.messages = MESSAGES

它是Python,所以真的, the sky’s the limit。例如,这里是如何发送HTTP请求获取 auth token,然后使用它来与WebSocket服务器进行身份验证:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from wsrepl import Plugin
from wsrepl.WSMessage import WSMessage

import json
import requests

class Demo(Plugin):
    """Demo plugin that dynamically acquires authentication token."""
    def init(self):
        # Here we simulate an API request to get a session token by supplying a username and password.
        # For the demo, we're using a dummy endpoint "https://hb.cran.dev/uuid" that returns a UUID.
        # In a real-life scenario, replace this with your own authentication endpoint and provide necessary credentials.
        token = requests.get("https://hb.cran.dev/uuid").json()["uuid"]

        # The acquired session token is then used to populate self.messages with an authentication message.
        # The exact format of this message will depend on your WebSocket server requirements.
        self.messages = [
            json.dumps({
                "auth": "session",
                "sessionId": token
            })
        ]

插件系统 designed to be as flexible as possible。你可以定义 hooks that are executed at various stages of the WebSocket lifecycle。例如,你可以使用 on_message_sent 来修改消息 before they are sent to the server,或 on_message_received 来 parse and extract meaningful data from the server’s responses。完整的钩子列表如下:

自定义REPL UI

wsrepl的真正 triumph 在于其 capacity to automate even the most complicated protocols。很容易添加 custom serialization routines, allowing you to focus on the stuff that matters。

假设你正在处理一个使用JSON进行数据序列化的协议,并且你只对数据结构中的单个字段感兴趣。wsrepl允许你隐藏所有样板, yet preserve the option to retrieve the raw data when necessary。

 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
38
from wsrepl import Plugin

import json
from wsrepl.WSMessage import WSMessage

class Demo(Plugin):
    async def on_message_sent(self, message: WSMessage) -> None:
        # Grab the original message entered by the user
        original = message.msg

        # Prepare a more complex message structure that our server requires.
        message.msg = json.dumps({
            "type": "message",
            "data": {
                "text": original
            }
        })

        # Short and long versions of the message are used for display purposes in REPL UI.
        # By default they are the same as 'message.msg', but here we modify them for better UX.
        message.short = original
        message.long = message.msg


    async def on_message_received(self, message: WSMessage) -> None:
        # Get the original message received from the server
        original = message.msg

        try:
            # Try to parse the received message and extract meaningful data.
            # The exact structure here will depend on your websocket server's responses.
            message.short = json.loads(original)["data"]["text"]
        except:
            # In case of a parsing error, let's inform the user about it in the history view.
            message.short = "Error: could not parse message"

        # Show the original message when the user focuses on it in the UI.
        message.long = original

总之,wsrepl designed to make your WebSocket pentesting life easier。它是交互式REPL体验与自动化便利性的完美 blend。它可能不是解决你在pentesting WebSockets中遇到的每一个挑战的 magical solution,但它是你 arsenal 中的一个 powerful tool。试一试,让我们知道你的 experiences!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计