next up previous
Next: Binary Representation Protocols Up: Presentation Layer Issues Previous: Presentation Layer Issues

Text Based Protocols

Different network applications have different methods of encapsulating data. One method very common with Internet protocols is a text oriented representation that transmits requests and responses as lines of ASCII text, terminated by a newline character (and usually a carriage return character). Typical examples are FTP (File Transfer Protocol), SMTP (Simple Mail Transfer Protocol), or the finger protocol.

The finger service lets you obtain status information about a user account on a remote host, such as the time of the last login, or the terminals the user is currently logged in on. On one hand, the service is in fact infamous for several reasons; one being that it is considered as giving away too much local information to potential attackers. In addition, it had a security bug that was abused in what was probably the first widely publicized Internet security incident: the RTM Worm, named after its author, Robert T. Morris Jr.

The finger protocol works like this: the client sends a single line of text containing the name of the user to be queried. The server responds with one or more lines of status output. Which sounds simple enough.

Now the bad thing about these old implementations of the finger server (also known as fingerd, pronounced finger-dee) was that it read the client request using the gets function. The function's prototype looks like this:

    char *     gets(char *buffer);

When invoked, the function will read characters from the process' standard input and store them in buffer, up to the first newline, or the end of file, whichever it encounters first. Sadly, it doesn't give a damn about how big the buffer is. In fact, it cannot even know how big it is, because there's no argument through which the caller can pass the buffer's maximum capacity.7.1

To make a long story short, the old fingerd code looked roughly like this:

    char    line[64], *sp;

    /* Read the first line from stdin */
    if (gets(line) == NULL)
    /* Zap trailing newline if present */
    if ((sp = strchr(username, '\n')) != NULL)
        *sp = '\0';

So an attacker sending 128 characters of text to the finger server could easily overflow the line buffer. We've discussed the mechanics of buffer overflows and how they can be exploited in chapter [*] to some extent. Suffice it to say here that the RTM worm did exactly that: exploit this buffer overflow and take over the fingerd process. In fact, the worm was so aggressive that the traffic caused by it brought the entire Internet to a complete stand-still.7.2

So how can this be done correctly? In all cases I've seen, it's enough to replace the call to gets with a call to fgets, which takes a buffer size parameter. fgets will never write more characters than the buffer is able to hold. All it takes to fix the buffer overflow bug above is to use the following instead of the gets call:

    if (fgets(line, sizeof(line), stdin) == NULL)
    ... and so on ...

While we're on the topic of fingerd, there was a second security glitch, which is not a presentation layer issue but still worth mentioning. fingerd was usually installed to run as the super user, so anyone exploiting the buffer overflow would immediately obtain maximum privilege on the server machine. However, fingerd doesn't need root privilege in order to do what it's supposed to do! Even when run as some completely unprivileged user such as nobody, it would still be able to display a user's status. So if fingerd had been configured to run as a non-privileged user, the buffer overflow would still have given the attacker a shell account on the victim host, but with a far lower impact. Making sure that a network server doesn't run with a higher privilege than absolutely required is called principle of least privilege. We will discuss this issue in greater detail in chapter [*].

next up previous
Olaf Kirch 2002-01-16