Misfeatures Strike Again

Melissa Elliott By Melissa Elliott
September 25, 2014

Bash – the Unix shell – came out when I was fourteen months old. It was a replacement for a similar program that came out eleven years before I was born. By the time I was learning to read, it’d already had years to mature and stabilize. The very first time I ever sat down at a Linux prompt, bash was fifteen years old. It’s now twenty-five. From my perspective, bash has always existed, and I have never given any thought to where it comes from or who maintains it. It’s just there.

It’s also unfathomably complicated. It’s a utility; it’s an interface; it’s a language; it’s a runtime. It’s the early nineties in a nutshell, and when it comes to computers, that means it’s a mess. Design goals like “flexible” and “powerful” translate into “demonically baroque” and “fully armed and operational battle-station.” Bash is the sort of language where “esac” and “fi” are keywords; where “:(){ :|:& };:” is both a completely valid program and a catastrophic one; where “2>&1” is expected to be profoundly meaningful to the reader, and has nothing to do with 2 being greater than 1 at all – and the ampersand does something completely, utterly different in these two examples. I am not going to sugarcoat this: bash is a bad language and it should feel bad. It’s a langsec nightmare from which there is no escape, except to the shell.

Take a look at this real-world bash scripting bug from a few months ago. It’s a major security flaw that boils down to whether or not there are quotes around the right side of a variable assignment. All that stands between executing and not executing mystery files as root is some silly quotation marks! My comment at the time was that “bash is a language basically designed to execute every file it comes across.” Bash is precipitated upon the idea that you’d never not want to run any program it can parse into existence. It never met a string it didn’t want to interpolate.

You’ve probably realized that this is all buildup to The Bash Bug (I do rather fancy the nickname Shellshock). Part of me is shocked and dismayed that this bug has existed for years and years in the wild unnoticed, bash having an unimaginably vast deployment; it wouldn’t be an exaggeration to describe it as one of the most-executed programs of all time. The other part of me thinks, well, look at how messy, complicated, and inscrutable bash is, rather like other critical code bases that recently had catastrophic bugs with catchy nicknames. This is a program that has been built up for as long as I have been alive upon the earth, and it exhibits the typical engineering mis-steps of its time.

The early days of the internet are technology’s own sort of industrial revolution – where massive infrastructure was built up very quickly without much idea of what the long-term consequences would be, and now we are stuck with the technical debt of poor architecture for far, far longer than it took to design and implement it. I can lean back in my chair and tell you that I think bash is a poorly designed language, for what my opinion is worth, but that changes absolutely nothing about the fact that it’s sitting just underneath thousands of important programs which rely on it. It’s not going to go away. We built our foundation on misfeatures.

“Power” and “flexibility” sound excellent in principle, but they are just a gilded covering over complexity, and complexity is the mire from which bugs spawn. Bugs at shell contact points such as system() and popen() are well-known and well-studied, but they fester and persist. “Flexibility” in most environments just means that everything is a string, and a string can be anything, and anything can happen to that poor string. The Shellshock bug is in a feature where a magic character sequence causes an exported environmental variable to have its contents read in as a function and added to the namespace. What a splendid idea! We can pass custom code at runtime from wherever to do whatever! It’s so flexible.

This is and always was a misfeature with regards to how environmental variables are used in the real world to transmit potentially arbitrary data from some other realm of existence through to the shell. Unless the front-end program specifically strips the magic leading sequence “() {” from arbitrary strings, arbitrary things could be added to the functions of the bash instance, with potentially arbitrarily surprising results including mystery crashes if it doesn’t parse. Considering that I have never once seen this mentioned anywhere in any security guide, I reckon the number of such programs to actually do so is approximately zero. This powerful, flexible feature is simply too obscure. That same obscurity and complexity is what facilitated the bug in the misfeature: you can close the function definition and append top-level code which will execute immediately upon shell spawn, without needing to get the right function called. Oops.

A screenshot has been going around Twitter of the news referring to “a bug called bash,” the sort of mistake the news usually makes. However, I feel it wouldn’t be too off-base to say “a misfeature called bash.” Many of the uses to which it is put are a complete mismatch to what it can and will do to your data given the smallest chance. A robustly engineered program should intersect with bash, or any other Turing-complete system, at as few points as possible, and in as constrained a format as possible. When interacting with anything outside your program with its own programming language, take the time to learn it and its pitfalls to avoid inadvertently causing it to be programmed. Just as importantly, think long and hard before adding such powerful, flexible misfeatures to your own code. Don’t pile on the technical debt for someone else to deal with in another fifteen or twenty-five years.

Melissa Elliott is an application security researcher who has been writing loud opinions from a quiet corner of the Veracode office for two years and counting. She enjoys yelling about computers on Twitter and can be bribed with white chocolate mocha.