Technic Blog

Technik BlogEgress Traffic Filter and FQDN in Firewall ACLs

21. July 2017 Max Fruth

Egress Traffic Filtering

Egress filtering is the control of traffic leaving your network. Far too often firewall administrators create a policy rule that essentially says let my internal network transmit any and all traffic patterns out to the Internet. One reason for this are public internet services with dynamic IPs. The connections to these services are often mandatory for an application and can not be limited by simple IP based filter rules.

But today's IT landscape is littered with threats that originate from endpoints infected with malware or malicious code. Attackers can use these to collect and forward sensitive information from your network or to attack or spam other networks. If one system in your network is compromised, egress filtering limits the damage by preventing this systems to communicate with remote hosts.

Egress filtering prevents you from sending unwanted traffic out to the Internet.

Companies are better served when firewall administrators are equally concerned with threats that are associated with outbound connections. You should be aware that data theft often results from software vulnerabilities or configuration errors. Irrespective of the cause, data piracy is a threat you can’t mitigate without egress traffic enforcement.

However, the configuration of egress IP filters in the age of cloud services with dynamic addressing is not an easy task. Either one has the money for expensive application-layer firewalls or you can configure permit rules for a variety of large network blocks allocated by cloud providers. Especially the latter is rather a bad compromise.

An alternative with some flaws is the configuration of FQDN rules on Layer3 IP firewalls.

FQDN based ACLs

This feature works by resolving the IP of the FQDN via DNS which it then stores within its cache. Traffic is then either denied or permitted accordingly. It is important to understand that FQDN based IP Filtering is not a replacement for URL filtering.

IT Security Concerns

As the DNS protocol is used for FQDN to IP resolution this adds an additional attack vector (i.e cache poisoning attacks etc) to your environment. Because of this it is recommended to use a trusted (i.e internal) DNS server.

Multiple FQDN`s

As you will be aware multiple FQDN`s can reside on a single IP. Meaning that, though you may permit abc.com as xyz.com also resolves to the same IP. You are not only permitting access to abc.com but also xyz.com.

Dynamic DNS

Many DNS servers respond with a single IP at a time, with each subsequent request resulting in different IP address being returned. As you will appreciate this isn't ideal, as it can result in resolving a different IP to the client and traffic being intermittently allowed or denied.

Multi-DNS Resolver or Loadbalanced DNS TTLs

A common DNS search strategy involves multiple DNS resolver clients. The algorithm used is to try a name server, and if the query times out, try the next, until out of name servers, then repeat trying all the name servers until a maximum number of retries are made.

Another method to ensure DNS availability is to present a server farm (pool) of DNS resolvers via a single IP address. For each DNS query that is sent, the request is loadbalanced across the pool of DNS resolvers. Based on this and also that the TTL value returned from a DNS resolver is the TTL value from its cache (where as an authoritative servers which provides the TTL set on the actual record).

Why does this matter ? Lets consider the following scenario,

  • The Fireall queries DNS for xyz.com.
  • The request is sent to the first listed DNS resolver or to a loadbalanced pool of DNS resolvers. DNS resolver A responds with an answer of 1.1.1.1 with a TTL of 30 seconds.
  • The client then queries DNS for xyz.com.
  • The request is sent to another listed DNS resolver or a loadbalanced pool of DNS resolvers. DNS resolver B responds with an answer of 1.1.1.1 with a TTL of 3600 seconds.
  • The DNS entry on DNS resolver A then expires 30 secs later.
  • The Firewall then queries DNS for xyz.com again. DNS resolver A responds with an answer of 2.2.2.2 because the IP changed in the meanwhile. But B would respond with 1.1.1.1 because the record is not yet expired in the cache.
  • So it depends on the selected resolver if traffic will be incorrectly (depending on the ACL action) permitted or denied.

For this reason, the way in which firewalls make use of the 'expiry timeout value' is good to know. The 'expiry timeout value' defines the additional time that the Firewall will wait once the original TTL has expired, before it removes the entry from its cache.

You should also look to configure special DNS Resolver without caching. But be aware of increased response times for your DNS queries when you bypass caching.

Unreachable DNS Server

There are times when the DNS server is unreachable. Should this occur, and the Firewall is unable to resolve the IP of the FQDN then the ACL should be marked as 'unresolved'.

A basic implementation for host firewalls based on IPTABLES

The following scripts show how FQDN egress rules can be implemented under RHEL 7 with iptables.
The wrapper script could be called by cron e.g. every minute.
  • #!/bin/bash
    #
    # AUTHOR:   Max Fruth
    # VERSION:  1.0
    # HISTORY:  23.09.2015 intitial creation
    #
    # DESCRIPTION:
    # wrapper script to set OUTBOUND FQDN allow rules. Destinations are defined in a 
    # config file. The file path of this config file is required as argument.
    # This feature works together with iptables and firewalld and was tested under RHEL 7.
    #
    
    function log
    {
      SYSLOG="kern.debug"
      if [ "$1" == "SCREEN" ]; then
        echo "$2"
      fi
      if [ "$1" == "SYSLOG" ]; then
        logger -p $SYSLOG -t $SCRIPT "$2"
      fi
    }
    
    function usage
    {
      MSG=$1
      log SCREEN "$MSG"
      log SCREEN "usage: $0 "
      exit 1
    }
    
    if [ $# -ne 1 ]; then
      usage "wrong argument count"
    fi
    
    CONFIG=$1
    CMD="/usr/local/sbin/firewalld_domain_rule.sh"
    
    if [ ! -f  $CONFIG ]; then
      log SCREEN "firewall config file [$CONFIG] does not exist"
      exit 1
    fi
    
    source $CONFIG
    
    if [ ! -z "$OUTBOUND_DYNAMIC" ]; then
      RULES=(${OUTBOUND_DYNAMIC//$/ })
      for i in "${!RULES[@]}"
      do
        TMP=${RULES[$i]} ;
        RULE=(${TMP//|/ })
        $CMD OUTPUT ${RULE[0]} ${RULE[1]} ${RULE[2]}
      done
    fi
    
    if [ ! -z "$INPUT_DYNAMIC" ]; then
      RULES=(${INPUT_DYNAMIC//$/ })
      for i in "${!RULES[@]}"
      do
        TMP=${RULES[$i]} ;
        RULE=(${TMP//|/ })
        $CMD INPUT ${RULE[0]} ${RULE[1]} ${RULE[2]}
      done
    fi
    
    • #!/bin/bash
      #
      # AUTHOR:   Max Fruth / Tobias Maier
      # VERSION:  1.0
      # HISTORY:  10.01.2017 intitial creation
      #
      # DESCRIPTION:
      # firewalld_domain_rule.sh
      # configure FQDN accept rules in iptables.
      #
      
      function log
      {
        SYSLOG="kern.debug"
      
        if [ "$1" == "SCREEN" ]; then
          echo "$2"
        fi
        if [ "$1" == "SYSLOG" ]; then
          logger -p $SYSLOG -t $SCRIPT "$2"
        fi
      }
      
      function log_msg
      {
        if [ -t 1 ]
        then
          # stdout is a terminal
          log SCREEN "$1"
        else
          # stdout is not a terminal
          log SYSLOG "$1"
        fi
      }
      
      function usage
      {
        log_msg "$1"
        if [ -t 1 ]
        then
          # show usage if stdout is a terminal
          log SCREEN "usage: $0 (INPUT|OUTPUT) DOMAIN (TCP|UDP) PORT"
        fi
        exit 1
      }
      
      IPSET=/usr/sbin/ipset
      SCRIPT=$0
      CHAIN=$1
      HOST=$2
      PORT=$4
      
      # Expiry Timeout
      TIMEOUT=7200
      
      if [ $# -ne 4 ]
      then
        usage "invalid number of arguments"
      fi
      
      if [ "$1" != "INPUT" -a "$1" != "OUTPUT" ]
      then
        usage "invalid argument '$1'"
      fi
      
      PROTOCOL=`echo $3 | awk '{print tolower($0)}'`
      if [ "$PROTOCOL" != "tcp" -a $PROTOCOL != "udp" ]
      then
        usage "invalid argument '$3'"
      fi
      IPS=`host $HOST | awk '/has address/ {print $NF}' | egrep -e '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'`
      if [ -z "$IPS" ]; then
        log_msg "lookup for host $HOST returns empty string"
        exit 1
      fi
      
      if [ "$CHAIN" == "OUTPUT" ]
      then
        setname="dynamic-out"
        table=filter
        chain=OUTPUT_allow
        priority=0
        #args="-p $PROTOCOL -m conntrack --ctstate NEW -m set --match-set $setname dst,dst -j ACCEPT"
        args="-m conntrack --ctstate NEW -m set --match-set $setname dst,dst -j ACCEPT"
      
        # assure netplace chains are installed
        if ! firewall-cmd --direct --query-chain ipv4 $table $chain >/dev/null
        then
          log_msg "failed to add dynamic rule, netplace chains not installed"
          exit 1
        fi
      
        # assure used ipset exists
        if ! $IPSET list -name $setname >/dev/null 2>&1
        then
          # create ipset
          $IPSET create $setname hash:ip,port timeout $TIMEOUT
        fi
      
        # assure required firewalld rule exists
        if ! firewall-cmd --direct --query-rule ipv4 $table $chain $priority $args >/dev/null
        then
          # add firewalld direct rule
          firewall-cmd --direct --add-rule ipv4 $table $chain $priority $args >/dev/null
        fi
      
        for ip in $IPS
        do
          # push ip on ipset
          $IPSET -exist add $setname "$ip,$PROTOCOL:$PORT" timeout $TIMEOUT
        done
      else
        log_msg "chain '$CHAIN' not yet implemented"
        exit 1
      fi
      
      • OUTBOUND_DYNAMIC="
          downloads.wordpress.org|tcp|443
          downloads.wordpress.org|tcp|80
          api.wordpress.org|tcp|443
          api.wordpress.org|tcp|80
          piwik.org|tcp|80
          graph.facebook.com|tcp|443
        "