I admit it: I have a hard time getting excited about 0-day. Defense against the concept is fascinating (trying to detect exploits for vulnerabilities that haven't been written yet, for example, is way cool). New attacks are important to respond to ASAP, and may change the current wisdom about what's safe, so security geeks (including me) need to pay attention to what's happening. It's just that the details of individual exploits of specific vulnerabilities in specific software are both soporific and depressing: after a while, all the different attacks and vulnerabilities start looking alike. How is it that we are still making the same mistakes, over and over? And how is it that we are continually astonished when the same attack techniques work on the same mistakes, over and over?
In the interests of making everyone else as bored & jaded as I am, and perhaps even helping increase common knowledge, let me start by explaining why vulnerabilities to cross-site scripting and buffer overflows are instances of the same abstract problem: injection vulnerability.
It's pretty common to categorize cross-site scripting (XSS) and, say, SQL injection or format string exploits as injection attacks. In all of these cases, Mallory puts control codes where the developer is expecting data. A system will be vulnerable to an injection attack if and only if all 3 of the following interrelated conditions hold:
a. A data and control channel are mixed in the same communications medium.
b. Untrustworthy data is added to the data channel.
c. The mechanism intended to separate data & control channels is either insufficient or (more frequently) insufficiently applied.
Here's the run-down of these conditions for XSS in HTML:
a. An HTML document contains interleaved tags and attribute labels (the control channel), and their values (the data channel).
b. In most if not all interactive Web applications, user input (always untrustworthy, whether it is taken from this request or some persistent store) is displayed in the HTML document.
c. The mechanism for separating data & control channels in HTML is encoding according to the HTML specification; HTML is well-defined and this mechanism is sufficient.
Since embedded markup is part of the fundamental nature and appeal of HTML, not to mention a long-standing standard, nobody is about to change (a) and the usual recommendations for avoiding vulnerabilities to XSS correspond to eliminating (b) and (c):
b. Validate input (i.e. ensure it is trustworthy).
c. Encode input for the appropriate context before displaying it (i.e. use the mechanism for separating the data channel from the control channel).
It's less common to think of a buffer overflow attack as an injection attack, but here's the run-down of the injection vulnerability conditions for a buffer overflow on the stack:
a. The stack contains interleaved stack frame structures (the control channel) and local variables, parameters and the like (the data channel).
b. Almost any application touches input from outside itself (untrustworthy data), and almost any application puts the input it is processing into function parameters and local variables.
c. The mechanism for separating data & control channels on the stack is size-based; the stack is well-defined and this mechanism is sufficient.
As you can see, it's a match. If any of these conditions does not hold, stack-based buffer overflows are not possible. For purposes of avoiding the vulnerability, the important thing isn't the kind of control information the attacker can insert (HTML vs. pointers and machine instructions), or the mechanism used to separate data & control channels (encoding vs. size), it's preventing the data channel from leaking into the control channel.
As one would expect, the usual recommendations for avoiding vulnerability to stack-based buffer overflows eliminate one or more of these conditions :
a. Separate data and call stacks. (Actually this is relatively uncommon recommendation, probably because calling conventions are a pretty low-level interface to be changing.)
b. Validate input, e.g. confirm that strings are actually null-terminated. (Of course, by the time it's input to a function that would want to do the validation, you frequently don't have enough information to tell whether the null terminator occurs within the bounds of the memory that was allocated for this variable, so in practice you need to decide on and check invariants at the time the data is output to the variable.)
c. Use safe functions, i.e. those that have length arguments or otherwise include length checking (i.e. use the mechanism for separating the data channel from the control channel).
b and c. Use an interpreted language. (Many interpreted languages put object IDs (frequently pointers) on the stack instead of the original data; if the object IDs are not controllable from outside, no untrustworthy data goes on the stack. Also, types in many interpreted languages do automatic bounds checking, i.e. use the mechanism for separating the data channel from the control channel.)
What about all the other stack-based buffer overflow recommendations you hear, like canaries, mirrored stacks, ASLR and non-executable stacks? They work by detecting the attack or reducing its possible consequences, not by avoiding the vulnerability to start with. So, they are worth doing, but if you don't address the conditions that make the attack possible to start with, the underlying vulnerability is still there.
Now that the conditions that enable injection attacks are clear, I hope you will see them everywhere they exist in your system, even if you are using proprietary data formats & protocols no one but you has ever heard of. It could be very instructive to make a quick table of data formats and network protocols used in each layer of your system, with columns for whether control is in-band, whether untrustworthy input appears in the data channel (remember that untrustworthy data inserted at a higher layer of the system is still untrustworthy when it is interleaved with a control channel at a lower layer), the mechanism(s) for separating the control and data channel, and whether each mechanism, if applied correctly, is sufficient.
With that, I'm relying on you to move on to bigger and better kinds of vulnerability in 2009!