November 27, 2014
Hot Topics:

Secure Design Principles

  • March 23, 2007
  • By Neil Daswani, Christoph Kern, & Anita Kesavan
  • Send Email »
  • More Articles »

Fail-Safe Stance

Fail-safe stance involves designing a system in such a way that even if one or more components fail, you can still ensure some level of security. In the physical world, there are many systems that take this type of stance. One example involves how an elevator behaves when the power goes out. When elevators lose power or other types of failures occur, they have the capability to automatically grab and latch onto the cables that support them, or use safeties to grab the guide rails on the sides of the elevator shaft, if necessary. Elevators are designed with the expectation that the power will sometimes fail. Software should similarly be designed with the expectation that things will fail.

For example, a firewall is designed to keep malicious traffic out. If a firewall ever fails, it should deny access by default and not let any traffic in. This will be inconvenient for users, but at least the information system protected by the firewall will not be insecure. If, on the other hand, the firewall fails and decides to let all traffic through, attackers could figure out how to induce the firewall to fail, and then would be able to send malicious traffic in. If the firewall is instead designed to let no traffic in upon failure, attackers would not have any additional incentive (besides that of conducting a DoS attack) to try to get the firewall to fail.

SimpleWebServer Fail-Safe Example

We now show that the implementation of the serveFile() method takes a fail-safe stance. The implementation of serveFile() is repeated in the following code for convenience:

85           public void serveFile (OutputStreamWriter osw,
86                                              String pathname) throws Exception {
87                         FileReader fr = null;
88                          int c = -1;
89                          StringBuffer sb = new StringBuffer();
90
91                           /* Remove the initial slash at the beginning
92                               of the pathname in the request. */
93                          if (pathname.charAt(0) == '/')
94                                        pathname = pathname.substring(1);
95
96                          /* If there was no filename specified by the
97                              client, serve the "index.html" file. */
98                         if (pathname.equals(""))
99                                       pathname = "index.html";
100
101                        /* Try to open file specified by pathname. */
102                        try {
103                                     fr = new FileReader (pathname);
104                                     c = fr.read();
105                        }
106                        catch (Exception e) {
107                                        /* If the file is not found, return the
108                                            appropriate HTTP response code. */
109                                       osw.write ("HTTP/1.0 404 Not Foundnn");
110                                       return;
111                        }
112
113                        /* If the requested file can be successfully opened
114                           and read, then return an OK response code and
115                           send the contents of the file. */
116                       osw.write ("HTTP/1.0 200 OKnn");
117                       while (c != -1) {
118                                    sb.append((char)c);
119                                    c = fr.read();
120                       }
121                       osw.write (sb.toString());
122         }

SimpleWebServer takes a fail-safe stance. If an attacker can force the web server to run out of memory, it will crash, but it will not do something insecure such as skipping an access control check or serving any document requested. How can the attacker force the web server to run out of memory?

Note that the way that the preceding serveFile() method works is that it uses a StringBuffer object (line 89) to store the contents of the file request prior to sending the data in the file to the client. Lines 117 to 120 load the contents of the file into the StringBuffer, and line 121 outputs all the content accumulated by the StringBuffer to the OutputStreamWriter object. In writing the code as such, the programmer assumes that the file is of finite length, and can be loaded in its entirety before it is sent to the client. Many files are of finite length. However, some files, such as live media streams from a web camera, may not be finite, or should at least be served a little bit at a time instead of all at once.

If the attacker can somehow request an infinite-length file, the contents of the file will be put into the StringBuffer until the web server process runs out of memory. While the machine that the web server is running on might not be connected to a web camera, if it is a Linux machine, there is (luckily for the attacker) an infinite-length file that the attacker can use. In Linux, /dev/random is a file that returns random bits that could, for example, be used to generate cryptographic keys. However, an attacker can misuse it as a source of infinite data. For the moment, let us assume that the checkPath() function earlier in this article was not implemented. If the attacker connects to the web server and issues GET //dev/random HTTP/1.0 as an HTTP request, SimpleWebServer will continuously read data from /dev/randomh until the web server runs out of memory and crashes. Even though the web server takes a failsafe stance and crashes when it runs out of memory, it is important to deal with this bug, as it can be used to conduct a DoS attack..

