DNS Tunnel Mitigation v2

Problem this snippet solves:

(Solution from Pedro Haoa)


Due to some people attempt DNS tunneling to pass data frames inside of DNS records to the Internet and the lack of information around here, I'm going to share with you some basic code for DNS Tunnel Mitigation on the BIG-IPs. This irule put some overhead in your CPU so check it with caution.

The idea is to improve this code (I'm looking for less overhead) here in DevCentral and try to build a better base solution for the most common techniques. You can use the DNS Protocol Security, DNS Anti-DDoS and IP Intelligence features to get the most comprehensive solution.

Remember that there are a lot of DNS Tunneling utilities with a wide range of capabilities and options, so this is one of many forms to mitigate some of the attacks.

How to use this snippet:

LTM + AFM + DNS Services


BIG-IP AFM (Protocol Security)


In Security ›› Protocol Security : Security Profiles : DNS ››


Create a New Security Profile and exclude obsolete record types like MD, MF, MAILA, NULL, HINFO, SPF, etc. Then apply to your DNS profile associated with your Listener.


BIG-IP DNS (LTM Data Groups and iRule)


Creating DNS Tunnel Query type Data Group


BIG-IP AFM (Protocol Security)


In Security ›› Protocol Security : Security Profiles : DNS ››


Create a New Security Profile and exclude obsolete record types like MD, MF, MAILA, NULL, HINFO, SPF, etc. Then apply to your DNS profile associated with your Listener.


BIG-IP DNS (LTM Data Groups and iRule)


Creating DNS Tunnel Query type Data Group

create ltm data-group internal TunnelType records replace-all-with { CNAME { } } type string
modify ltm data-group internal TunnelType records add { TXT { } }
modify ltm data-group internal TunnelType records add { SRV { } }
modify ltm data-group internal TunnelType records add { KEY { } }


Creating Whitelist Data Group


create ltm data-group internal Dominios_Lista_Blanca records replace-all-with { facebook.com { data facebook.com } } type string
modify ltm data-group internal Dominios_Lista_Blanca records add { instagram.com { data instagram.com } }
modify ltm data-group internal Dominios_Lista_Blanca records add { fbcdn.net { data fbcdn.net } }
modify ltm data-group internal Dominios_Lista_Blanca records add { google.com { data google.com } }
modify ltm data-group internal Dominios_Lista_Blanca records add { googleapis.com { data googleapis.com } }


Creating Blacklist Data Group


create ltm data-group internal Dominios_Lista_Negra records replace-all-with { dnstunnel.de { data dnstunnel.de } } type string
modify ltm data-group internal Dominios_Lista_Negra records add { cutheatergroup.cn { data cutheatergroup.cn } }
modify ltm data-group internal Dominios_Lista_Negra records add { demodomain.cz { data demodomain.cz } }
modify ltm data-group internal Dominios_Lista_Negra records add { buo.cc { data buo.cc } }
modify ltm data-group internal Dominios_Lista_Negra records add { pdk.lcn.cc { data pdk.lcn.cc } }


Code :

when RULE_INIT {
    # Max DNS queries during detection period per source IP / destination domain
    set static::maxq 180
    # Detection & Blocking Period
    set static::btime 60
}
when DNS_REQUEST {
    set srcip [IP::remote_addr]
    set qtype [DNS::question type]
    set DomOrigen [domain [DNS::question name] 4]
    set key "$srcip:$DomOrigen"
if { ([class match $qtype equals TunnelType]) and [DNS::len] > 512 } {
    if {[class match $DomOrigen ends_with Dominios_Lista_Blanca] }{
        return
    } elseif {[class match $DomOrigen ends_with Dominios_Lista_Negra] }{
        DNS::drop
        return
    } elseif {[table lookup $key] ne ""} {
        set count [table incr $key]
        if {$count > $static::maxq} {
            DNS::drop
            return
        }
    } else {
        table add $key 1 indef $static::btime
        }
    }
}

Tested this on version:

No Version Found
Published Jun 19, 2019
Version 1.0

Was this article helpful?

6 Comments

  • Thanks

    But I dont understand " why [DNS::len] > 512 and domain [DNS::question name] 4 "

     

    Plz explain help me

     

    Thanks

    Hung Hoang

  • I didn't write the rule, so I can only speculate. But they are using the domain command to take only the last 4 sections of a dotted FQDN to include with the src IP as a key in the table memory for counting queries. I am not sure with the len if that is being set as a control for it actually being legit tunnel traffic, but too much, or if anything less than that size would be illegitimate.

  • Thanks Jason

    Do you known tool using to Simulation Tunneling attack via F5 ?

  • Hi,

    It's been a while since my last post on Devcentral. And in order to help resolve the above doubts, I share the following:

    1. [DNS::len] > 512 and domain [DNS::question name] 4 are filters to improve the performance of the solution. Both are adaptable to the particular needs of each customer environment.

    2. 512 is a good starting point to analyze DNS messages and any below that will not be processed by the iRule. Also it's aligned with RFC 7766 in order to switch to TCP for messages whose sizes exceed the DNS protocol's original 512-byte limit.

    3. [DNS::question name] 4 is a good starting point for the domain name entropy. The larger this number is, the more granular and effective the protection will be, but the size of the table can grow very quickly, worsening iRule performance.

  • A tool for testing purposes that I used very long time ago was Iodine.

  • And now in 2023 using ChatGPT as an iRules assistant:

    when RULE_INIT {
        # Max DNS queries during detection period per source IP / destination domain
        set maxq 180
        # Detection & Blocking Period
        set btime 60
    }
    
    when DNS_REQUEST {
        set srcip [IP::remote_addr]
        set qtype [DNS::question type]
        set domain [domain [DNS::question name] 4]
        set key "$srcip:$domain"
    
        if {[DNS::len] > 512 && [class match $qtype equals TunnelType]} {
            switch $domain {
                "ends_with Dominios_Lista_Blanca" {
                    return
                }
                "ends_with Dominios_Lista_Negra" {
                    DNS::drop
                    return
                }
                default {
                    if {[table lookup $key] ne ""} {
                        set count [table incr $key]
                        if {$count > $maxq} {
                            DNS::drop
                            return
                        }
                    } else {
                        table add $key 1 indef $btime
                    }
                }
            }
        }
    }

    Some of the changes include:

    The use of switch for a more concise and readable evaluation of the $domain variable.
    Replace static with local variables to improve clarity and reduce redundancy.
    Rename the sourceDom variable to domain for better readability.

    Cheers!