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/socks4a.rb
Views: 11704
1
# -*- coding: binary -*-
2
#
3
# sf - Sept 2010
4
#
5
require 'thread'
6
require 'rex/socket'
7
8
module Rex
9
module Proto
10
module Proxy
11
12
#
13
# A Socks4a proxy server.
14
#
15
class Socks4a
16
17
#
18
# A client connected to the Socks4a server.
19
#
20
class Client
21
22
REQUEST_VERSION = 4
23
REPLY_VERSION = 0
24
25
COMMAND_CONNECT = 1
26
COMMAND_BIND = 2
27
28
REQUEST_GRANTED = 90
29
REQUEST_REJECT_FAILED = 91
30
REQUEST_REJECT_CONNECT = 92
31
REQUEST_REJECT_USERID = 93
32
33
HOST = 1
34
PORT = 2
35
36
#
37
# A Socks4a packet.
38
#
39
class Packet
40
41
def initialize
42
@version = REQUEST_VERSION
43
@command = 0
44
@dest_port = 0
45
@dest_ip = '0.0.0.0'
46
@userid = ''
47
end
48
49
#
50
# A helper function to recv in a Socks4a packet byte by byte.
51
#
52
# sf: we could just call raw = sock.get_once but some clients
53
# seem to need reading this byte by byte instead.
54
#
55
def Packet.recv( sock, timeout=30 )
56
raw = ''
57
# read in the 8 byte header
58
while( raw.length < 8 )
59
raw << sock.read( 1 )
60
end
61
# if its a request there will be more data
62
if( raw[0..0].unpack( 'C' ).first == REQUEST_VERSION )
63
# read in the userid
64
while( raw[8..raw.length].index( "\x00" ) == nil )
65
raw << sock.read( 1 )
66
end
67
# if a hostname is going to be present, read it in
68
ip = raw[4..7].unpack( 'N' ).first
69
if( ( ip & 0xFFFFFF00 ) == 0x00000000 and ( ip & 0x000000FF ) != 0x00 )
70
hostname = ''
71
while( hostname.index( "\x00" ) == nil )
72
hostname += sock.read( 1 )
73
end
74
raw << hostname
75
end
76
end
77
# create a packet from this raw data...
78
packet = Packet.new
79
packet.from_r( raw ) ? packet : nil
80
end
81
82
#
83
# Pack a packet into raw bytes for transmitting on the wire.
84
#
85
def to_r
86
raw = [ @version, @command, @dest_port, Rex::Socket.addr_atoi( @dest_ip ) ].pack( 'CCnN' )
87
return raw if( @userid.empty? )
88
return raw + [ @userid ].pack( 'Z*' )
89
end
90
91
#
92
# Unpack a raw packet into its components.
93
#
94
def from_r( raw )
95
return false if( raw.length < 8 )
96
@version = raw[0..0].unpack( 'C' ).first
97
return false if( @version != REQUEST_VERSION and @version != REPLY_VERSION )
98
@command = raw[1..1].unpack( 'C' ).first
99
@dest_port = raw[2..3].unpack( 'n' ).first
100
@dest_ip = Rex::Socket.addr_itoa( raw[4..7].unpack( 'N' ).first )
101
if( raw.length > 8 )
102
@userid = raw[8..raw.length].unpack( 'Z*' ).first
103
# if this is a socks4a request we can resolve the provided hostname
104
if( self.is_hostname? )
105
hostname = raw[(8+@userid.length+1)..raw.length].unpack( 'Z*' ).first
106
@dest_ip = self.resolve( hostname )
107
# fail if we couldnt resolve the hostname
108
return false if( not @dest_ip )
109
end
110
else
111
@userid = ''
112
end
113
return true
114
end
115
116
def is_connect?
117
@command == COMMAND_CONNECT ? true : false
118
end
119
120
def is_bind?
121
@command == COMMAND_BIND ? true : false
122
end
123
124
attr_accessor :version, :command, :dest_port, :dest_ip, :userid
125
126
protected
127
128
#
129
# Resolve the given hostname into a dotted IP address.
130
#
131
def resolve( hostname )
132
if( not hostname.empty? )
133
begin
134
return Rex::Socket.getaddress(hostname, false)
135
rescue ::SocketError
136
return nil
137
end
138
end
139
return nil
140
end
141
142
#
143
# As per the Socks4a spec, check to see if the provided dest_ip is 0.0.0.XX
144
# which indicates after the @userid field contains a hostname to resolve.
145
#
146
def is_hostname?
147
ip = Rex::Socket.addr_atoi( @dest_ip )
148
if( ip & 0xFFFFFF00 == 0x00000000 )
149
return true if( ip & 0x000000FF != 0x00 )
150
end
151
return false
152
end
153
154
end
155
156
#
157
# A mixin for a socket to perform a relay to another socket.
158
#
159
module Relay
160
161
#
162
# Relay data coming in from relay_sock to this socket.
163
#
164
def relay( relay_client, relay_sock )
165
@relay_client = relay_client
166
@relay_sock = relay_sock
167
# start the relay thread (modified from Rex::IO::StreamAbstraction)
168
@relay_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyServerRelay", false) do
169
loop do
170
closed = false
171
buf = nil
172
173
begin
174
s = Rex::ThreadSafe.select( [ @relay_sock ], nil, nil, 0.2 )
175
if( s == nil || s[0] == nil )
176
next
177
end
178
rescue
179
closed = true
180
end
181
182
if( closed == false )
183
begin
184
buf = @relay_sock.sysread( 32768 )
185
closed = true if( buf == nil )
186
rescue
187
closed = true
188
end
189
end
190
191
if( closed == false )
192
total_sent = 0
193
total_length = buf.length
194
while( total_sent < total_length )
195
begin
196
data = buf[total_sent, buf.length]
197
sent = self.write( data )
198
if( sent > 0 )
199
total_sent += sent
200
end
201
rescue
202
closed = true
203
break
204
end
205
end
206
end
207
208
if( closed )
209
@relay_client.stop
210
::Thread.exit
211
end
212
end
213
end
214
215
end
216
217
end
218
219
#
220
# Create a new client connected to the server.
221
#
222
def initialize( server, sock )
223
@server = server
224
@lsock = sock
225
@rsock = nil
226
@client_thread = nil
227
@mutex = ::Mutex.new
228
end
229
230
#
231
# Start handling the client connection.
232
#
233
def start
234
# create a thread to handle this client request so as to not block the socks4a server
235
@client_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyClient", false) do
236
begin
237
@server.add_client( self )
238
# get the initial client request packet
239
request = Packet.recv( @lsock )
240
raise "Invalid Socks4 request packet received." if not request
241
# handle the request
242
begin
243
# handle socks4a connect requests
244
if( request.is_connect? )
245
# perform the connection request
246
params = {
247
'PeerHost' => request.dest_ip,
248
'PeerPort' => request.dest_port,
249
}
250
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
251
252
@rsock = Rex::Socket::Tcp.create( params )
253
# and send back success to the client
254
response = Packet.new
255
response.version = REPLY_VERSION
256
response.command = REQUEST_GRANTED
257
@lsock.put( response.to_r )
258
# handle socks4a bind requests
259
elsif( request.is_bind? )
260
# create a server socket for this request
261
params = {
262
'LocalHost' => '0.0.0.0',
263
'LocalPort' => 0,
264
}
265
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
266
bsock = Rex::Socket::TcpServer.create( params )
267
# send back the bind success to the client
268
response = Packet.new
269
response.version = REPLY_VERSION
270
response.command = REQUEST_GRANTED
271
response.dest_ip = '0.0.0.0'
272
response.dest_port = bsock.getlocalname()[PORT]
273
@lsock.put( response.to_r )
274
# accept a client connection (2 minute timeout as per spec)
275
begin
276
::Timeout.timeout( 120 ) do
277
@rsock = bsock.accept
278
end
279
rescue ::Timeout::Error
280
raise "Timeout reached on accept request."
281
end
282
# close the listening socket
283
bsock.close
284
# verify the connection is from the dest_ip originally specified by the client
285
rpeer = @rsock.getpeername_as_array
286
raise "Got connection from an invalid peer." if( rpeer[HOST] != request.dest_ip )
287
# send back the client connect success to the client
288
#
289
# sf: according to the spec we send this response back to the client, however
290
# I have seen some clients who bawk if they get this second response.
291
#
292
response = Packet.new
293
response.version = REPLY_VERSION
294
response.command = REQUEST_GRANTED
295
response.dest_ip = rpeer[HOST]
296
response.dest_port = rpeer[PORT]
297
@lsock.put( response.to_r )
298
else
299
raise "Unknown request command received #{request.command} received."
300
end
301
rescue
302
# send back failure to the client
303
response = Packet.new
304
response.version = REPLY_VERSION
305
response.command = REQUEST_REJECT_FAILED
306
@lsock.put( response.to_r )
307
# raise an exception to close this client connection
308
raise "Failed to handle the clients request."
309
end
310
# setup the two way relay for full duplex io
311
@lsock.extend( Relay )
312
@rsock.extend( Relay )
313
# start the socket relays...
314
@lsock.relay( self, @rsock )
315
@rsock.relay( self, @lsock )
316
rescue
317
wlog( "Client.start - #{$!}" )
318
self.stop
319
end
320
end
321
end
322
323
#
324
# Stop handling the client connection.
325
#
326
def stop
327
@mutex.synchronize do
328
if( not @closed )
329
330
begin
331
@lsock.close if @lsock
332
rescue
333
end
334
335
begin
336
@rsock.close if @rsock
337
rescue
338
end
339
340
@client_thread.kill if( @client_thread and @client_thread.alive? )
341
342
@server.remove_client( self )
343
344
@closed = true
345
end
346
end
347
end
348
349
end
350
351
#
352
# Create a new Socks4a server.
353
#
354
def initialize( opts={} )
355
@opts = { 'ServerHost' => '0.0.0.0', 'ServerPort' => 1080 }
356
@opts = @opts.merge( opts )
357
@server = nil
358
@clients = ::Array.new
359
@running = false
360
@server_thread = nil
361
end
362
363
#
364
# Check if the server is running.
365
#
366
def is_running?
367
return @running
368
end
369
370
#
371
# Start the Socks4a server.
372
#
373
def start
374
begin
375
# create the servers main socket (ignore the context here because we don't want a remote bind)
376
@server = Rex::Socket::TcpServer.create(
377
'LocalHost' => @opts['ServerHost'],
378
'LocalPort' => @opts['ServerPort'],
379
'Comm' => @opts['Comm']
380
)
381
# signal we are now running
382
@running = true
383
# start the servers main thread to pick up new clients
384
@server_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyServer", false) do
385
while( @running ) do
386
begin
387
# accept the client connection
388
sock = @server.accept
389
# and fire off a new client instance to handle it
390
Client.new( self, sock ).start
391
rescue
392
wlog( "Socks4a.start - server_thread - #{$!}" )
393
end
394
end
395
end
396
rescue
397
wlog( "Socks4a.start - #{$!}" )
398
return false
399
end
400
return true
401
end
402
403
#
404
# Block while the server is running.
405
#
406
def join
407
@server_thread.join if @server_thread
408
end
409
410
#
411
# Stop the Socks4a server.
412
#
413
def stop
414
if( @running )
415
# signal we are no longer running
416
@running = false
417
# stop any clients we have (create a new client array as client.stop will delete from @clients)
418
clients = []
419
clients.concat( @clients )
420
clients.each do | client |
421
client.stop
422
end
423
# close the server socket
424
@server.close if @server
425
# if the server thread did not terminate gracefully, kill it.
426
@server_thread.kill if( @server_thread and @server_thread.alive? )
427
end
428
return !@running
429
end
430
431
def add_client( client )
432
@clients << client
433
end
434
435
def remove_client( client )
436
@clients.delete( client )
437
end
438
439
attr_reader :opts
440
441
end
442
443
end; end; end
444
445
446