Attempted Fix 1: Checking the File Length

One way to attempt to handle the problem would be for the web server to have a default maximum amount of data to read from the file. Prior to reading data from the file, the web server could determine how much memory it has available, and only decide to serve the file if it has enough memory to read it in. The serveFile() method can be written as follows to implement such a feature:

FileInputStream fr = null;
StringBuffer sb = new StringBuffer();
pathname = checkPath(pathname);
File f = new File (pathname);
if (f.isDirectory()) {
                 // add list of files in directory
                 // to StringBuffer...
}
else {
                 if (f.length() > Runtime.getRuntime().freeMemory()) {
                                  throw new Exception();
                 }
                 int c = -1;
                 fr = new FileReader (f);
                 do {
                                      c = fr.read();
                                      sb.append ((char)c);
                 } while (c != -1);
}

Unfortunately, with the preceding approach, while the intentions are in the right place, it will not prevent an attack in which the adversary places an HTTP request for /dev/random. The reason the preceding code will not solve the problem is because the operating system will report that the length of the file (f.length()) is 0 since /dev/random is a special file that does not actually exist on disk.

Attempted Fix 2: Don't Store the File in Memory

An alternate attempt to correct the problem might involve not having SimpleWebServer store the bytes of the file prior to sending it. The following code will stream the bytes of the file incrementally and significantly save memory:

FileReader fr = null;
int c = -1;

/* Try to open file specified by pathname */
try {
                     fr = new FileReader (pathname);
                     c = fr.read();
}
catch (Exception e) {
                      /* If the file is not found, return the
                          appropriate HTTP response code. */
                      osw.write ("HTTP/1.0 404 Not Found");
                      return;
}

/* If the requested file can be successfully opened
    and read, then return an OK response code and
    send the contents of the file. */
osw.write ("HTTP/1.0 200 OK");
while (c != -1) {
                   osw.write (c);
                   c = fr.read();
}

However, the problem with the preceding approach is that if the attacker requests /dev/random, the server will be forever tied up servicing the attacker's request and will not serve any other legitimate user's request. (Remember, SimpleWebServer is not multithreaded.)

Fix: Don't Store the File in Memory, and Impose a Download Limit

To properly defend against the attack, you can take advantage of the approach in which you do not store the file in memory, and impose a maximum download size. The following code will stream at most MAX_DOWNLOAD_LIMIT bytes to the client before returning from serveFile():

FileReader fr = null;
int c = -1;
int sentBytes = 0;

/* Try to open file specified by pathname */
try {
                     fr = new FileReader (pathname);
                     c = fr.read();
}
catch (Exception e) {
                      /* If the file is not found, return the
                         appropriate HTTP response code. */
                         osw.write ("HTTP/1.0 404 Not Found");
                         return;
}

/* If the requested file can be successfully opened
    and read, then return an OK response code and
    send the contents of the file. */
osw.write ("HTTP/1.0 200 OK");
while ( (c != -1) && (sentBytes < MAX_DOWNLOAD_LIMIT) ) {
                  osw.write (c);
                  sentBytes++;
                  c = fr.read();
}

If the attacker places an HTTP request for /dev/random, the connection to the attacker will be cut off once the server has sent MAX_DOWNLOAD_LIMIT bytes of /dev/random to the client. While the preceding code will defend against the attack, the downside of the preceding implementation is that a legitimate client can receive a truncated file without any warning or indication. As a result, the downloaded file might be corrupted.

In addition, a DoS attack in which the attacker requests a file such as /dev/random will only be somewhat mitigated. We say "somewhat" because if the MAX_DOWNLOAD_LIMIT is relatively high, it may be some time before a legitimate client is able to download a file. Hence, it is important to choose a MAX_DOWNLOAD_LIMIT that is not so low that legitimate download requests will get cut off, but that is not so high that it will allow abusive requests to tie up the server for too long.





Page 3 of 5



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel