Not just $SAFE, but damn $SAFE

A

Aredridel

I've been toying with an IRC bot that takes input from users in channel,
evals it, and returns the result.

Doing so safely has proved to be a challenge -- the biggest problem
being that you can't trust any method on the returned object.

Here's our solution, and I'd love to know if anyone can break it.

module Safe
def class
super
end
end

def safe_to_s(obj)
t = Thread.new {
$SAFE = 4
obj.to_s
}
o = t.value
class << o
include Safe
end
if String == o.class
o
else
raise SecurityError
end
end

def safe_eval(code)
t = Thread.new {
$SAFE = 4
eval(code)
}
t.value
end

puts(safe_to_s(safe_eval("exit! # or variations")))

puts "Made it!"


Consider this a challenge. The ways I broke simpler variants was to try
to trick safe_to_s into calling methods outside of the safe Thread.

Ari
 
M

Mauricio Fernández

Here's our solution, and I'd love to know if anyone can break it.

module Safe
def class
super
end
end

def safe_to_s(obj)
t = Thread.new {
$SAFE = 4
obj.to_s
}
o = t.value
class << o
include Safe
end
if String == o.class
better make it
String === o
o
else
raise SecurityError
end
end

def safe_eval(code)
t = Thread.new {
$SAFE = 4
eval(code)
}
t.value
end

puts(safe_to_s(safe_eval("exit! # or variations")))

puts "Made it!"

Take a look at [107071].

We had quite some fun in #ruby-lang nearly 1 year ago trying to break
Florian Groß' rubdo; the underlying code (safe.rb) was a quite more
involved, though.
It soon became apparent that it would be impossible to prevent DOS
attacks to rubdo:
* it was possible to block all threads with a slow builtin method (in C) like
Bignum#** (and overcome the timeout mechanism)
* Thread.new and Object#define_finalizer proved to be evil (he had to
disable them)
* symbols are not GCed; one could easily make flgr's machine swap to
death by creating new symbols repeatedly

Most of them can be addressed with rlimit but he was using win32 :)
 
F

Florian Gross

Aredridel said:
I've been toying with an IRC bot that takes input from users in channel,
evals it, and returns the result.

Doing so safely has proved to be a challenge -- the biggest problem
being that you can't trust any method on the returned object.

Here's our solution, and I'd love to know if anyone can break it.

It is easily breakable because of singleton methods. I've attached
safe.rb which ought to be secure now (hello ts ;)) that
ObjectSpace.define_finalizer doesn't allow one to escape the sandbox
anymore.

However, as batsman already mentioned all this still doesn't help much
against DoS attacks. (You have to protect against those on the OS level
right now.)

Regards,
Florian Gross


module Safe
extend self

# Runs passed code in a relatively safe sandboxed environment.
#
# You can pass a block which is called with the sandbox as its first
# argument to apply custom changes to the sandbox environment.
#
# Returns an Array with the result of the executed code and
# an exception, if one occurred.
#
# Example of usage:
#
# result, error = safe "1.0 / rand(10)"
# puts if error then
# "Error: #{error.inspect}"
# else
# result.inspect
# end
def safe(code, sandbox = nil)
error, result = nil, nil

begin
thread = Thread.new do
sandbox ||= Object.new.taint
yield(sandbox) if block_given?

# FIXME: ENV and ARGV are globally made uselss
ENV.replace Hash.new
ARGV.replace Array.new
$-w = nil
$SAFE = 5

eval(code, sandbox.send:)binding))
end
result = secure_object(thread.value)
rescue Exception => error
error = secure_object(error)
end

return result, error
end

def secure_object(obj)
# We can't dup immediate values. But that's no problem
# because most of them can't have any singleton methods
# anyway. (nil, true and false can, but they can't be
# defined in safe contexts.)
immediate_classes = [Fixnum, Symbol, NilClass, TrueClass, FalseClass]
return obj if immediate_classes.any? { |klass| klass === obj }

safe_call = lambda do |obj, method, *args|
Object.instance_method(method).bind(obj).call(*args)
end

klass = safe_call[obj, :class]
return nil if safe_call[klass, :tainted?]

