Monday, March 16, 2009

Fix it with *nix.

I recently had to deal with something that can only be described as an EPIC SECURITY FAIL on the part of a software vendor. I'm half tempted to name the vendor just to shame them, but I'm going to refrain on the off chance that what we're seeing is misconfiguration on the part of our system administrator. Unlikely, but I don't want to get called out if I'm wrong.

Anyway, we're using this help desk ticketing software that has a reputation for sucking. I don't know anyone that has used this in real life and didn't say "Oh man, this sucks." We recently upgraded and along with the upgrade we finally got the ability for users to interact with a web client rather than a Windows-only fat application. That made me happy until I saw that the authentication portion of the web application was completely unencrypted. What if we were using Active Directory to log into this thing? We aren't, but if we were we would have our AD passwords going out in the clear. Besides, I'm sure that some of us are using the same password for both systems. So I talked to the system administrator and found out just how deep the depths of sucking in this application are.

The web client is basically an application running on Apache Tomcat that is distributed as an all-in-one binary bundle of some kind. That means that we have no way of slapping an SSL certificate on this bad boy or making it run on a different port (like 443). In other words, we're completely screwed. Luckily, Black Fist is a wise and resourceful security manager. I devised a way to make this thing suck less (I cannot completely remove the suction) and make it acceptable to me in some way.

I decided to build up a reverse proxy in front of the crapplication that would provide SSL encryption and then take steps to make sure that the unencrypted traffic didn't get back on the wire. We are using a virtual server to provide the application, so the first thing I did was have a second virtual NIC added to the machine in a private network. Then I had another virtual machine built up with two NICs: one for the public and on in the same private network. Then I installed OpenBSD on my proxy server because OpenBSD is sweet. Yes, I could have used linux, but I chose OpenBSD.

Obviously I want to take advantage of the sweet firewall that comes with OpenBSD, so I set up pf as follows.
# Edit /etc/rc.conf to make pf firewall start automatically at system startup.
pf=YES
pf_rules=/etc/pf.conf
and then...
# Edit /etc/pf.conf with a basic setup that allows https traffic to my proxy front end
ext_if="vic0"
int_if="vic1"

# It is a good idea to disable filtering on the loopback if
set skip on lo

# It is also a good idea to scrub incoming traffic
scrub in

# This is our default deny rule. Deny traffic in, but let
# everything out.
block in log on $ext_if
pass out log all keep state

# This rule allows icmp echo in
pass in inet proto icmp all icmp-type echoreq keep state

# This rule allows ssh in
pass in log on $ext_if inet proto tcp from any to external_ip port 22 flags S/SA synproxy state

# This rule allows https in
pass in log on $ext_if inet proto tcp from any to external_ip port 443 flags S/SA synproxy state
Relayd is going to act as our proxy server because it is built in and simple.
# Edit /etc/rc.conf to turn on relayd at system startup
relayd_flags=""
And then...
# Edit /etc/relayd.conf to turn on our proxy server
ext_addr="external ip address"
helpdesk="192.168.1.10"
table { 192.168.1.10 }

#
# Global Options
#
interval 10
timeout 1000
prefork 5

# Relay and protocol for HTTP layer 7 loadbalancing and SSL acceleration
#
http protocol httpssl {
header append "$REMOTE_ADDR" to "X-Forwarded-For"
header append "$SERVER_ADDR:$SERVER_PORT" to "X-Forwarded-By"
header change "Connection" to "close"

# Various TCP performance options
tcp { nodelay, sack, socket buffer 65536, backlog 128 }

ssl { no sslv2, sslv3, tlsv1, ciphers HIGH }
ssl session cache disable
}

relay sendtohelpdesk {
# Run as a SSL accelerator
listen on $ext_addr port 443 ssl
protocol httpssl

# Forward to hosts in the webhosts table using a src/dst hash
forward to port 8180 mode loadbalance \
check http "/directory/file.html" code 200
}

A quick reboot just to make sure that everything starts up on boot. Sure enough, my firewall rules are in place
$ sudo pfctl -s rules
scrub in all fragment reassemble
block drop in log on vic0 all
pass out log all flags S/SA keep state
pass in inet proto icmp all icmp-type echoreq keep state
pass in log on vic0 inet proto tcp from any to external_ip port = ssh flags S/SA synproxy state
pass in log on vic0 inet proto tcp from any to external_ip port = https flags S/SA synproxy state
and on relayd...
$ relayctl show summary
Id Type Name Avlblty Status
1 relay sendtohelpdesk active
1 table helpdesk:8180 active (1 hosts up)
1 host 192.168.1.10 99.97% up

When I visit my front end ip address on port 443 with my browser, I am seamlessly proxied to the backend server. All the traffic crossing the wire is encrypted, and the traffic between the proxy server and the real server is in memory on the virtual machine server. It's not perfect, but a big improvement over sending unencrypted authentication data over the wire.

There are a couple other things I want to point out. First of all, I am not a master of pf. I know it well enough to make things work. I'm sure that my pf.conf file could use some work. If you have any suggestions, let me know. I would love to improve this. I also know that I didn't have to use a table in relayd.conf. I chose to do that in case we ever add another server like this. It's there for future-proofing, not because of necessity.

I hope this helps somebody out there, or at a minimum gives someone an idea on how to fix some problem they're having with a less conventional technique. Anyone that knows me knows that I am all about unconventional.

No comments: