Headquarters888-732-9406
Security Operations Center 888-732-9407
Blog: SSL Relay Proxy

SSL Relay Proxy: A Creative Solution to a Complex Issue

I ran into a situation recently where I needed an SSL interception proxy. I was testing a thick client that spoke with a server (not HTTP) over SSL. I couldn't find anything on the Internet about a female-to-male (relay) non-HTTP SSL proxy. The situations where an SSL interception/relay proxy would be useful are fairly slim, but so are things that I can't find anything about on the Internet, so here you go.

The Restrictions

I didn't have the private key, and I was in an environment that wouldn't allow me to use any tools except BackTrack and Windows (which the client was running). I also could not actively man-in-the-middle the traffic (e.g., ARP Poison). The only things going for me were that I could tell the app what server to use and that the app was not validating the SSL certificate.

Dead Ends

A lot of tools assume that the traffic will be MITM'd (ettercap, SSL sniff), so packet forwarding just needs to be enabled and it will get to its destination. My iptables skills were probably limiting me, but I wasn't able to get the traffic routed to the server and get my backtrack box to see it. There may be a way to do this, but I couldn't figure it out. Iptables gets hard quickly (for me at least). I tried NCAT and Open SSL, but neither of them allow multi-thread connections -- basically they are only good for one TCP session. So, everything would be fine until the second TCP session decided to ruin things. NCAT has a "keep open" mode, but it never closes the output stream, so the client program would hang because it's waiting for the TCP session to close.

The Solution

Finally, I tried using stunnel, which is multi-threaded and would take care of the SSL striping, then a second instance would re-add SSL to keep the server happy. To get that working, I'd use two configuration files, one to bring it in, strip SSL and drop it on a port, then another to pick it up on a listening port and replay the traffic over SSL to the server. Yes, we'll need something in-between those two to actually log the traffic. More on that later. Here are the configuration files I ended up using. I also generated an SSL cert to be used (stunnel.pem, self-signed of course).

Generate stunnel.pem Certificate

openssl req -new -x509 -days 365 -nodes -out stunnel.pem -keyout stunnel.pem

listener.conf

client = no

debug = 3

foreground = yes

#chroot = /var/run/stunnel

#setuid = nobody

#setgid = nobody

pid = /stunnel.pid

cert= ./cert.pem

[MyApp]

accept = 3800

connect = 3801

client.conf

client = yes

debug = 3

foreground = yes

#chroot = /var/run/stunnel

#setuid = nobody

#setgid = nobody

pid = /stunnel.pid

cert= ./stunnel.pem

[MyApp]

#change to 3801 to just relay the traffic without using the ruby tunnel

accept = 3802

#this is where the server is listening

connect = 192.168.1.87:3800

The Logger

So everything is complete at this point, except we have to get the traffic logged, which is why we left the gap between ports 3801 and 3802. I got pretty far on a ruby proxy, but when I was working on the multi-threading, I found one that did exactly what I needed here by Amnon. Ruby threading is beautiful, by the way. I only added two lines, one to dump each "direction" of data to the console.

require 'socket'

if ARGV.length < 1
    $stderr.puts "Usage: #{$0} remoteHost:remotePort [ localPort [ localHost ] ]"
    exit 1
end

$remoteHost, $remotePort = ARGV.shift.split(":")
puts "target address: #{$remoteHost}:#{$remotePort}"
localPort = ARGV.shift || $remotePort
localHost = ARGV.shift

$blockSize = 1024

server = TCPServer.open(localHost, localPort)

port = server.addr[1]
addrs = server.addr[2..-1].uniq

puts "*** listening on #{addrs.collect{|a|"#{a}:#{port}"}.join(' ')}"

# abort on exceptions, otherwise threads will be silently killed in case
# of unhandled exceptions
Thread.abort_on_exception = true

# have a thread just to process Ctrl-C events on Windows
# (although Ctrl-Break always works)
Thread.new { loop { sleep 1 } }

def connThread(local)
    port, name = local.peeraddr[1..2]
    puts "*** receiving from #{name}:#{port}"

    # open connection to remote server
    remote = TCPSocket.new($remoteHost, $remotePort)

    # start reading from both ends
    loop do
        ready = select([local, remote], nil, nil)
        if ready[0].include? local
            # local -> remote
            data = local.recv($blockSize)
            if data.empty?
                puts "local end closed connection"
                break
            end
            remote.write(data)
            puts(data)
        end
        if ready[0].include? remote
            # remote -&gt; local
            data = remote.recv($blockSize)
            if data.empty?
                puts "remote end closed connection"
                break
            end
            local.write(data)
            puts(data)
        end
    end

    local.close
    remote.close

    puts "*** done with #{name}:#{port}"
end

loop do
    # whenever server.accept returns a new connection, start
    # a handler thread for that connection
    Thread.start(server.accept) { |local| connThread(local) }
end

OK, now we are finally ready to do something that shouldn't be that hard in the first place. All we need to do is run the stunnel with the two conference files and then run the ruby proxy to dump the traffic.

stunnel listener.conf & stunnel client.conf & ruby tunnel.rb 127.0.0.1:3802 3801 127.0.0.1 > logfile 

Of course, you can tee your log file if you want to view it on the fly. There are limitless solutions to this (e.g., implementing SSL in ruby -- probably isn't hard), but this is what I ended with. Let me know if you come up with a different solution. I'd love to hear about it.

Author



Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.