It's probably not one of our more admirable personality traits, but
once in a while a vulnerability comes along that makes us laugh out
loud. To tell the truth, we enjoy watching (from a safe vantage
point) while a fellow practitioner takes a good slip on the old
banana peel. More soberly, such blunders remind us how distant our
world is today from true "software engineering". How are we ever
going to build virtual bridges that don't fall down with the crude
tools in use today? (And we mean to include that piece of meat
between our ears as one of the "crude tools".)

Last week, Arnaud Jacques of Securiteinfo.com reported a
vulnerability in digi-FX's "digi-news", a PHP script that allows you
to easily add, edit, and delete news. (SecurityTracker followed up
the announcement with an analysis. You can find all of the details at
http://securitytracker.com/alerts/2003/Jul/1007218.html.)
We can't shake a grin as we think about it. See if you can do any
better.

The script is supposed to authenticate the user by verifying the
user name (taken from a cookie) and the password, supplied at run
time. Here is the check.

Do you see the bug? It's timeless: one of your authors admits to
making this exact mistake over 30 years ago. In pseudo-code, we
might paraphrase the buggy line as, "IF the username is not right
AND the password is not right THEN reject the login attempt." The
effect, of course, is that an attacker only needs to get one or the
other correct in order to pass the authentication check!

A COGNITIVE ERROR

What we have here is a beautiful example of the sort of mistakes
that occur when we try to translate our everyday thinking to
computerese.

We guess the programmer was probably thinking, "If the username is
right and the password is right, we're OK." But the way that most
block-oriented languages work, it's usually easier to place the
"reject" code first in lexical order--that is, as it lies "on the
page". That's the way to avoid the use of the horrific GOTO
(remember those?). In order to make that happen, we need to create a
logical converse by negating the two conditions and then changing
the AND to an OR. "If the username is not right", you want to say,
"or the password is not right, we're not OK." The logical error was
introduced when the programmer forgot to flip the binary operator.
We guess the reason the result seems so silly to us is that it's the
kind of authentication error a human being isn't likely to make.

A TRICKY POINT OF LANGUAGE

There's another point of substance here. If the statement had been
written in natural human language, the proof-reading programmer
(with a little luck) might well have found it.

Take another look at the code. The problem arises in those two
concatenated ampersands ("&&"). Were they vertical bars instead
("||"), the bug would not exist.

It really comes down to a usability (or "human factors") argument.
Even though many of our friends are so experienced with those very
Boolean operators that they casually drop them into conversational
jokes, we will argue that including them in a language raises the
cognitive burden on the programmer, and makes semantic mistakes
more likely. It may be for this reason that some languages
(including PERL, but not, we think, PHP) allow the use of the
English words "AND" and "OR" in addition to the Boolean symbols.
(As you know, the proper use of all these operators is a very
complicated topic, especially when one considers possible side
effects resulting from the order in which conditions are expressed
or evaluated--so we'll travel no further down that road.)

In general, we're fans of simple code. After all, if the programmer
had written the equivalent of "If the username is wrong, exit; if
the password is wrong, exit", the code might execute slightly
slower, or take up a little more memory; but we wouldn't be
discussing it in a security analysis.

A QUESTION OF TESTING

Bringing the discussion back to security, we have a final point. We
think the failure here is not purely one of implementation. We'll
also point the finger at inadequate testing.

Just because simple logical errors like the one under the microscope
here are so easy, it's critical to devise and use testing methods
that verify correct operation. In this case, while it may be
possible to find a static tester that could catch this error, we
think it's unlikely. This seems to us to be a case for "black box"
testing, in which applications are fed myriad input streams and the
results checked predicted results. Precisely because this is the
sort of logical error that a human is unlikely to commit, if the
check was being performed manually, it's important to use automated
test harnesses and idiosyncratic input streams to look for
"illogical" logic errors. Chapter 6 covers this ground well.

Automating your testing won't always keep you from being the object
of fun and finger-pointing. It can keep you from slipping on the
same banana peel twice, though. Some days, that's all the dignity we
can hope for.