i have a text field in a database table (a text file, generally
speaking), with arbitrary Ruby code. i would like to execute this code
in my application's current environment. but i need to hide all my
variables except for some specific ones from this code.
so i was thinking to create a class with a static method that accepts
parameters and put the content of the file in to this method. and pass
the variables i want to expose as parameters.
how is this done in ruby? i have never done it.
in addition, how to execute this code with maximal security level?
also, what would happen if the code reopened classes that i have
created? is this possible? how to make it not do that?
basically i want the user to type some code in to my application and
run it but i want to protect against malicious users who want to erase
my hard disk, etc.
harp:~ > cat a.rb
def run file, vars = {}
code = IO::read file
dumped = vars.inject({}){|d,kv| k, v = kv; d.update k => Marshal::dump(v)}
Thread::new(vars, code) do |vars, code|
$SAFE = 4
program = ""
dumped.each{|k,v| program << "#{ k } = Marshal::load <<-__data__\n#{ v }\n__data__\n\n"}
program << code
eval program
end.value
end
value = run "b.rb", "a" => "forty-two"
p value
value = run "c.rb", "a" => "42"
p value
harp:~ > cat b.rb
a.upcase
harp:~ > cat c.rb
a.center 42
harp:~ > ruby a.rb
"FORTY-TWO"
" 42 "
hth.
-a
···
On Thu, 8 Dec 2005, ako... wrote:
hello,
i have a text field in a database table (a text file, generally speaking),
with arbitrary Ruby code. i would like to execute this code in my
application's current environment. but i need to hide all my variables
except for some specific ones from this code.
so i was thinking to create a class with a static method that accepts
parameters and put the content of the file in to this method. and pass the
variables i want to expose as parameters.
how is this done in ruby? i have never done it.
in addition, how to execute this code with maximal security level?
also, what would happen if the code reopened classes that i have created? is
this possible? how to make it not do that?
basically i want the user to type some code in to my application and run it
but i want to protect against malicious users who want to erase my hard
disk, etc.
--
ara [dot] t [dot] howard [at] noaa [dot] gov
all happiness comes from the desire for others to be happy. all misery
comes from the desire for oneself to be happy.
-- bodhicaryavatara
my application runs inside a web browser process, and creating a thread
does not seem scalable.
i tried to evaluate a file from inside a lambda and it seems to work. i
also was able to set safety level for lambda locally which effectively
restores $SAFE when lambda finishes. the only drawback of this was that
my environment is still visible from lambdas.
code = <<CODE
v = MyClass.new('from text')
v.mymethod
CODE
class MyClass
def initialize(v) @v = v end
def mymethod() puts "SAFE=#{$SAFE}, value=#{@v}" end
end
p = eval <<LAMBDA
lambda {
module Mod
$SAFE = 3
#{code}
end
}
LAMBDA
v = MyClass.new('original')
v.mymethod
p.call
v.mymethod
There are a number of mechanisms to "lock down" the execution for a
chunk of Ruby code, the most common being the built-in $SAFE global
variable. Unfortunately, due to a number of recently-published
vulnerabilities in the implementation of the $SAFE checks, it's
generally not considered a good way to insure that your application
will be protected from malignant users.
The best example I've seen to date of a truly safe Ruby interpreter has
to be why the lucky stiff's "Try Ruby" website
(http://tryruby.hobix.com/), which lets anyone interact with a
restricted IRb prompt via their browser and some slick Javascript. You
might email why_ directly, and see if he'd be willing to share some
tips with you (and the rest of us!).
At a bare minimum, you need to make sure the following built-in classes
and modules are either entirely hidden, or redefined to "safe"
versions:
Dir
File
FileStat
FileUtils
IO (at least methods like 'popen', 'fcntl', etc.)
ObjectSpace
Pathname
Process
GzipFile
Kernel ('fork', 'at_exit', 'caller', 'load', 'require', many more)
....and probaby others, as well. Really, it's a *hard* thing to make
arbitrary code execution safe, so unless you can re-use and share
effort with others, I'd caution you against doing this unless
absolutely necessary.
my application runs inside a web browser process, and creating a thread does
not seem scalable.
but what if the user code creates one? it's the only way to isolate $SAFE and
your vars, see below...
i tried to evaluate a file from inside a lambda and it seems to work. i also
was able to set safety level for lambda locally which effectively restores
$SAFE when lambda finishes. the only drawback of this was that my
environment is still visible from lambdas.
also note that lambda will prevent __everything__ in it's scope from __ever__
being garbage collected. this is bad idea in a web app - assuming it's
persistant like fastcgi.
in any case be careful:
harp:~ > cat a.rb
code = IO::read "b.rb"
p = eval <<LAMBDA
lambda {
module Mod
$SAFE = 3
#{code}
end
}
LAMBDA
p.call
p "we won't see this!"
harp:~ > cat b.rb
exit!
harp:~ > ruby a.rb
a thread will prevent you main program from exiting.
regards.
-a
···
On Thu, 8 Dec 2005, ako... wrote:
--
ara [dot] t [dot] howard [at] noaa [dot] gov
all happiness comes from the desire for others to be happy. all misery
comes from the desire for oneself to be happy.
-- bodhicaryavatara
putting aside security issues i think i was able to isolate my
environment and pass only desired variables in to the code. i create a
temporary module and evaluate my code in its context. then i remove
this module (that is why i needed the external module A, i could not
figure out how to remove a module otherwise). experts, does this look
feasible? i do not know though what to do to ensure security...
thanks
konstantin
module A
x = 'x'
y = 'y'
module T
class << self
def get_binding(p) binding end
end
end
this still leaves things like Object in scope. to truely isolate yourself
you'll need to fork a process that does all the work.
-a
···
On Thu, 8 Dec 2005, ako... wrote:
putting aside security issues i think i was able to isolate my
environment and pass only desired variables in to the code. i create a
temporary module and evaluate my code in its context. then i remove
this module (that is why i needed the external module A, i could not
figure out how to remove a module otherwise). experts, does this look
feasible? i do not know though what to do to ensure security...
--
ara [dot] t [dot] howard [at] noaa [dot] gov
all happiness comes from the desire for others to be happy. all misery
comes from the desire for oneself to be happy.
-- bodhicaryavatara
i understand abouth threads. i think i am just researching the problem
so far. would you explain about Object?
i tried this:
module A
class Object
def mymethod(x) puts "#{x} in A" end
end
code = <<CODE
class Object; end
o = Object.new #o.mymethod(p) # -> fails
class Object
def mymethod(x) puts "\#{x} in code" end
end
o = Object.new
o.mymethod(p)
CODE
x = 'x'
y = 'y'
module T
class << self
def get_binding(p) binding end
end
end