Executing User-Supplied Ruby Code on a Web Server

Executing user-supplied ruby code on a web server

You can use a "blank slate" as a clean room, and a sandbox in which to set the safe level to 4.

A blank slate an object you've stripped all the methods from:

class BlankSlate

instance_methods.each do |name|
class_eval do
unless name =~ /^__|^instance_eval$|^binding$|^object_id$/
undef_method name
end
end
end

end

A clean room is an object in which context you evaluate other code:

  clean_room = BlankSlate.new

Read a command from an untrusted source, then untaint it. Unless untainted, Ruby will refuse to eval the string in a sandbox.

  command = gets
command.untaint

Now execute the string in a sandbox, cranking the safe level up as high as it will go. The $SAFE level will go back to normal when the proc ends. We execute the command in the context of the clean room's binding, so that it can only see the methods and variables that the clean room can see (remember, though, that like any object, the clean room can see anything in global scape).

  result = proc do
$SAFE = 4
clean_room.instance_eval do
binding
end.eval(command)
end.call

print the result:

  p result

How to safely let users run arbitrary Ruby code?

Three suggestions:

1) Take a look at Ruby taint levels. This provides some degree of protection against, eval('evil_code') type things, etc.

2) Unless user's actually need access to the local file system, use something like fakefs

3) No matter what else you do follow Tronic's suggestion (can be a pain to setup, but limited chroot jails are about the only way to make absolutely sure that user's cannot access resources you don't explicitly want them to).

Why a ruby command runs even if a user don't activate the script yet?


I'm a Ruby user

Welcome to Rails!!


Stateless

Firstly, you need to understand that Rails applications - by virtue of running through HTTP - are stateless, meaning that "state" such as User or Account have to be re-established with each new action.

In short, this means that invoking actions/commands on your system have to be done through ajax or another form of server-connectivity.

Many native developers (native apps are stateful) don't understand how Rails / web apps are able to retain "state", and thus make a bunch of mistakes with their code.

receives user's active request

Even if you understand how to set up authentication inside a Rails app, it's important to understand the virtues of it being stateless... EG the above line means you have to have a user signed in and authenticated before you can send the request.

This forms one part of your problem (I'll explain in a second)


ERB

Secondly, the other problem you have is with the ERB nature of Rails.

the ruby code just start on its own even before I click this button.

This happens because you're including pure Ruby code in your front-end scripts. This means that whenever these scripts are loaded (triggered), they will fire.

The bottom line here is you need to put this script on your server. Otherwise it will just run...


Fixes

1. ERB

<%= content_tag :div, class: "page-title" do %>
<%= button_tag "Help Request", class:"btn-send-alert" %>
<%= content_tag :p, "Hello %>
<% end %>

You'll thank me in 1+ months.

Convention over Configuration means you use as many of the Rails helpers as you can. You don't need to go stupid with it, but the more "conventional" your code is, the better it will be for future developers to improve it.

  • Another tip - only use HTML for formatting; CSS for styling. Don't use <br> unless you actually want to break a line.

  • Another tip - never use inline styling - Rails has an adequate asset pipeline into which you should put all your CSS

--

2. Ajax

Secondly, your use of Javascript is incorrect.

More specifically, you're calling a server-based function inside front-end views. To explain this a little more, I'll show you the famed MVC image I post on here a lot:

Sample Image

This is how Rails works (MVC - Model View Controller) - this means that whenever you deal with your application, you have to accommodate a layer of abstraction between the user & your app -- the browser.

By its nature, the browser is not stateful - it stores session cookies which you have to authenticate on the server. You cannot call "server" code in the front-end HTML/JS.

This is why your Ruby code is firing without any interaction, although I'm not sure how it's able to fire in the asset pipeline.

If you want to make it work properly, you'll need to create a controller action to invoke the mailer send function, which you'll be able to do using the following setup:

#config/routes.rb
get :support, to: "application#support", as: :support -> url.com/support

#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
respond_to :js, only: :support
def support
Smalier.class_alert(@lesson.current_user).deliver_now
end
end

#app/views/controller/view.html.erb
<%= content_tag :div, class: "page-title" do %>
<%= button_to "Help Request", support_path, method: :get, class:"btn-send-alert" %>
<%= content_tag :p, "Hello" %>
<% end %>

#app/views/application/support.js.erb
alert ("Hello");

Run Ruby file from html form submit

The easiest way is as follows:

  1. Package the example.rb code into a class or module like so:

    class FileReader
    def self.read_grammar_defs(filename)
    # ...
    end
    end
  2. require the file from your sinatra server

  3. Inside the post action, read the params and call the method:

    post '/p' do
    @result = FileReader.read_grammar_defs(params[:grammar_file])
    erb :home
    end

With this code, after submitting the form, it would populate the @result variable and render the :home template. Instance variables are accessible from ERB and so you could access it from therer if you wanted to display the result.

This is one potential issue with this, though - when the page is rendered the url will still say "your_host.com/p" and if the user reloads the page, they will get a 404 / "route not found" error because there is no get "/p" defined.

As a workaround, you can redirect '/' and use session as described in this StackOverflow answer or Sinatra' official FAQ to pass the result value.



Related Topics



Leave a reply



Submit