Hack me, I'm yours!

Posted by: Brent York

Today’s article covers the concepts of fail closed and fail open design. These two concepts are extremely simple to grasp through example. So, let’s step aside and let the following short story teach us about fail-open systems design:

 

It was a nice sunny day in Van-Nuys... the birds were singing, and the leaves were starting to bud on the trees outside of the bank. People were coming in, going about their business and leaving. Dave stood outside drinking his cappuccino, dreading waiting in the line up. Dave hated banks, in fact, he only visited them the day after pay-day and then only to withdraw his pay to put in a small safe that he kept at home.

Dave finished his cappuccino and walked inside. He stepped into line and began padding from foot to foot impatiently as he waited. All of the sudden, the lights went out, and several men in masks burst through the front doors. They forced Dave and all the patrons behind the counter. There we no explosions, or drill sounds, it didn't seem like anyone really had to fight with the vaults at all! Dave saw the thieves turn the vault handle and pull, and the vault opened without so much as a fight. The thieves grabbed bag after bag of money. They went about their business quickly, threw the sacks in the truck outside, and tore away.

Minutes later, several squad cars arrived on the scene. It was however, much too late. The robbers were gone, and so was Dave's money.

 

Meet Dave, the protagonist in our story. Dave has been distrustful of banks his entire life, and with the recent subprime mortgage fiasco, who can blame him? He's so distrustful in fact that he's been squirreling away his savings in a personal safe he hides in his closet. If real banks had the systems design that exists in our cheesy Hollywood-style story, I could understand why.

So let’s analyze what went wrong here. First, the power went out. Let’s say for the sake of argument, the thieves involved had something to do with that. Let’s also say that the thieves knew something very important about the bank. What did they know you ask? Well, they knew that if the power failed to the electronic locks for the bank vaults, that the locks would disengage. When that happened all they had to do to steal the money was turn the door wheel and pull.

Well that seems incredibly dumb, now doesn't it? I mean anyone knows that if the power goes out to a bank vaults electronic locks, they should immediately lock closed and prevent anyone from getting into the vault, right?

Well... the short answer is yes.

So what does this have to do with software? Well, imagine for a second as an aside from all the documentation, designing, and various other time involved activities in a typical work day, you actually get around to writing an application. I know, it doesn't happen near often enough in a typical developers job, but this is theoretical, so bear with me :). For the sake of argument imagine that application takes some user input and proceeds to use that input as part of a larger process. For example, it uses the input and uses it as part of a shell command. (I'd like to point out that this is extremely bad practice but for the sake of a quick example, we might as well go whole hog with the bad practices.)

Now, this software has specific inputs (characters) that you're not going to allow the user to enter. You think you know what most of them are, so you put them in a list of characters that you check the string for, and if any of those characters are present, then you reject the string. Seems secure, right?

Wrong.

This one little mistake can very much give away the keys to the castle. Let me explain. Imagine that you block some characters that you don't want in your input like !, &, |, and ; knowing that these have specific shell functions that you certainly wouldn't want your users to use... Now, is that secure?

Consider the following as input:

blah `/usr/bin/perl -e 'print chr(59) . "/bin/rm -rf /" . chr(38)'`

Ouch... looks like there's something to that adage about there being more than one way to part a feline from its fur. So just because we happened to miss the back-tick, and a few other characters we're vulnerable. This, much like our bank vault doors above, is fail open. What this means is, if an attacker can cause it to fail at its job then the protection mechanism offers no real obstacle. In this case, the result of the failure could mean different things depending on the executing level of privilege of the application, but that's a subject for another blog post. Any way you slice it though, it's probably bad.

Ok so if filtering out characters is a bad option, what is a good one? Well...the answer is similar. Instead of providing a list of bad characters we won't accept, however we're going to provide a list of known good characters. That is, we're going to give the application a list of characters that we want to allow into the input stream. If any character in the input does not exist in that string then we reject the input. This is a fail closed solution.

For example, say I said that my system was going to allow A-Z, a-z, 0-9, and the period character as valid characters for input for that field. This subset of characters would cause several points of failure for the data entered above, and thus prevent the user from performing a very bad command on our UNIX system. Better yet for the sake of argument, let’s say that the - character is also a valid character in our input. I forgot it from the list, didn't I? Oops. Well the worst that can happen is they can't enter a filename called the-green-ball.txt. Certainly a much better scenario than having our application run rm -rf / on you, regardless of privilege level of the executing application.

Fail closed and fail open are not just concepts for user input, but for all forms of input. At any given point in a program where input is retrieved from a source that you absolutely cannot trust, that data needs to be validated in a fail closed manner to ensure that the attacker does not get any further than the check performed. This means forms input, ncurses input, command line input, text input, file input, socket input, and yes, even shared memory input. If you can't trust it validate, and if you're going to validate, then validate fail closed.

Ok, so I make my inputs fail closed and that's it right? Well no. You also have to consider what a failure means to the code that called the function. It does us no good to fail closed and prevent the execution of a function if failing to execute that function does not in some way signal back up the call-stack to ensure that functions above the one that failed know it failed and preferably why it failed as well. The functions above need to know that the function failed, why it failed (if only for logging purposes!), and they also need to handle it in a fail-closed manner.

This ensures that if someone does something they shouldn't, they're locked out. Period.

 

So, lets review what we've learned here.

  • Fail open bad.
  • Fail closed good.
  • Validate all untrusted inputs.
  • It's not enough to design your function fail closed, you have to design your application fail closed as well.

Stay tuned for the next installment, *psst* hey buddy, wanna buy an internal address map?.

Comments (0)Add Comment

Write comment

busy