Bradey Honsinger
2002-Oct-14 20:00 UTC
MS PPTP security hole, workaround (was RE: [Shorewall-users] VPN PPTP DNAT does not work)
Steve Cowles wrote:> FWIW: if your using an MS based PPTP server... consider checking out the > following security advisory: > > http://online.securityfocus.com/archive/1/293146/2002-09-24/2002-09-30/0 > > Based on other threads I have followed, PoPToP does not seem vulnerable to > this exploit.I''m worried enough about this problem that I blocked PPTP traffic when I saw it. There''s a DOS exploit (blue-screen the VPN server) out there, and it works--I''ve tried it out. It''s still not clear whether a remote-root exploit is possible. After leaving our PPTP VPN server down for a week and a half--and dealing with the pain that this caused our road warriors--we decided to allow PPTP connections from "trusted" IPs. Shorewall can do this using a comma-separated list of IPs in the rule, but it''s awkward when you have ten or twelve IPs. I wrote a ''vpnfilter'' shell script that works with Shorewall''s chains to set up a list of IPs trusted for VPN. It also allows you to add and remove trusted IPs without restarting Shorewall. This isn''t the ideal approach--it''s certainly better to use IPSec, or at least Poptop--but if you need to do it this way for expedient or political reasons, it''s certainly more secure than opening up your PPTP server to the world. - Bradey Here''s the script. Configuration values that you should fill in are marked by <<value>>. You need to add ''vpnfilter start'' and ''vpnfilter stop'' to /etc/shorewall/start and /etc/shorewall/stop. Suggestions and comments are welcome. vpnfilter ========#!/bin/bash -p # # VPN Filter # # Add iptables rules to filter PPTP connections by IP # # This is a small, simple script which works in conjunction # with Shorewall to allow PPTP access only to trusted IP addresses. # It adds a pair of DNAT rules (for GRE and PPTP) to Shorewall''s # chain for the FROM_ZONE in the NAT PREROUTING table. It adds pair # of rules to Shorewall''s chain for FROM_ZONE->TO_ZONE traffic in order # to route new VPN requests to the "vpnfilter" chain, which it creates. Rules # are added to the vpnfilter chain to accept (via the vpnlogaccept chain) # traffic from allowed IP addresses; if the incoming VPN request is not # from an allowed IP, it runs into the last rule on the vpnfilter chain, # which drops the packet (via vpnlogdrop). # # When ''vpnfilter start'' is called, it sets up the rules and reads the # initial filter list from FILTER_FILE. As IPs are added and removed, # they are added and removed from that file as well as the vpnfilter chain, # so that they may be preserved across reboots or Shorewall restarts # (''vpnfilter start'' is called each time Shorewall starts). # # It has been intentionally kept very simple, to the point of not # removing duplicate code (viz. rule insert/deletes) to avoid adding # additional shell functions. When adding or removing an IP address, # it does not check that the IP address does or doesn''t already exist # in the filter list. # # I didn''t use Shorewall''s run_iptables wrapper (as recommended by Tom) # because I wanted this script to be runnable from the command line; # run_iptables is defined in /var/lib/shorewall/firewall, which isn''t # sourceable from another script. # # Bradey Honsinger 10/8/02 # # # Configuration # # Shorewall zones VPN traffic is coming from and going to FROM_ZONE=net TO_ZONE=<<internal zone>> # External IP address VPN traffic is coming in on EXTERNAL_IP=<<external IP>> # Internal IP address of VPN server VPN_SERVER=<<internal IP>> # File containing list FILTER_FILE=/etc/shorewall/vpnfilter # Secure temporary dir (used when manipulating FILTER_FILE) TMP_DIR=<<tmp dir accessible only by root>> # # Constants # FILTER_CHAIN=vpnfilter LOGDROP_CHAIN=vpnlogdrop LOGACCEPT_CHAIN=vpnlogaccept # Forward chain is created by Shorewall, in the format <from>2<to> FORWARD_CHAIN=${FROM_ZONE}2${TO_ZONE} # NAT chain is created by Shorewall; same name as FROM_ZONE NAT_CHAIN=$FROM_ZONE # Temp file TMP_FILE=$TMP_DIR/`basename $FILTER_FILE`.tmp # Use Shorewall functions #SHOREWALL=/var/lib/shorewall/firewall #if [ -f $SHOREWALL ]; then # source $SHOREWALL #fi # # Functions # # Setup rules, and add saved IPs from file start() { echo "Initializing VPN filter..." echo " Creating VPN filter chains and rules" setup_rules echo " Adding allowed VPN IP addresses" add_from_file } # Set up VPN filter chains and rules # Start at the end of the chains, so that any traffic coming in while # we''re setting things up isn''t disrupted or misrouted. setup_rules() { # Create and setup logdrop chain iptables --new-chain $LOGDROP_CHAIN iptables --append $LOGDROP_CHAIN \ --jump LOG \ --log-prefix "$LOGDROP_CHAIN " --log-level 6 iptables --append $LOGDROP_CHAIN --jump DROP # Create and setup logaccept chain iptables --new-chain $LOGACCEPT_CHAIN iptables --append $LOGACCEPT_CHAIN \ --jump LOG \ --log-prefix "$LOGACCEPT_CHAIN " --log-level 6 iptables --append $LOGACCEPT_CHAIN --jump ACCEPT # Create and setup filter chain (drop by default) iptables --new-chain $FILTER_CHAIN iptables --append $FILTER_CHAIN --jump $LOGDROP_CHAIN # Add forwarding rules: Direct VPN traffic to the filter chain iptables --insert $FORWARD_CHAIN \ --destination $VPN_SERVER --protocol gre --match state --state NEW \ --jump $FILTER_CHAIN iptables --insert $FORWARD_CHAIN \ --destination $VPN_SERVER \ --protocol tcp --match tcp --dport 1723 --match state --state NEW \ --jump $FILTER_CHAIN # Add DNAT rules for all incoming traffic iptables -t nat --insert $NAT_CHAIN \ --destination $EXTERNAL_IP --protocol gre \ --jump DNAT --to-destination $VPN_SERVER iptables -t nat --insert $NAT_CHAIN \ --destination $EXTERNAL_IP --protocol tcp --match tcp --dport 1723 \ --jump DNAT --to-destination $VPN_SERVER } # Add filter rules for saved IPs from file add_from_file() { while read ip_address; do echo " $ip_address" add_rule $ip_address done < $FILTER_FILE } # Add filter rule for a single IP (from command line or from file) add_rule() { iptables --insert $FILTER_CHAIN \ --source $1 \ --jump $LOGACCEPT_CHAIN } # Remove VPN filter chains and rules # Remove in opposite order from add, to ensure that no packets are misrouted stop() { # Remove DNAT rules iptables -t nat --delete $NAT_CHAIN \ --destination $EXTERNAL_IP --protocol gre \ --jump DNAT --to-destination $VPN_SERVER iptables -t nat --delete $NAT_CHAIN \ --destination $EXTERNAL_IP --protocol tcp --match tcp --dport 1723 \ --jump DNAT --to-destination $VPN_SERVER # Remove forwarding rules iptables --delete $FORWARD_CHAIN \ --destination $VPN_SERVER --protocol gre --match state --state NEW \ --jump $FILTER_CHAIN iptables --delete $FORWARD_CHAIN \ --destination $VPN_SERVER \ --protocol tcp --match tcp --dport 1723 --match state --state NEW \ --jump $FILTER_CHAIN # Flush and remove filter chain iptables --flush $FILTER_CHAIN iptables --delete-chain $FILTER_CHAIN # Flush and remove logaccept chain iptables --flush $LOGACCEPT_CHAIN iptables --delete-chain $LOGACCEPT_CHAIN # Flush and remove logdrop chain iptables --flush $LOGDROP_CHAIN iptables --delete-chain $LOGDROP_CHAIN echo "Stopped VPN filter" } # Add IP to filter (i.e., allow VPN from this IP) add() { add_rule $1 echo $1 >> $FILTER_FILE echo "Added $1" } # Remove IP from filter remove() { iptables --delete $FILTER_CHAIN \ --source $1 \ --jump $LOGACCEPT_CHAIN egrep -v "^$1\$" $FILTER_FILE > $TMP_FILE cp $TMP_FILE $FILTER_FILE echo "Removed $1" } # List currently allows IPs list() { cat $FILTER_FILE } # Print usage and exit usage() { echo "Usage: $0 {start|stop|add <ip>|remove <ip>|list}" exit 1 } # # Main body # # Check for proper usage [ $# -lt 1 ] && usage # Clean IP address to avoid security risks, if provided if [ $# -eq 2 ]; then IP_ADDRESS=`echo "$2" | sed s/[^0-9.]//g` [ ${#IP_ADDRESS} -gt 15 ] && usage fi # Do stuff case $1 in start) [ $# -ne 1 ] && usage start ;; stop) [ $# -ne 1 ] && usage stop ;; add) [ $# -ne 2 ] && usage add $IP_ADDRESS ;; remove) [ $# -ne 2 ] && usage remove $IP_ADDRESS ;; list) [ $# -ne 1 ] && usage list ;; *) usage ;; esac