Not just $SAFE, but damn $SAFE

Discussion in 'Ruby' started by Aredridel, Sep 2, 2004.

  1. Aredridel

    Aredridel Guest

    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
     
    Aredridel, Sep 2, 2004
    #1
    1. Advertising

  2. On Thu, Sep 02, 2004 at 03:47:50PM +0900, Aredridel wrote:
    > 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 :)

    --
    Running Debian GNU/Linux Sid (unstable)
    batsman dot geo at yahoo dot com
     
    Mauricio Fernández, Sep 2, 2004
    #2
    1. Advertising

  3. Aredridel wrote:

    > 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
     
    Florian Gross, Sep 2, 2004
    #3
  4. 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.)
     
    Florian Gross, Sep 2, 2004
    #4
  5. Aredridel

    David Ross Guest

    --- Mauricio Fernández <> wrote:
    > 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

    >
    > --
    > Running Debian GNU/Linux Sid (unstable)
    > batsman dot geo at yahoo dot com
    >
    >
    >





    __________________________________
    Do you Yahoo!?
    Yahoo! Mail - 50x more storage than other providers!
    http://promotions.yahoo.com/new_mail
     
    David Ross, Sep 2, 2004
    #5
  6. Aredridel

    ts Guest

    >>>>> "F" == Florian Gross <> writes:

    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
     
    ts, Sep 2, 2004
    #6
  7. Aredridel

    Dave Fayram Guest

    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.

    --
    Dave Fayram


    --

    Aredridel <> wrote:

    > 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
    >
    >
    >
    >
     
    Dave Fayram, Sep 2, 2004
    #7
  8. ts wrote:

    > 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
     
    Florian Gross, Sep 2, 2004
    #8
  9. Aredridel

    Aredridel Guest

    > 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.
     
    Aredridel, Sep 2, 2004
    #9
  10. Aredridel

    Aredridel Guest

    On Fri, 2004-09-03 at 02:01 +0900, Dave Fayram wrote:
    > 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)
     
    Aredridel, Sep 2, 2004
    #10
  11. Aredridel wrote:

    >> 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.
    >
    >
    >


    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 :
    259 Front St. Phone : (207) 442-7994 x 128
    Bath, ME 04530 Fax : (207) 443-6284
     
    Walter Szewelanczyk, Sep 2, 2004
    #11
  12. Aredridel

    Aredridel Guest

    On Fri, 2004-09-03 at 05:50 +0900, Walter Szewelanczyk wrote:
    > 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
     
    Aredridel, Sep 2, 2004
    #12
  13. Walter Szewelanczyk <> wrote:
    >
    > 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
     
    Martin DeMello, Sep 3, 2004
    #13
  14. Aredridel

    ts Guest

    >>>>> "F" == Florian Gross <> writes:

    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
     
    ts, Sep 3, 2004
    #14
  15. Aredridel

    ts Guest

    >>>>> "A" == Aredridel <> writes:

    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
     
    ts, Sep 3, 2004
    #15
  16. ts wrote:

    > 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
     
    Florian Gross, Sep 3, 2004
    #16
  17. Aredridel

    ts Guest

    >>>>> "F" == Florian Gross <> writes:

    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
     
    ts, Sep 3, 2004
    #17
  18. Aredridel

    ts Guest

    >>>>> "t" == ts <> writes:

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

    and is in the array

    Guy Decoux
     
    ts, Sep 3, 2004
    #18
  19. Aredridel

    Aredridel Guest

    On Fri, 2004-09-03 at 17:08 +0900, ts wrote:
    > >>>>> "A" == Aredridel <> writes:

    >
    > 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
     
    Aredridel, Sep 3, 2004
    #19
  20. Aredridel

    ts Guest

    >>>>> "A" == Aredridel <> writes:

    A> On Fri, 2004-09-03 at 17:08 +0900, ts wrote:

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


    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
     
    ts, Sep 4, 2004
    #20
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Admin
    Replies:
    0
    Views:
    679
    Admin
    Jul 8, 2003
  2. Lukasz Lacki

    god damn ViewState.

    Lukasz Lacki, Nov 14, 2003, in forum: ASP .Net
    Replies:
    5
    Views:
    585
    Lukasz Lacki
    Nov 14, 2003
  3. Neo Geshel
    Replies:
    2
    Views:
    3,634
    Versteijn
    Aug 18, 2004
  4. The Clansman

    help me with this damn problem!!!!!!!

    The Clansman, Aug 8, 2004, in forum: ASP .Net
    Replies:
    9
    Views:
    408
    =?Utf-8?B?QWxleGFuZGVy?=
    Sep 29, 2004
  5. Kev

    Damn CS0006 error

    Kev, Jun 27, 2005, in forum: ASP .Net
    Replies:
    0
    Views:
    407
Loading...

Share This Page