What are the taint and trust methods? How can I use them effectively?
Tainted objects are a way for Ruby to track from where data has come. When you set Ruby to a certain mode, all further strings read from files, the keyboard or the network, as well as many other objects "tainted" by outside influence can be marked as "tainted." Tainted objects can't be used to do certain things like open files, can't be evaluated as Ruby code, etc. All of this is a security feature in Ruby which is disabled by default.
The security measure in question here is the $SAFE level. This is a special global variable that, when assigned a Fixnum, will take certain precautions. Normally, this special global variable is set to 0, Ruby's "DEFCON" zero where everything can be trusted. Raising this to 1 by setting $SAFE = 1 makes Ruby a bit more cautious. It now looks at all objects and checks if they're tainted before doing things like opening files, initiating network connections, evaluating strings as Ruby code, loading more Ruby code, etc. If the object in question is tainted, you'll get a SecurityError exception reading something like "Insecure Operation - initialize".
Just how strict Ruby is depends on the $SAFE level.
- $SAFE == 0, Ruby won't check if any objects are tainted. No objects will be created with the tainted flag, they can only be manually labelled as tainted. This is how Ruby acts when it first starts.
- $SAFE >= 1, Ruby checks the tainted status of all objects before using them to open a file or doing anything else that may be dangerous.
- $SAFE >= 2, Ruby prevents you from loading Ruby code from globally writible file locations. This prevents an attacker from writing malicious code to a file that the Ruby program thinks is legitimate.
- $SAFE >= 3, All newly created objects are tainted. This is Ruby's "lockdown" mode. Once a program gets loaded up and acquires its resources (network, file, etc), it can set $SAFE to 3 to prevent any accidental resource acquisition from any objects created after the $SAFE level was set to 3.
- $SAFE >= 4, Locks down even further. Locks all untainted objects using freeze, prevents all manner of behavior including writing to files, all new objects are untrusted and modifying global variables. This is an extreme measure, it does so much and has such wide-ranging implications that it cannot be explained here.
And one more thing you'll notice about $SAFE: you can set it to a higher level, but you cannot set it to a lower level. You can do $SAFE = 1, but once you do that you can't then do $SAFE = 0. There should be no way to lower the safe level, if there were a way to do that then the entire concept of the safe level would be corrupted. If you haven't gotten it by now, the safe level is not a minor thing, and it's not used very often since it prevents many third party gems from working correctly. Your program really has to be designed from the ground up to use a certain safe level.
So, back to tainted and untrusted flags. The rule of thumb for the tainted flag is that anything that comes from user input, a file or the network will be tainted. Basically, anything that isn't a literal from within the program itself is tainted. The untrusted flag is similar, it prevents the same types of actions as the tainted flag. However, it really only comes into play when the safe level is 4 or higher. And then, after setting the safe level to 4 or higher, any new objects created will be untrusted.
And one more thing to remember about tainted objects is that they're contagious. If you use a tainted string in a string interpolation, the newly created string will also be tainted. This prevents tainted objects from leaking through.
But what if you've verified that this user input string is perfectly OK? Maybe you're using it to open a file and you've inspected the string and there's nothing suspect about it. That's easy, just call untaint on it. It'll now be implicitly trusted by Ruby, so if security is an issue them make sure that string has nothing malicious. This forces you to do sanity checks at the very least.
$SAFE = 1 # Will be tainted, comes from user filename = gets.chomp # Untaint unless "parent directory" found in the string filename.untaint unless filename =~ /\.\./ file = File.open(filename, 'r')