|
Any Ruby object derived from some external source (for example, a
string read from a file, or an environment variable) is automatically
marked as being tainted. If your program uses a tainted object to
derive a new object, then that new object will also be tainted, as
shown in the code below. Any
object with external data somewhere in its past will be tainted. This
tainting process is performed regardless of the current safe level. You
can inspect the tainted status of an object using
Object#tainted?
.
# internal data
|
# =============
|
x1 = "a string"
|
x1.tainted?
|
� |
false
|
|
x2 = x1[2, 4]
|
x2.tainted?
|
� |
false
|
|
x1 =~ /([a-z])/
|
� |
0
|
$1.tainted?
|
� |
false
|
|
|
|
|
# external data
|
# =============
|
y1 = ENV["HOME"]
|
y1.tainted?
|
� |
true
|
|
y2 = y1[2, 4]
|
y2.tainted?
|
� |
true
|
|
y1 =~ /([a-z])/
|
� |
1
|
$1.tainted?
|
� |
true
|
|
You can force any object to become tainted by invoking its
taint method. If the safe level is less than 3, you can
remove the taint from an object by invoking
untaint . [There are also some devious ways of doing
this without using untaint . We'll leave it up to your
darker side to find them.] This is clearly not something to do
lightly.
Clearly, Walter should have run his CGI script at a safe level of
1. This would have raised an exception when the program tried to
pass form data to eval . Once this had happened, Walter would
have had
a number of choices. He could have chosen to implement a proper expression
parser, bypassing the risks inherent in using eval . However,
being lazy, it's more likely he'd have performed some simple sanity check on
the form data, and untaint it if it looked innocuous.
require 'cgi';
$SAFE = 1
cgi = CGI::new("html4")
expr = cgi["field"].to_s
if expr =~ %r{^-+*/\d\seE.()*$}
expr.untaint
result = eval(expr)
# display result back to user...
else
# display error message...
end
|
Personally, we think Walter's still taking undue risks. We'd probably
prefer to see a real parser here, but implementing one here has
nothing to teach us about tainting, so we'll move on.
And remember---it's a dangerous world out there. Be careful.
Definition of the safe levels
-
$SAFE >= 1
-
- The environment variables
RUBYLIB and RUBYOPT are not
processed, and the current directory is not added to the path.
- The command-line options
-e , -i , -I , -r ,
-s , -S , and -x are
not allowed.
- Can't start processes from
$PATH if any directory
in it is world-writable.
- Can't manipulate or chroot to a directory whose name is a tainted string.
- Can't glob tainted strings.
- Can't eval tainted strings.
- Can't load or require a file whose name is a tainted string.
- Can't manipulate or query the status of a file or pipe whose
name is a tainted string.
- Can't execute a system command or exec a program from a
tainted string.
- Can't pass
trap a tainted string.
-
$SAFE >= 2
-
- Can't change, make, or remove directories, or use chroot.
- Can't load a file from a world-writable directory.
- Can't load a file from a tainted filename starting with ~.
- Can't use
File#chmod
,
File#chown
,
File#lstat
,
File.stat
,
File#truncate
,
File.umask
,
File#flock
,
IO#ioctl
,
IO#stat
,
Kernel#fork
,
Kernel#syscall
,
Kernel#trap
.
Process::setpgid
,
Process::setsid
,
Process::setpriority
, or
Process::egid=
.
- Can't handle signals using
trap .
-
$SAFE >= 3
-
- All objects are created tainted.
- Can't untaint objects.
-
$SAFE >= 4
-
- Can't modify a nontainted array, hash, or string.
- Can't modify a global variable.
- Can't access instance variables of nontainted objects.
- Can't change an environment variable.
- Can't close or reopen nontainted files.
- Can't freeze nontainted objects.
- Can't change visibility of methods (private/public/protected).
- Can't make an alias in a nontainted class or module.
- Can't get meta information (such as method or variable lists).
- Can't define, redefine, remove, or undef a method in a nontainted
class or module.
- Can't modify
Object .
- Can't remove instance variables or constants from nontainted
objects.
- Can't manipulate threads, terminate a thread other than the
current, or set
abort_on_exception .
- Can't have thread local variables.
- Can't raise an exception in a thread with a lower
$SAFE value.
- Can't move threads between ThreadGroups.
- Can't invoke
exit , exit! , or abort .
- Can load only wrapped files, and can't include modules
in nontainted classes and modules.
- Can't convert symbol identifiers to object references.
- Can't write to files or pipes.
- Can't use
autoload .
- Can't taint objects.
|
|
|