CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/proxy/socks5/server_client.rb
Views: 11766
1
# -*- coding: binary -*-
2
3
require 'bindata'
4
require 'rex/socket'
5
require 'rex/proto/proxy/socks5/packet'
6
7
module Rex
8
module Proto
9
module Proxy
10
11
#
12
# A client connected to the proxy server.
13
#
14
module Socks5
15
#
16
# A mixin for a socket to perform a relay to another socket.
17
#
18
module TcpRelay
19
#
20
# TcpRelay data coming in from relay_sock to this socket.
21
#
22
def relay(relay_client, relay_sock)
23
@relay_client = relay_client
24
@relay_sock = relay_sock
25
# start the relay thread (modified from Rex::IO::StreamAbstraction)
26
@relay_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyServerTcpRelay", false) do
27
loop do
28
closed = false
29
buf = nil
30
31
begin
32
s = Rex::ThreadSafe.select([@relay_sock], nil, nil, 0.2)
33
next if s.nil? || s[0].nil?
34
rescue
35
closed = true
36
end
37
38
unless closed
39
begin
40
buf = @relay_sock.sysread( 32768 )
41
closed = buf.nil?
42
rescue
43
closed = true
44
end
45
end
46
47
unless closed
48
total_sent = 0
49
total_length = buf.length
50
while total_sent < total_length
51
begin
52
data = buf[total_sent, buf.length]
53
sent = self.write(data)
54
total_sent += sent if sent > 0
55
rescue
56
closed = true
57
break
58
end
59
end
60
end
61
62
if closed
63
@relay_client.stop
64
::Thread.exit
65
end
66
end
67
end
68
end
69
end
70
71
#
72
# A client connected to the SOCKS5 server.
73
#
74
class ServerClient
75
AUTH_NONE = 0
76
AUTH_GSSAPI = 1
77
AUTH_CREDS = 2
78
AUTH_NO_ACCEPTABLE_METHODS = 255
79
80
AUTH_PROTOCOL_VERSION = 1
81
AUTH_RESULT_SUCCESS = 0
82
AUTH_RESULT_FAILURE = 1
83
84
COMMAND_CONNECT = 1
85
COMMAND_BIND = 2
86
COMMAND_UDP_ASSOCIATE = 3
87
88
REPLY_SUCCEEDED = 0
89
REPLY_GENERAL_FAILURE = 1
90
REPLY_NOT_ALLOWED = 2
91
REPLY_NET_UNREACHABLE = 3
92
REPLY_HOST_UNREACHABLE = 4
93
REPLY_CONNECTION_REFUSED = 5
94
REPLY_TTL_EXPIRED = 6
95
REPLY_CMD_NOT_SUPPORTED = 7
96
REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 8
97
98
HOST = 1
99
PORT = 2
100
101
#
102
# Create a new client connected to the server.
103
#
104
def initialize(server, sock, opts={})
105
@server = server
106
@lsock = sock
107
@opts = opts
108
@rsock = nil
109
@client_thread = nil
110
@mutex = ::Mutex.new
111
end
112
113
# Start handling the client connection.
114
#
115
def start
116
# create a thread to handle this client request so as to not block the socks5 server
117
@client_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyClient", false) do
118
begin
119
@server.add_client(self)
120
# get the initial client request packet
121
handle_authentication
122
123
# handle the request
124
handle_command
125
rescue => exception
126
# respond with a general failure to the client
127
response = ResponsePacket.new
128
response.command = REPLY_GENERAL_FAILURE
129
@lsock.put(response.to_binary_s)
130
131
wlog("Client.start - #{$!}")
132
self.stop
133
end
134
end
135
end
136
137
def handle_authentication
138
request = AuthRequestPacket.read(@lsock.get_once)
139
if @opts['ServerUsername'].nil? && @opts['ServerPassword'].nil?
140
handle_authentication_none(request)
141
else
142
handle_authentication_creds(request)
143
end
144
end
145
146
def handle_authentication_creds(request)
147
unless request.supported_methods.include? AUTH_CREDS
148
raise "Invalid SOCKS5 request packet received (no supported authentication methods)."
149
end
150
response = AuthResponsePacket.new
151
response.chosen_method = AUTH_CREDS
152
@lsock.put(response.to_binary_s)
153
154
version = @lsock.read(1)
155
raise "Invalid SOCKS5 authentication packet received." unless version.unpack('C').first == 0x01
156
157
username_length = @lsock.read(1).unpack('C').first
158
username = @lsock.read(username_length)
159
160
password_length = @lsock.read(1).unpack('C').first
161
password = @lsock.read(password_length)
162
163
# +-----+--------+
164
# | VER | STATUS |
165
# +-----+--------+ VERSION: 0x01
166
# | 1 | 1 | STATUS: 0x00=SUCCESS, otherwise FAILURE
167
# +-----+--------+
168
if username == @opts['ServerUsername'] && password == @opts['ServerPassword']
169
raw = [ AUTH_PROTOCOL_VERSION, AUTH_RESULT_SUCCESS ].pack ('CC')
170
ilog("SOCKS5: Successfully authenticated")
171
@lsock.put(raw)
172
else
173
raw = [ AUTH_PROTOCOL_VERSION, AUTH_RESULT_FAILURE ].pack ('CC')
174
@lsock.put(raw)
175
raise "Invalid SOCKS5 credentials provided"
176
end
177
end
178
179
def handle_authentication_none(request)
180
unless request.supported_methods.include? AUTH_NONE
181
raise "Invalid SOCKS5 request packet received (no supported authentication methods)."
182
end
183
response = AuthResponsePacket.new
184
response.chosen_method = AUTH_NONE
185
@lsock.put(response.to_binary_s)
186
end
187
188
def handle_command
189
request = RequestPacket.read(@lsock.get_once)
190
response = nil
191
case request.command
192
when COMMAND_BIND
193
response = handle_command_bind(request)
194
when COMMAND_CONNECT
195
response = handle_command_connect(request)
196
when COMMAND_UDP_ASSOCIATE
197
response = handle_command_udp_associate(request)
198
end
199
@lsock.put(response.to_binary_s) unless response.nil?
200
end
201
202
def handle_command_bind(request)
203
# create a server socket for this request
204
params = {
205
'LocalHost' => request.address_type == Address::ADDRESS_TYPE_IPV6 ? '::' : '0.0.0.0',
206
'LocalPort' => 0,
207
}
208
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
209
bsock = Rex::Socket::TcpServer.create(params)
210
211
# send back the bind success to the client
212
response = ResponsePacket.new
213
response.command = REPLY_SUCCEEDED
214
response.address = bsock.getlocalname[HOST]
215
response.port = bsock.getlocalname[PORT]
216
@lsock.put(response.to_binary_s)
217
218
# accept a client connection (2 minute timeout as per the socks4a spec)
219
begin
220
::Timeout.timeout(120) do
221
@rsock = bsock.accept
222
end
223
rescue ::Timeout::Error
224
raise "Timeout reached on accept request."
225
end
226
227
# close the listening socket
228
bsock.close
229
230
setup_tcp_relay
231
response = ResponsePacket.new
232
response.command = REPLY_SUCCEEDED
233
response.address = @rsock.peerhost
234
response.port = @rsock.peerport
235
response
236
end
237
238
def handle_command_connect(request)
239
# perform the connection request
240
params = {
241
'PeerHost' => request.address,
242
'PeerPort' => request.port,
243
}
244
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
245
@rsock = Rex::Socket::Tcp.create(params)
246
247
setup_tcp_relay
248
response = ResponsePacket.new
249
response.command = REPLY_SUCCEEDED
250
response.address = @rsock.getlocalname[HOST].split('-')[-1]
251
response.port = @rsock.getlocalname[PORT]
252
response
253
end
254
255
def handle_command_udp_associate(request)
256
response = ResponsePacket.new
257
response.command = REPLY_CMD_NOT_SUPPORTED
258
response
259
end
260
261
#
262
# Setup the TcpRelay between lsock and rsock.
263
#
264
def setup_tcp_relay
265
# setup the two way relay for full duplex io
266
@lsock.extend(TcpRelay)
267
@rsock.extend(TcpRelay)
268
# start the socket relays...
269
@lsock.relay(self, @rsock)
270
@rsock.relay(self, @lsock)
271
end
272
273
#
274
# Stop handling the client connection.
275
#
276
def stop
277
@mutex.synchronize do
278
unless @closed
279
begin
280
@lsock.close if @lsock
281
rescue
282
end
283
284
begin
285
@rsock.close if @rsock
286
rescue
287
end
288
289
@client_thread.kill if @client_thread and @client_thread.alive?
290
@server.remove_client(self)
291
@closed = true
292
end
293
end
294
end
295
end
296
end
297
end
298
end
299
end
300
301