Advertisement
opexxx

transslvania.rb

Aug 2nd, 2014
340
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 9.00 KB | None | 0 0
  1. #!/usr/bin/env ruby
  2.  
  3. require 'socket'
  4. require 'openssl'
  5. require 'logger'
  6. require 'optparse'
  7. require 'uri'
  8.  
  9. $CLIENT_HELLOS = ["\x16\x03", #First 2 bytes of ClientHellos this should (?) cover common SSL/TLS version
  10.                   "\x80\x9e"]
  11. #For some reason, ruby doesn't define this constant
  12. if not Socket.const_defined? 'SO_ORIGINAL_DST'
  13.   Socket.const_set 'SO_ORIGINAL_DST', 80
  14. end
  15.  
  16. class Request # grabs an HTTP request from the socket
  17.   # TODO this should probably be WEBrick::HTTPRequest
  18.   attr_accessor :contents, :method, :host, :port
  19.   def initialize(client, ssl=false)
  20.     @contents = ""
  21.     while l = client.readpartial(4096) and not l.end_with? "\r\n"
  22.       @contents << l
  23.     end
  24.     @contents << l
  25.     lines = @contents.split("\n")
  26.     @method, addr, protocol = lines[0].split
  27.     if self.connect_method? #addr is host:port
  28.       @host, @port = addr.split ':'
  29.       if @port.nil?
  30.         @port = 443
  31.       else
  32.         @port = @port.to_i
  33.       end
  34.     else #addr is a uri
  35.       uri = URI(addr)
  36.       @host = uri.host || lines[1].split[1]
  37.       @port = uri.port || (ssl ? 443 : 80)
  38.     end
  39.   end
  40.   def connect_method?
  41.     @method == "CONNECT"
  42.   end
  43. end
  44.  
  45.  
  46. class SSLProxy
  47.   def initialize(host, port, opt = {})
  48.     @host = host
  49.     @port = port
  50.     @invisible = opt[:invisible] || false
  51.     @upstream_host = opt[:upstream_host] || nil
  52.     @upstream_port = opt[:upstream_port] || nil
  53.     # use this to cache forged ssl certs (SSLContexts)
  54.     @ssl_contexts = Hash.new { |ssl_contexts, subject|
  55.       #we use a previously generated root ca
  56.       root_key = OpenSSL::PKey::RSA.new File.open("root.key")
  57.       root_ca = OpenSSL::X509::Certificate.new File.open("root.pem")
  58.      
  59.       #generate the forged cert
  60.       key = OpenSSL::PKey::RSA.new 2048
  61.       cert = OpenSSL::X509::Certificate.new
  62.       cert.version = 2
  63.       cert.serial = Random.rand(1000)
  64.       cert.subject = subject
  65.       cert.issuer = root_ca.subject # root CA is the issuer
  66.       cert.public_key = key.public_key
  67.       cert.not_before = Time.now
  68.       cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity
  69.       ef = OpenSSL::X509::ExtensionFactory.new cert, root_ca
  70.       ef.create_ext("keyUsage","digitalSignature", true)
  71.       ef.create_ext("subjectKeyIdentifier","hash",false)
  72.       ef.create_ext("basicConstraints","CA:FALSE",false)
  73.       cert.sign(root_key, OpenSSL::Digest::SHA256.new)
  74.  
  75.       #fill out the context
  76.       ctx = OpenSSL::SSL::SSLContext.new
  77.       ctx.key = key
  78.       ctx.cert = cert
  79.       ctx.ca_file="root.pem"
  80.       ssl_contexts[subject] = ctx
  81.     }
  82.     @proxy = TCPServer.new(@host, @port)
  83.   end
  84.  
  85.   def upstream_proxy?
  86.     #are we forwarding traffic to an upstream proxy (vs. directly to
  87.     #host)
  88.     not (@upstream_host.nil? or @upstream_port.nil?)
  89.   end
  90.  
  91.   def start
  92.     puts "Serving on #{@host}:#{@port}"
  93.     loop do
  94.       client = @proxy.accept
  95.       Thread.new(client) { |client|
  96.     begin
  97.           if @invisible
  98.             #we grab the 1st two bytes to see if they contain the magic number
  99.             #for SSL ClientHello, and create an SSL socket accordingly
  100.             bytes = client.recv(2, Socket::MSG_PEEK)  
  101.             if $CLIENT_HELLOS.include? bytes
  102.               $LOG.debug("First bytes #{bytes}, SSL ClientHello")
  103.               dummy, port, host = client.getsockopt(Socket::SOL_IP, Socket::SO_ORIGINAL_DST).unpack("nnN")
  104.               cert = self.get_cert(host, port)
  105.               ctx = @ssl_contexts[cert.subject]
  106.               ssl_client = OpenSSL::SSL::SSLSocket.new(client,ctx)
  107.               ssl_client.accept
  108.               request = Request.new ssl_client, true
  109.               self.request_handler_ssl ssl_client, request
  110.             else
  111.               $LOG.debug("First bytes #{bytes}, HTTP")
  112.               request = Request.new client
  113.               self.request_handler client, request
  114.             end
  115.           else
  116.             request = Request.new client
  117.             self.request_handler client, request
  118.           end
  119.         rescue
  120.           $LOG.error($!)
  121.         end
  122.       }
  123.     end
  124.   end
  125.  
  126.   def connect_ssl(host, port, initial = nil)
  127.     socket = TCPSocket.new(host,port)
  128.     if initial
  129.       socket.write initial << "\r\n"
  130.       #TODO: interpret this and error out here if its not 200?
  131.       dummy = (socket.readpartial(4096)) rescue nil
  132.     end
  133.     ssl = OpenSSL::SSL::SSLSocket.new(socket)
  134.     ssl.sync_close = true
  135.     ssl.connect
  136.   end
  137.  
  138.   def get_cert(host, port)
  139.     c = self.connect_ssl host, port
  140.     c.peer_cert
  141.   end
  142.  
  143.   def request_handler(client, request)
  144.     #if this is the visible proxy mode, the client will send us an
  145.     #unencrypted CONNECT request before we begin the SSL handshake
  146.     #we ascertain the host/port from there
  147.     if request.connect_method?
  148.       #connect to the server and forge the correct cert (using the same
  149.       #subject as the server we connected to)
  150.       if self.upstream_proxy?
  151.         cert = self.get_cert request.host, request.port
  152.         server = self.connect_ssl @upstream_host, @upstream_port
  153.       else
  154.         server = self.connect_ssl request.host, request.port
  155.         cert = server.peer_cert
  156.       end
  157.       ctx = @ssl_contexts[cert.subject]
  158.       client.write "HTTP/1.0 200 Connection established\r\n\r\n"
  159.       #initiate handshake
  160.       ssl_client = OpenSSL::SSL::SSLSocket.new(client, ctx)
  161.       ssl_client.accept
  162.       self.create_pipe ssl_client, server, initial_request
  163.     else
  164.       #we're just passing through unencrypted data
  165.       if self.upstream_proxy?
  166.         server = TCPSocket.new(@upstream_host, @upstream_port)
  167.       else
  168.         server = TCPSocket.new(request.host, request.port)
  169.         #server.write request.contents
  170.         #server.write "\r\n"
  171.       end
  172.       #we pass along the request we cached
  173.       self.create_pipe client, server, request
  174.     end
  175.   end
  176.  
  177.   def request_handler_ssl(ssl_client, request)
  178.     if self.upstream_proxy?
  179.       server = self.connect_ssl @upstream_host, @upstream_port, "CONNECT #{request.host}:#{request.port} HTTP/1.1\r\n"
  180.     else
  181.       server = self.connect_ssl request.host, request.port
  182.     end
  183.     self.create_pipe ssl_client, server, request
  184.   end
  185.  
  186.   def create_pipe(client, server, initial_request)
  187.     if initial_request
  188.       server.write initial_request.contents
  189.      # server.write "\r\n"
  190.       server.flush
  191.       $LOG.info("#{Thread.current}: client->server (initial) #{initial_request.inspect}")
  192.     end
  193.     while true
  194.       # Wait for data to be available on either socket.
  195.       (ready_sockets, dummy, dummy) = IO.select([client, server])
  196.       begin
  197.         ready_sockets.each do |socket|
  198.           if socket == client #and not socket.eof?
  199.             # Read from client, write to server.
  200.             request = Request.new client
  201.             # we may get requests for another domain coming down
  202.             # this pipe if we are a visible proxy
  203.             # if wer're not proxied, we restart the handler
  204.             unless @invisible or self.upstream_proxy?
  205.               if request.host != initial_request.host or request.port != initial_request.port
  206.                 #we can also close the connection here??
  207.                 #server.close
  208.                 #client.close
  209.                 self.request_handler client, request
  210.                 break
  211.               end
  212.             end
  213.             $LOG.info("#{Thread.current}: client->server #{request.inspect}")
  214.             server.write request.contents
  215.             server.flush
  216.           else
  217.             # Read from server, write to client.
  218.             (data = socket.readpartial(4096)) rescue nil
  219.             $LOG.info("#{Thread.current}: server->client #{data.inspect}")
  220.             client.write data
  221.             client.flush
  222.           end
  223.         end
  224.       rescue IOError
  225.         $LOG.debug($!)
  226.         break
  227.       end
  228.     end
  229.     unless client.closed?
  230.       client.close
  231.     end
  232.     unless server.closed?
  233.       server.close
  234.     end
  235.   end
  236. end
  237.  
  238. $LOG = Logger.new($stdout)
  239. $LOG.sev_threshold = Logger::ERROR
  240.  
  241. options = {}
  242. host = "localhost"
  243. port = 8080
  244. OptionParser.new do |opts|
  245.   opts.banner = "Usage: example.rb [options]"
  246.   opts.on("-l", "--listen HOST:PORT", "Host and port to listen on") do |address|
  247.     h, p = address.split(':')
  248.     if h.nil? or p.nil?
  249.       $stderr.puts "address must be in the form host:port"
  250.       exit
  251.     end
  252.     host = h
  253.     port = p
  254.   end
  255.   opts.on("-p", "--upstream_proxy HOST:PORT", "Use an upstream proxy") do |proxy|
  256.     host, port = proxy.split(':')
  257.     if host.nil? or port.nil?
  258.       $stderr.puts "upstream proxy must be in the form host:port"
  259.       exit
  260.     end
  261.     options[:upstream_host] = host
  262.     options[:upstream_port] = port
  263.   end
  264.   opts.on("-i", "--invisible", "Run in invisible proxy mode (use iptables to forward traffic to SSLProxy)") do
  265.     options[:invisible] = true
  266.   end
  267.   opts.on("-d", "--debug", "Enable debug output") do
  268.     $LOG.sev_threshold = Logger::DEBUG
  269.   end
  270. end.parse!
  271.  
  272. puts options
  273. s = SSLProxy.new(host, port, options)
  274. s.start
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement