IPv6 Focus Month: The warm and fuzzy side of IPv6

Published: 2013-03-19
Last Updated: 2013-03-20 03:33:10 UTC
by Johannes Ullrich (Version: 1)
1 comment(s)

Protocols like IPv6 and IPv4 suffer from two very different types of security issues: Oversights in the specification of the protocol and implementation errors. The first one is probably the more difficult one to fix as it may require changing the protocol itself and it may lead to incompatible implementations. The second one isn't easy to avoid, but at least we do have some decent tools to verify the correct implementation of the protocol. In implementing protocols, developers usually try to stick to the specifications, and implement the "robustness principle" (RFC 1122) which is sometimes also referred to as Postel's law after Jon Postel. In short, the principle stipulates that a protocol implementation should stick close to the specification in sending data, but should be very forgiving in accepting data. This principle makes robust interoperability possible, but also leads to many security issues. For example, in many cases an IDS may not consider data because it is "out of spec" but the host will still accept it because it will try to make things work. Or on the other hand, an IDS may consider a host to be more forgiving then it actually is. 

What we need is techniques and tools to check the implementation and push the boundaries of what the specification considers acceptable. This method of security testing is usually referred to as "Fuzzing", and one great tool to implement it for IPv6 is scapy. Scapy used to have an add on, scapy6, that implmeneted IPv6. However, recent versions of scapy include scapy6 as part of the tool.

So what can we do? Lets start with something straight forward: A simple TCP packet. In scapy, we first build an IPv6 header, then attach a TCP header. Here we keep it as simple as possible:

# scapy
Welcome to Scapy (2.2.0)
>>> ip=IPv6(dst="2001:db8::1");
>>> tcp=TCP(sport=32666,dport=80,flags=S);
>>> sr1(ip/tcp) Begin emission: Finished to send 1 packets. Received 293 packets, got 1 answers, remaining 0 packets <Pv6 version=6L tc=0L fl=0L plen=24 nh=TCP hlim=57 src=2001:db8::1 |<TCP sport=http dport=32666 seq=3689474164 ack=1 dataofs=6L reserved=0L flags=SA window=5680 chksum=0xaab6 urgptr=0 options=[('MSS', 1420)] |>>

Cool. We send a SYN packet, and got a SYN-ACK back! All normal and as expected. First rule of fuzzing: Start with something simple and normal that you know works.
Next, lets set a neat little extension header: A "Hop-by-Hop" header, indicating that we got a "jumbogram". But, our jumbogram is nasty. Instead of making it big as it is supposed to, we make it of size 0. We start like above, but the we add an hop-by-hop header:
 
>>> hbh=IPv6ExtHdrHopByHop(nh=59,len=0,options=Jumbo(jumboplen=0));
>>> sr1(ip/hbh/tcp);
Begin emission:
Finished to send 1 packet.
 
And as expected, we do not get a repsonse. To verify, it helps collecting to keep tcpdump running to collect some packets:
 
# tcpdump -i en0 -nn -tvv ip6 and host 2001:db8::1
IP6 (hlim 64, next-header Options (0) payload length: 28) 2001:db8::2 > 2001:db8::1: HBH (jumbo: 0) no next header
 
One thing that scapy "fixed" for us is the payload length, It should be 0 for a jumbogram. No problem... We can tell scapy to set it to 0.

>>> ip=IPv6(dst="2001:db8::1",plen=0); >>> sr1(ip/hbh/tcp);

and again no response.

So this was prety simple. Next step: Lets do a 3 way handshake. Instead of pasting the script here, I uploaded a simple IPv6 3-way TCP handshake here. The script will setup a TCP connection to port 80, then transmit a simple HTTP request in two segments. Again: We start simple. This should work.

Next, lets be a bit evasive. We will retransmit the second segment, but the second segment contains a different content. The full script can be found here. The interesting part: 

my_payload2="sec546.com "
my_payload3="secxxx.com "
TCP_PUSH=TCP(sport=sport,dport=dport, flags="PA", seq=isn+1,ack=my_ack)
send(ip/TCP_PUSH/my_payload1)
TCP_PUSH=TCP(sport=sport,dport=dport, flags="PA", seq=isn+1+len(my_payload1),ack=my_ack)
send(ip/TCP_PUSH/my_payload2)
send(ip/TCP_PUSH/my_payload3)
 
In this case, the second payload will get ignored. This can be confirmed easily if the target web server logs the host name as received from the client. You will see that the target server received "sec546.com".
Next, we can add a destination header into the mix. We only add the destination header to the first copy ("payload2"). The destination header is constructed such that it will cause the packet to be dropped. As a result, only the second copy of the segment will get used. The full script can be found here The "diff" again:
 
DH=IPv6ExtHdrDestOpt(options=HBHOptUnknown(otype=255,optdata='x'))
send(ip/DH/TCP_PUSH/my_payload2)
send(ip/TCP_PUSH/my_payload3)
 
"payload2" will be ignored, but "payload3" will be received just fine. As a result, the web server will respond with "secxxx.com", not "sec546.com". But how do our packet tools reassemble this kind of traffic? You can find a packet capture here to try your own favorite tool. Let me know what you find!
 
------

Johannes B. Ullrich, Ph.D.
SANS Technology Institute
Twitter

1 comment(s)

Comments

Great post Johannes. You may also be interested in Antonios Atlasis' work on IPv6 Extension Headers and new attack vectors opened by them. He constructed his demos using scapy scripts.

Latest presentation: https://www.ernw.de/download/IPv6%20Extension%20Headers%20-%20New%20Features,%20and%20New%20Attack%20Vectors.pdf

Scapy scripts: https://www.ernw.de/download/IPv6%20Extension%20Headers%20-%20New%20Features,%20and%20New%20Attack%20Vectors.py

Diary Archives