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:
doit(void) { char line[64], *sp; /* Read the first line from stdin */ if (gets(line) == NULL) return; /* Zap trailing newline if present */ if ((sp = strchr(username, '\n')) != NULL) *sp = '\0'; display(line); return; }
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) return; ... 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 .