# Dup won't copy any singleton methods and without any
# of them the Object will be safe. (But we can't call
# the Object's .dup because it might be evil already.)
result = safe_call[obj, :dup]

# result can now be trusted which means that we can call
# methods like instance_variables etc. directly
result.instance_variables.each do |iv|
value = result.instance_variable_get(iv)
result.instance_variable_set(iv, secure_object(value))
end

return result
end
end

def safe(*args, &block)
Safe.safe(*args, &block)
end
 
F

Florian Gross

Oh, and be careful about calling methods on the 'safe' return value.
Object#inspect is recursive which means that it will be called on every
element of Arrays, Structs etc. (and those aren't filtered through
secure_object right now.)
 
D

David Ross

--- Mauricio Fernández said:
Most of them can be addressed with rlimit but he was
using win32 :)

Yes well, this is what I was referring to when I said
Ruby needed its own limiting factors built-in to the
interpreter. Not everyone has a machine that is unix
based. There needs to be..
1) memory limiting
2) process limiting
3) file limiting and usage
4) etc. I am sure there are others

--David Ross




__________________________________
Do you Yahoo!?
Yahoo! Mail - 50x more storage than other providers!
http://promotions.yahoo.com/new_mail
 
T

ts

F> It is easily breakable because of singleton methods. I've attached
F> safe.rb which ought to be secure now (hello ts ;)) that
F> ObjectSpace.define_finalizer doesn't allow one to escape the sandbox
F> anymore.

Sincerely I don't understand (I know, I'm stupid) but why don't you define
classes that you want to trust and give a result only when an object
belong to theses classes ?


Guy Decoux
 
D

Dave Fayram

I asked this question quite some time ago, doing the exact same thing (because having programmable bots that are also safe is really neat!).

Matz provided exactly what I wanted. Once you get your object back, you can't trust it, but you can make a new string from it with relative safety:

sprime = String.new( tainted_string )

This gets around most kinds of method hacking.
 
F

Florian Gross

ts said:
F> It is easily breakable because of singleton methods. I've attached
F> safe.rb which ought to be secure now (hello ts ;)) that
F> ObjectSpace.define_finalizer doesn't allow one to escape the sandbox
F> anymore.

Sincerely I don't understand (I know, I'm stupid) but why don't you define
classes that you want to trust and give a result only when an object
belong to theses classes ?

An user might want to use the sandbox with custom classes. And I'm not
sure if the check should be applied recursively or if an user is
expected to be careful himself.

Regards,
Florian Gross
 
A

Aredridel

Sincerely I don't understand (I know, I'm stupid) but why don't you define
classes that you want to trust and give a result only when an object
belong to theses classes ?

The problem is in finding out what class something is.

obj.class # Bad -- class can be overridden to be evil

obj.instance_of? # Bad -- same as #class

Class === obj # Bad, because you can define an evil subclass.
 
A

Aredridel

I asked this question quite some time ago, doing the exact same thing (because having programmable bots that are also safe is really neat!).

Matz provided exactly what I wanted. Once you get your object back, you can't trust it, but you can make a new string from it with relative safety:

sprime = String.new( tainted_string )

This gets around most kinds of method hacking.

What if I make Bad#to_str in this context?

Try this on:

class EvilStr < String
def to_str
puts "Haha2"
end
end

class Bad
def to_str
puts "Haha!"
EvilStr.new("Boo")
end
end

puts String.new(Bad.new.taint)
 
W

Walter Szewelanczyk

Aredridel said:
The problem is in finding out what class something is.

obj.class # Bad -- class can be overridden to be evil

obj.instance_of? # Bad -- same as #class

Class === obj # Bad, because you can define an evil subclass.

To find the class you can use :

class ClassFinder
@@classCall = Object.instance_method:)class)
def ClassFinder.class(obj)
@@classCall.bind(obj).call
end
end

class Bad
def class
Object
end
end

puts Bad.new.class
puts ClassFinder.class(Bad.new)

outputs :
Object
Bad


Walt

--
Walter Szewelanczyk
IS Director
M.W. Sewall & CO. email : (e-mail address removed)
259 Front St. Phone : (207) 442-7994 x 128
Bath, ME 04530 Fax : (207) 443-6284
 
A

Aredridel

To find the class you can use :

class ClassFinder
@@classCall = Object.instance_method:)class)
def ClassFinder.class(obj)
@@classCall.bind(obj).call
end
end

Walt, that's genius. That one's getting stowed somewhere for posterity.

Ari
 
M

Martin DeMello

Walter Szewelanczyk said:
To find the class you can use :

class ClassFinder
@@classCall = Object.instance_method:)class)
def ClassFinder.class(obj)
@@classCall.bind(obj).call
end
end

Brilliant!

martin
 
T

ts

F> An user might want to use the sandbox with custom classes. And I'm not
F> sure if the check should be applied recursively or if an user is
F> expected to be careful himself.

An user might want to write stupid thing, why your safe module must do
stupid thing ?


Guy Decoux
 
T

ts

A> Class === obj # Bad, because you can define an evil subclass.

This is not a problem.

1) if you use `case', you can trust the result (i.e. an object can't "lie")

2) you know that some methods are safe. For example, if you have a String
or an Exception use String.new(obj). If you have an Integer or a
Bignum, use Integer(String.new(obj.to_s)). If you have a Time object
use Time.at(obj), etc

Now, if you have a container (Array, Hash, ...) just create a new
container and store it only "safe" objects.


Guy Decoux
 
F

Florian Gross

ts said:
F> An user might want to use the sandbox with custom classes. And I'm not
F> sure if the check should be applied recursively or if an user is
F> expected to be careful himself.
An user might want to write stupid thing, why your safe module must do
stupid thing ?

Because sometimes the user wants to do a non-stupid, but still
unexpected thing and then flexibility is good. But I think it would
maybe be a good idea to allow white lists via an option to the safe()-call.

Regards,
Florian Gross
 
T

ts

F> Because sometimes the user wants to do a non-stupid, but still
F> unexpected thing and then flexibility is good. But I think it would
F> maybe be a good idea to allow white lists via an option to the safe()-call.

and where is the problem ?

There are 2 cases :

* some classes are safe, because you can safely use a method to create an
object.

For example Symbol, NilClass, TrueClass, FalseClass, Fixnum, Bignum,
Float, String, Exception, Regexp, Time, Range, Array, Hash

your module can handle these classes

* for other classes, define a protocol

- when you enter in #safe store in an array, all classes which are not
tainted (test it with Kernel#tainted?) and which define the method
::secure_it

- when the class of the result respond to this method, call it with the
result of #eval

kl.secure_it(obj)

now this is the responsibilty to the user to make the "right" thing
and at least it will not be the fault of *your* module if the user
has a problem.


It's evident that all this must be done at $SAFE >=4


Guy Decoux
 
T

ts

t> - when the class of the result respond to this method,

and is in the array

Guy Decoux
 
A

Aredridel

A> Class === obj # Bad, because you can define an evil subclass.

This is not a problem.

1) if you use `case', you can trust the result (i.e. an object can't "lie")

Except that by using case, I can have an EvilString < String, for which
that returns true.
2) you know that some methods are safe. For example, if you have a String
or an Exception use String.new(obj). If you have an Integer or a
Bignum, use Integer(String.new(obj.to_s)). If you have a Time object
use Time.at(obj), etc

Now, if you have a container (Array, Hash, ...) just create a new
container and store it only "safe" objects.

Yup. That's similar to what we're doing. The problem lies in to_s, in
that our eventual goal is to output text. Any deep data structure is
harder to deal with.

Ari
 
T

ts

A> Except that by using case, I can have an EvilString < String, for which
A> that returns true.

We really don't use the same ruby

svg% cat b.rb
#!/usr/bin/ruby
class EvilString < String
end

obj = EvilString.new("hello")
res = case obj
when String
String.new(obj)
else
raise "EvilString"
end
p res
svg%

svg% b.rb
"hello"
svg%



`case' will call Module#===, try to modify it with $SAFE >= 4



Guy Decoux
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,156
Messages
2,570,878
Members
47,413
Latest member
KeiraLight

Latest Threads

Top