-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add native websocket support #2350
base: dev
Are you sure you want to change the base?
Changes from all commits
e0fd0e6
1118bcd
24c5d5d
5536d93
2bde202
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.. testsetup:: * | ||
|
||
from pwn import * | ||
from websocket import WebSocket, ABNF, WebSocketException, WebSocketTimeoutException | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't necessary since it's not used in the doc tests themselves |
||
|
||
:mod:`pwnlib.tubes.wstube` --- Wstube | ||
=========================================================== | ||
|
||
.. automodule:: pwnlib.tubes.wstube | ||
|
||
.. autoclass:: pwnlib.tubes.wstube.wstube | ||
:members: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
|
||
# * Portions of this file are derived from pwntools-tube-websocket by frankli0324 | ||
# * under the MIT License. | ||
# * | ||
# * Copyright (c) frankli0324. | ||
# * https://gist.github.com/frankli0324/795162a14be988a01e0efa0531f7ac5a | ||
# * | ||
# * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# * of this software and associated documentation files (the "Software"), to deal | ||
# * in the Software without restriction, including without limitation the rights | ||
# * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# * copies of the Software, and to permit persons to whom the Software is | ||
# * furnished to do so, subject to the following conditions: | ||
# * | ||
# * The above copyright notice and this permission notice shall be included in all | ||
# * copies or substantial portions of the Software. | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
from websocket import WebSocket, ABNF, WebSocketException, WebSocketTimeoutException | ||
|
||
from pwnlib.tubes.tube import tube | ||
|
||
|
||
class wstube(tube): | ||
""" | ||
A basic websocket interface that wrapped as a tube. | ||
|
||
Arguments: | ||
url (str): The websocket server's URL to connect to. | ||
headers (dict): The same headers as the websocket protocol. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't those additional headers to pass around? |
||
|
||
Examples: | ||
|
||
>>> ws = wstube('wss://echo.websocket.events') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we host a basic echo server ourselves in ci instead of relying on an external service? Those tend to be flakey and disturb the testing flow from time to time. |
||
>>> ws.recv() | ||
b'echo.websocket.events sponsored by Lob.com' | ||
>>> for i in range(3): | ||
... ws.send(b'test') | ||
... ws.recv(2) | ||
... ws.recv(2) | ||
b'te' | ||
b'st' | ||
b'te' | ||
b'st' | ||
b'te' | ||
b'st' | ||
>>> ws.sendline(b'test') | ||
>>> ws.recv() | ||
b'test\\n' | ||
>>> ws.send(b'12345asdfg') | ||
>>> ws.recvregex(b'[0-9]{5}') | ||
b'12345' | ||
>>> ws.recv() | ||
b'asdfg' | ||
>>> ws.close() | ||
""" | ||
def __init__(self, url, headers=None, *args, **kwargs): | ||
if headers is None: | ||
headers = {} | ||
super(wstube, self).__init__(*args, **kwargs) | ||
self.closed = False | ||
self.sock = WebSocket() | ||
self.url = url | ||
self.sock.connect(url, header=headers) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is worth it to forward other useful arguments to the constructor like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handling the proxy configured in |
||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a magic Some |
||
def recv_raw(self, numb): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if self.closed: | ||
raise EOFError | ||
|
||
while True: | ||
try: | ||
data = self.sock.recv() | ||
if isinstance(data, str): | ||
data = data.encode() | ||
break | ||
except WebSocketTimeoutException: | ||
return None | ||
except WebSocketException: | ||
self.shutdown("recv") | ||
raise EOFError | ||
|
||
if not data: | ||
self.shutdown() | ||
raise EOFError('Recv Error') | ||
|
||
return data | ||
|
||
def send_raw(self, data): | ||
if self.closed: | ||
raise EOFError | ||
|
||
try: | ||
self.sock.send_binary(data) | ||
except WebSocketException as e: | ||
self.shutdown() | ||
raise EOFError('Send Error') | ||
|
||
def settimeout_raw(self, timeout): | ||
if getattr(self, 'sock', None): | ||
self.sock.settimeout(timeout) | ||
|
||
def connected_raw(self, direction): | ||
try: | ||
self.sock.ping() | ||
opcode, data = self.sock.recv_data(True) | ||
return opcode == ABNF.OPCODE_PONG | ||
except: | ||
return False | ||
|
||
def close(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be possible to specify the web socket close reason |
||
if not getattr(self, 'sock', None): | ||
return | ||
|
||
self.closed = True | ||
|
||
self.sock.close() | ||
self.sock = None | ||
self._close_msg() | ||
|
||
def _close_msg(self): | ||
self.info('Closed connection to %s', self.url) | ||
|
||
def shutdown_raw(self, direction): | ||
if self.closed: | ||
return | ||
|
||
self.closed = True | ||
self.sock.shutdown() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add new entries to the bottom of the list