Skip to content

Instantly share code, notes, and snippets.

@jduck
Created June 6, 2017 01:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jduck/49183a4399e50645e327542829b2a639 to your computer and use it in GitHub Desktop.
Save jduck/49183a4399e50645e327542829b2a639 to your computer and use it in GitHub Desktop.
From 6b4eba818a99db7cd0b268d5410b0b2e1aa1aab9 Mon Sep 17 00:00:00 2001
From: "Joshua J. Drake" <github.jdrake@qoop.org>
Date: Wed, 12 Nov 2014 19:31:56 -0600
Subject: [PATCH] rebase http-proxy patch
---
lib/rex/io/stream_server.rb | 9 ++
lib/rex/proto.rb | 2 +
lib/rex/proto/http/server.rb | 7 +
lib/rex/proto/proxy.rb | 77 +++++++++++
lib/rex/proto/proxy/http.rb | 243 +++++++++++++++++++++++++++++++++
lib/rex/proto/proxy/socks4a.rb | 72 +---------
modules/auxiliary/server/http_proxy.rb | 79 +++++++++++
7 files changed, 422 insertions(+), 67 deletions(-)
create mode 100644 lib/rex/proto/proxy.rb
create mode 100644 lib/rex/proto/proxy/http.rb
create mode 100644 modules/auxiliary/server/http_proxy.rb
diff --git a/lib/rex/io/stream_server.rb b/lib/rex/io/stream_server.rb
index 188fb63..b452521 100644
--- a/lib/rex/io/stream_server.rb
+++ b/lib/rex/io/stream_server.rb
@@ -102,6 +102,15 @@ module StreamServer
end
#
+ # Remove a client socket without closing it
+ #
+ def remove_client(client)
+ if (client)
+ clients.delete(client)
+ end
+ end
+
+ #
# This method waits on the server listener thread
#
def wait
diff --git a/lib/rex/proto.rb b/lib/rex/proto.rb
index dbfd86c..39375c9 100644
--- a/lib/rex/proto.rb
+++ b/lib/rex/proto.rb
@@ -7,6 +7,8 @@ require 'rex/proto/drda'
require 'rex/proto/iax2'
require 'rex/proto/kerberos'
+require 'rex/proto/proxy'
+
module Rex
module Proto
diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb
index ca0ed82..6e9e559 100644
--- a/lib/rex/proto/http/server.rb
+++ b/lib/rex/proto/http/server.rb
@@ -185,6 +185,13 @@ class Server
end
#
+ # Remove the client without closing it.
+ #
+ def remove_client(cli)
+ listener.remove_client(cli)
+ end
+
+ #
# Mounts a directory or resource as being serviced by the supplied handler.
#
def mount(root, handler, long_call = false, *args)
diff --git a/lib/rex/proto/proxy.rb b/lib/rex/proto/proxy.rb
new file mode 100644
index 0000000..2980837
--- /dev/null
+++ b/lib/rex/proto/proxy.rb
@@ -0,0 +1,77 @@
+require 'thread'
+
+require 'rex/proto/proxy/socks4a'
+require 'rex/proto/proxy/http'
+
+module Rex
+module Proto
+module Proxy
+
+#
+# A mixin for a socket to perform a relay to another socket.
+#
+module Relay
+
+ #
+ # Relay data coming in from relay_sock to this socket.
+ #
+ def relay( relay_client, thread_name, relay_sock )
+ @relay_client = relay_client
+ @relay_sock = relay_sock
+
+ # start the relay thread (modified from Rex::IO::StreamAbstraction)
+ @relay_thread = Rex::ThreadFactory.spawn(thread_name, false) do
+ loop do
+ closed = false
+ buf = nil
+
+ begin
+ s = Rex::ThreadSafe.select( [ @relay_sock ], nil, nil, 0.2 )
+ if( s == nil || s[0] == nil )
+ next
+ end
+ rescue
+ closed = true
+ end
+
+ if( closed == false )
+ begin
+ buf = @relay_sock.sysread( 32768 )
+ closed = true if( buf == nil )
+ rescue
+ closed = true
+ end
+ end
+
+ if( closed == false )
+ total_sent = 0
+ total_length = buf.length
+ while( total_sent < total_length )
+ begin
+ data = buf[total_sent, buf.length]
+ sent = self.write( data )
+ if( sent > 0 )
+ total_sent += sent
+ end
+ rescue
+ closed = true
+ break
+ end
+ end
+ end
+
+ if( closed )
+ @relay_client.stop if @relay_client
+ ::Thread.exit
+ end
+ end
+ end
+
+ end
+
+end
+
+end
+end
+end
+
diff --git a/lib/rex/proto/proxy/http.rb b/lib/rex/proto/proxy/http.rb
new file mode 100644
index 0000000..21b7bf8
--- /dev/null
+++ b/lib/rex/proto/proxy/http.rb
@@ -0,0 +1,243 @@
+#
+# Http Proxy - jduck
+#
+
+require 'rex/proto/http/server'
+
+module Rex
+module Proto
+module Proxy
+
+
+module Request
+ def initialize
+ super
+ self.uri_obj = nil
+ end
+
+ def uri_obj=
+ self.uri = uri_obj
+ end
+
+ attr_accessor :uri_obj
+end
+
+
+module Response
+ def initialize
+ super
+ self.orig_req = nil
+ end
+
+ def orig_req=
+ self.req = orig_req
+ end
+
+ attr_accessor :orig_req
+end
+
+
+#
+# The Proxy::Http class
+#
+class Http < Rex::Proto::Http::Server
+
+ #
+ # Callbacks that can be modified by consumers
+ #
+ def on_http_request(cli, req)
+ if (on_http_request_proc)
+ return on_http_request_proc.call(cli, req)
+ end
+ true
+ end
+
+ def on_http_response(cli, res)
+ if (on_http_response_proc)
+ return on_http_response_proc.call(cli, res)
+ end
+ true
+ end
+
+ def on_http_connect(cli, res)
+ if (on_http_connect_proc)
+ return on_http_connect_proc.call(cli, res)
+ end
+ true
+ end
+
+ attr_accessor :on_http_request_proc
+ attr_accessor :on_http_response_proc
+ attr_accessor :on_http_connect_proc
+
+protected
+
+ #
+ # Put humpty dumpty back together again.
+ #
+ def rebuild_uri(request)
+ # reconstitute the requested URI based on the parts
+ uri = "#{request.resource}?"
+
+ # NOTE: #normalize! screws with the request, so fix its madness.
+ if uri =~ /:\/[^\/]/
+ uri.gsub!(':/', '://')
+ end
+
+ # reconstitute the query string
+ vars = []
+ request.qstring.each { |k,v|
+ vars << "#{k}=#{v}"
+ }
+
+ vars_comb = vars.join('&')
+ vars_comb.gsub!(/ /, '+')
+
+ # combine resource and qstring
+ uri << vars_comb
+ uri
+ end
+
+ def send_ok(cli, request)
+ ok = Rex::Proto::Http::Response::OK.new
+ cli.put(ok.to_s)
+ end
+
+
+ # Overrides for stuff from Rex::Proto::Http::Server
+
+ def dispatch_request(cli, request)
+ case request.method
+
+ when "GET", "POST"
+ begin
+ uri = URI.parse(rebuild_uri(request))
+ rescue ::Exception => e
+ send_e404(cli, request)
+ wlog("Exception in HttpProxy dispatch_request while parsing request URI: #{e.class}: #{e}")
+ wlog("Call Stack\n#{e.backtrace.join("\n")}")
+ close_client(cli)
+ return
+
+ end
+
+ # Allow callers to change the incoming request
+ request.extend(Request)
+ request.uri_obj = uri
+
+ return if not on_http_request(cli, request)
+
+ # Now, we must connect to the target server and repeat the request.
+ rcli = Rex::Proto::Http::Client.new(
+ uri.host,
+ uri.port || 80,
+ self.context)
+
+ # Delete headers that end up getting duplicated. They should end up the
+ # same value, but having two of them still isn't helpful :-/
+ request.headers.delete('Host')
+ request.headers.delete('Content-Length')
+
+ # Send the request
+ rreq = rcli.request_raw({
+ 'uri' => uri.path + "?" + uri.query,
+ 'method' => request.method,
+ 'headers' => request.headers,
+ 'data' => request.body,
+ })
+
+ begin
+ # Read the response
+ resp = rcli.send_recv(rreq)
+
+ rescue ::Exception => e
+ send_e404(cli, request)
+ wlog("Exception in HttpProxy dispatch_request while relaying request: #{e.class}: #{e}")
+ wlog("Call Stack\n#{e.backtrace.join("\n")}")
+ close_client(rcli)
+ close_client(cli)
+ return
+
+ end
+
+ # Add the original request (prior to rebuilding)
+ resp.extend(Response)
+ resp.orig_req = request
+
+ # Don't rescue exceptions in this, they should be shown to whoever is
+ # implementing on_http_response.
+ return if not on_http_response(cli, resp)
+
+ begin
+ # Send it back to the requesting client
+ cli.put(resp.to_s)
+
+ rescue ::Exception => e
+ send_e404(cli, request)
+ wlog("Exception in HttpProxy dispatch_request while relaying response: #{e.class}: #{e}")
+ wlog("Call Stack\n#{e.backtrace.join("\n")}")
+
+ end
+
+ # Bye!
+ close_client(rcli)
+ close_client(cli)
+
+ when "CONNECT"
+ host,port = request.resource.split(':')
+ port = port.to_i
+
+ return if not on_http_connect(cli, request)
+
+ # only tunnel SSL requests
+ if port != 443
+ send_e404(cli, request)
+ ilog("HttpProxy: Rejecting CONNECT request for #{host}:#{port} from #{cli.peerhost} ...", LEV_2);
+ close_client(cli)
+ return
+ end
+
+ # Now, we must connect to the target server and relay the data...
+ # NOTE: according to rfc2817, the data accompanying this request should be included too.
+ begin
+ rcli = Rex::Socket::Tcp.create({
+ 'PeerHost' => host,
+ 'PeerPort' => port,
+ 'Context' => self.context,
+ })
+
+ rescue ::Exception => e
+ wlog("Exception in Proxy dispatch_request while handling CONNECT: #{e.class}: #{e}")
+ wlog("Call Stack\n#{e.backtrace.join("\n")}")
+
+ send_e404(cli, request)
+ close_client(cli)
+ return
+
+ end
+
+ # don't let the server's client monitor see this guy anymore
+ remove_client(cli)
+
+ # Tell the client it's go time.
+ send_ok(cli, request)
+
+ # Relay the data back and forth
+ cli.extend(Rex::Proto::Proxy::Relay)
+ rcli.extend(Rex::Proto::Proxy::Relay)
+ cli.relay(nil, "HttpProxySSLRelay (c2s)", rcli)
+ rcli.relay(nil, "HttpProxySSLRelay (s2c)", cli)
+
+ # If we are given a trusted CA cert to sign with, we could also
+ # transparently generate a CERT and MITM the SSL data.
+
+ end
+
+ end
+
+end
+
+end
+end
+end
+
diff --git a/lib/rex/proto/proxy/socks4a.rb b/lib/rex/proto/proxy/socks4a.rb
index aa02e27..e7e9004 100644
--- a/lib/rex/proto/proxy/socks4a.rb
+++ b/lib/rex/proto/proxy/socks4a.rb
@@ -5,6 +5,7 @@
require 'thread'
require 'rex/logging'
require 'rex/socket'
+require 'rex/proto/proxy'
module Rex
module Proto
@@ -155,69 +156,6 @@ class Socks4a
end
#
- # A mixin for a socket to perform a relay to another socket.
- #
- module Relay
-
- #
- # Relay data coming in from relay_sock to this socket.
- #
- def relay( relay_client, relay_sock )
- @relay_client = relay_client
- @relay_sock = relay_sock
- # start the relay thread (modified from Rex::IO::StreamAbstraction)
- @relay_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyServerRelay", false) do
- loop do
- closed = false
- buf = nil
-
- begin
- s = Rex::ThreadSafe.select( [ @relay_sock ], nil, nil, 0.2 )
- if( s == nil || s[0] == nil )
- next
- end
- rescue
- closed = true
- end
-
- if( closed == false )
- begin
- buf = @relay_sock.sysread( 32768 )
- closed = true if( buf == nil )
- rescue
- closed = true
- end
- end
-
- if( closed == false )
- total_sent = 0
- total_length = buf.length
- while( total_sent < total_length )
- begin
- data = buf[total_sent, buf.length]
- sent = self.write( data )
- if( sent > 0 )
- total_sent += sent
- end
- rescue
- closed = true
- break
- end
- end
- end
-
- if( closed )
- @relay_client.stop
- ::Thread.exit
- end
- end
- end
-
- end
-
- end
-
- #
# Create a new client connected to the server.
#
def initialize( server, sock )
@@ -309,11 +247,11 @@ class Socks4a
raise "Failed to handle the clients request."
end
# setup the two way relay for full duplex io
- @lsock.extend( Relay )
- @rsock.extend( Relay )
+ @lsock.extend( Rex::Proto::Proxy::Relay )
+ @rsock.extend( Rex::Proto::Proxy::Relay )
# start the socket relays...
- @lsock.relay( self, @rsock )
- @rsock.relay( self, @lsock )
+ @lsock.relay( self, "SOCKS4AProxyServerRelay (l2r)", @rsock )
+ @rsock.relay( self, "SOCKS4AProxyServerRelay (r2l)", @lsock )
rescue
wlog( "Client.start - #{$!}" )
self.stop
diff --git a/modules/auxiliary/server/http_proxy.rb b/modules/auxiliary/server/http_proxy.rb
new file mode 100644
index 0000000..5ecaae4
--- /dev/null
+++ b/modules/auxiliary/server/http_proxy.rb
@@ -0,0 +1,79 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'thread'
+require 'msf/core'
+require 'rex/proto/proxy/http'
+
+class Metasploit3 < Msf::Auxiliary
+
+ include Msf::Auxiliary::Report
+
+ def initialize(info = {})
+ super(update_info(info,
+ 'Name' => 'HTTP Proxy Server',
+ 'Description' => 'This module provides an HTTP proxy server that uses the builtin Metasploit routing to relay connections.',
+ 'Author' => [ 'jduck' ],
+ 'License' => MSF_LICENSE,
+ 'Actions' =>
+ [
+ [ 'Proxy' ]
+ ],
+ 'PassiveActions' =>
+ [
+ 'Proxy'
+ ],
+ 'DefaultAction' => 'Proxy'))
+
+ register_options(
+ [
+ OptString.new( 'SRVHOST', [ true, "The address to listen on", '0.0.0.0' ] ),
+ OptPort.new('SRVPORT', [ true, "The daemon port to listen on", 80 ]),
+ ], self.class)
+ end
+
+ def setup
+ super
+ @mutex = ::Mutex.new
+ @hproxy = nil
+ end
+
+ def cleanup
+ @mutex.synchronize do
+ if( @hproxy )
+ print_status( "Stopping the HTTP proxy server" )
+ @hproxy.stop
+ @hproxy = nil
+ end
+ end
+ super
+ end
+
+ def run
+ opts = {
+ 'ServerHost' => datastore['SRVHOST'],
+ 'ServerPort' => datastore['SRVPORT'],
+ 'Context' => {'Msf' => framework, 'MsfExploit' => self}
+ }
+
+ @hproxy = Rex::Proto::Proxy::Http.new(
+ datastore['SRVPORT'],
+ datastore['SRVHOST'],
+ false,
+ {
+ 'Msf' => framework,
+ 'MsfExploit' => self
+ })
+
+ print_status( "Starting the HTTP proxy server" )
+
+ @hproxy.start
+ @hproxy.wait
+ end
+
+end
+
--
1.9.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment