Blocking of domain strings in iptables

David Ford david at blue-labs.org
Sat Feb 8 16:59:05 UTC 2014


I implemented this easily some time ago due to a situation where product
development was unable or unwilling to disable open resolvers.

i'll post my ruleset then describe it then describe it since it contains
multiple functions.

    Chain INPUT (policy ACCEPT 68M packets, 4377M bytes)
     pkts bytes target        prot opt in     out    
    source               destination        
      22M 1423M ACCEPT        all  --  lo     *      
    0.0.0.0/0            0.0.0.0/0          
        0     0 REJECT        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            recent: CHECK name:
    blacklist side: source reject-with icmp-admin-prohibited
      34M 2463M find_dnsany   udp  --  *      *      
    0.0.0.0/0            0.0.0.0/0            udp dpt:53

    Chain FORWARD (policy ACCEPT 460M packets, 298G bytes)
     pkts bytes target        prot opt in     out    
    source               destination        
        0     0 REJECT        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            recent: CHECK name:
    blacklist side: source reject-with icmp-admin-prohibited
        0     0 irc           tcp  --  *      eth0   
    0.0.0.0/0            0.0.0.0/0            multiport dports
    6660:6669,6670
    1826M 1144G local_ips     all  --  *      *      
    0.0.0.0/0            0.0.0.0/0          
    35387 2569K find_dnsany   udp  --  *      *      
    0.0.0.0/0            0.0.0.0/0            udp dpt:53

    Chain OUTPUT (policy ACCEPT 39M packets, 316G bytes)
     pkts bytes target        prot opt in     out    
    source               destination        
        0     0 irc           tcp  --  *      eth0   
    0.0.0.0/0            0.0.0.0/0            multiport dports
    6660:6669,6670
      22M 1423M ACCEPT        all  --  *      lo     
    0.0.0.0/0            0.0.0.0/0
     310M 1637G local_ips     all  --  *      *      
    0.0.0.0/0            0.0.0.0/0
      13M 1056M CONNMARK      udp  --  *      *      
    0.0.0.0/0            0.0.0.0/0            udp dpt:53 owner UID match
    25 CONNMARK set 0x35
      13M 1056M find_dnsany   udp  --  *      *      
    0.0.0.0/0            0.0.0.0/0            udp dpt:53

    Chain find_dnsany (3 references)
     pkts bytes target        prot opt in     out    
    source               destination
     302K   19M limit_dnsany  all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            u32
    "0x0>>0x16&0x3c at 0x8>>0xf&0x1=0x0&&0x0>>0x18&0x1=0x1" STRING match 
    "|0000ff0001|" ALGO name bm FROM 36 TO 70 /* match ANY? queries */

    Chain irc (2 references)
     pkts bytes target        prot opt in     out    
    source               destination
        0     0 ULOG          all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            ULOG copy_range 0 nlgroup
    30 queue_threshold 1
        0     0 LOG           all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            LOG flags 8 level 4 prefix
    "[IRC] "
        0     0 REJECT        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            reject-with
    icmp-admin-prohibited

    Chain limit_dnsany (1 references)
     pkts bytes target        prot opt in     out    
    source               destination
      827 53727 ACCEPT        all  --  *      *       1.2.3.4          
       0.0.0.0/0            limit: avg 20/min burst 60
        0     0 limit_venet   all  --  *      *       1.2.3.4     
            0.0.0.0/0
     4297  302K ACCEPT        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            CONNMARK match  0x35
    limit: avg 10/min burst 30
    22798 1475K ACCEPT        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            limit: avg 4/min burst 10
     7277  468K LOG           all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            limit: avg 1/min burst 5
    LOG flags 0 level 4 prefix "DNSANY: "
     279K   18M DROP          all  --  *      *      
    0.0.0.0/0            0.0.0.0/0

    Chain limit_venet (1 references)
     pkts bytes target        prot opt in     out    
    source               destination
        0     0 LOG           all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            limit: avg 1/min burst 5
    LOG flags 0 level 4 prefix "DNSANYint: "
        0     0 REJECT        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            reject-with
    icmp-admin-prohibited

    Chain local_ips (2 references)
     pkts bytes target        prot opt in     out    
    source               destination
    2136M 2782G RETURN        all  --  *      !eth0  
    0.0.0.0/0            0.0.0.0/0            /* only check outgoing
    packets */
        0     0 RETURN        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            ADDRTYPE match src-type
    LOCAL /* accept packet generated from any locally bound IP */
        0     0 RETURN        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            recent: CHECK name:
    local_ips side: source
        0     0 ULOG          all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            ULOG copy_range 0 nlgroup
    30 queue_threshold 1
        0     0 LOG           all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            limit: avg 1/min burst 5
    /* block non-local IPs from exiting */ LOG flags 8 level 4 prefix
    "SPOOF: "
        0     0 REJECT        all  --  *      *      
    0.0.0.0/0            0.0.0.0/0            reject-with
    icmp-admin-prohibited



First, INPUT, FORWARD, and OUTPUT are very similar.
1) INPUT accepts all /lo/ traffic as does OUTPUT
2) INPUT and FORWARD auto block any IPs admins put into the
//proc/net/ipt_recent/blacklist/ (or //proc/net/xt_recent/blacklist/
depending on kernel version)
3) common IRC port traffic is blocked (insane number of bots use these
ports)
4) outgoing IPs are matched to interface IPs. this set of rules are used
for when the kernel is too old to employ
//proc/sys/net/ipv4/conf/all/rp_filter/ mode against forwarded IPs too
(prevents spoofing)
5) OUTPUT tags DNS traffic owned by uid 25 (usually for sendmail,
postfix, etc, change accordingly)
6) in order to prevent killing our box with syslog when an attack
happens, logging is strictly rate limited

now on to the DNS specific stuff
1) each of INPUT, FORWARD, and OUTPUT call /find_dnsany/ to identify our
suspect traffic
2) the rule in /find_dnsany/ uses a u32 match rule to first identify DNS
traffic that is a query, and a string match to identify the ANY flag,
further, we try to make this efficient by limiting our match to the byte
range of 36 to 70. we've found that 100% of our DNS amplification
attacks request zone data that fits within this. it certainly may be a
longer zone but is unlikely. change accordingly
3) once DNS ANY queries have been identified, jump to /limit_dnsany/
4) /limit_dnsany/ is designed to accept packets until a limit is
reached. there are three limits employed:
4.1) customers in a VZ or bound to a specific local IP that may have
higher than normal rates of legitimate DNS ANY queries
4.2) our local smtp initiated lookups. qmail is horrible in that it
employs DNS ANY (broken design, discussion out of scope)
4.3) all other DNS ANY traffic
5) incoming DNS ANY that is rate limited is DROPped on the floor, rate
limited outgoing is REJECted so internal customers get a friendly icmp
reject message

additional notes:
1) outgoing should also be checked against the blacklist, somehow our QA
dropped that rule before disting
2) IRC is checked before local traffic to ensure two infected customers
aren't communicating with each other

this set of rules reliably filters:
1) incoming and outgoing DNS QUERY floods. incoming is rate limited to
4/min per IP source. forwarded and outgoing has higher limits
2) majority of bot traffic on IRC ports
3) spoofed and blacklisted packets


-david


On 02/08/2014 03:34 AM, Jonathan Lassoff wrote:
> This is going to be tricky to do, as DNS packets don't necessarily contain
> entire query values or FQDNs as complete strings due to packet label
> compression (remember, original DNS only has 512 bytes to work with).
>
> You can use those u32 module matches to find some known-bad packets if
> they're sufficiently unique, but it simply lacks enough logic to fully
> parse DNS queries.
> Here's an interesting example to visualize what's happening:
> http://dnsamplificationattacks.blogspot.com/p/iptables-block-list.html
>
> One quick thing that would work would be to match a single label (e.g.
> "google", but not "google.com"), but this will end up blocking any frames
> with that substring in it (e.g. you want to block "evil.com", but this also
> blocks "evil.example.com").
>
> If you find yourself needing to parse and block DNS packets based on their
> content in a more flexible way, I would look into either making an iptables
> module that does the DNS parsing (
> http://inai.de/documents/Netfilter_Modules.pdf), or using a userspace
> library like with NFQUEUE (e.g. https://pypi.python.org/pypi/NetfilterQueue)
> or l7-filter (http://l7-filter.sourceforge.net/).
>
> Best of luck and happy hacking!
>
> Cheers,
> jof
>
>
>
> On Sat, Feb 8, 2014 at 12:08 AM, Anurag Bhatia <me at anuragbhatia.com> wrote:
>
>> Hello everyone
>>
>>
>> I am trying to figure out the way to drop a domain name DNS resolution
>> before it hits application server. I do not want to do domain to IP mapping
>> and block destination IP (and source IP blocking is also not an option).
>>
>> I can see that a string like this:
>>
>> iptables -A INPUT -p udp -m udp --dport 53 -m string --string "domain"
>> --algo kmp --to 65535 -j DROP
>>
>>
>> this can block "domain" which includes domain.com/domain.net and
>> everything
>> in that pattern. I tried using hexadecimal string for value like domaincom
>> (hexa equivalent) and firewall doesn't pics that at all.
>>
>> The only other option which I found to be working nicely is u32 based
>> string as something suggested on DNS amplification blog post here -
>>
>> http://dnsamplificationattacks.blogspot.in/2013/12/domain-dnsamplificationattackscc.html
>>
>>
>> A string like this as suggested on above link works exactly for that domain
>>
>> iptables --insert INPUT -p udp --dport 53 -m u32 --u32
>> "0x28&0xFFDFDFDF=0x17444e53 && 0x2c&0xDFDFDFDF=0x414d504c &&
>> 0x30&0xDFDFDFDF=0x49464943 && 0x34&0xDFDFDFDF=0x4154494f &&
>> 0x38&0xDFDFDFDF=0x4e415454 && 0x3c&0xDFDFDFDF=0x41434b53 &&
>> 0x40&0xFFDFDFFF=0x02434300" -j DROP -m comment --comment "DROP DNS Q
>> dnsamplificationattacks.cc"
>>
>>
>> but here I am not sure how to create such string out and script them for
>> automation.
>>
>>
>>
>> Can someone suggest a way out for this within IPTables or may be some other
>> open source firewall?
>>
>>
>> Thanks.
>>
>> --
>>
>>
>> Anurag Bhatia
>> anuragbhatia.com
>>
>> Linkedin <http://in.linkedin.com/in/anuragbhatia21> |
>> Twitter<https://twitter.com/anurag_bhatia>
>> Skype: anuragbhatia.com
>>
>> PGP Key Fingerprint: 3115 677D 2E94 B696 651B 870C C06D D524 245E 58E2
>>



More information about the NANOG mailing list