Sexy Theme Templating with Haml Safemode! Finally ...

posted: February 5th, 2008 · by: Sven

in: Programming · tagged as: , , , , ·  18 comments »

Ok, this is really a looong lasting itch of mine I wanted to scratch ever since I’ve learned Liquid templates for Mephisto.

Liquid still is (as far as I know) the only usable “safe” Ruby templating engine that one could use for themes/templates in an application like Mephisto. In this context “safe” means that you can allow your users to download and install themes from arbitrary sources.

Liquid is safe …

So, with Liquid you can still sleep at night without any worries that some bastard might have included a bit of code into a theme that sends your password files to the russian mafia, runs rm -rf / or whatever nightmare you like worse.

Liquid does a very solid job here and as such it earns respect. But … let’s face it: Liquid sucks, syntaxwise.

As a Ruby programmer you want a templating system that makes your templates easier to type and more intuitive to grasp than ERB, not worse! Maybe it’s really just me, but for me Liquid fails miserably in this regard.

Haml is sexy …

On the other side of the Ruby template engines universe lives Haml. A templating system that is that awesome that you can’t possibly toy around with it for more than 3 minutes without getting totally addicted to it. But Haml is an evaluating templating system like ERB and as such you can’t use it for themes from arbitrary sources.

So how cool would it be to combine the best of both? Obviously it’d totally rock. It would be as cool as Yahoo sunglasses in 1994 and as sexy as the Audi R8 in 2008 combined.

But that would be really hard, wouldn’t it?

I always thought that implementing this would be way over my head. To accomplish this one had to parse the Ruby code that Haml evaluates and take measures to ensure that only certain (whitelisted) methods on assigned objects can be called at all.

It’s easy to limit the template author’s access to certain methods on our own stuff. Liquid greatly demonstrates how to do this with its so called Drops and Strainers.

But how can one make sure that nothing else is used besides the objects assigned to the template? I.e. how could we prevent a (valid) Haml snippet like = File.open('/etc/htpasswd'){|f| f.read} from being evaluated?

RubyParser rocks …

Last night I accidentally stumbled across the RubyParser which is actually a Ruby syntax parser written in Ruby. And it’s available as a gem! Yeah. So with RubyParser we can easily hack Haml to parse and check any Ruby code from the templates before storing it for later evaluation.

My impression is that (given that we’ve closed access to unsecure methods down for the assigned variables) all we have to do is forbid access to all Ruby constants (because suspicious methods like Kernel.load, File.read etc. are all on classes) and shell command execution using backticks.

I’m sure that I’m missing something here … and I’d very much appreciate your heads-up if you see anything that also needs to be forbidden to make this waterproof.

But I’m totally thrilled that there’s an approach to make Haml a real candidate for a theme template engine finally. Oh, and, of course this can be applied to other evaluating templating engines like ERB, too. :)

I plan to continue playing with this and then check back with the Haml folks whether this could make its way into Haml or release it as a plugin for Haml otherwise.

For those of you interested in actual code … here’s a proof of concept piece of code:


require 'rubygems'
require 'haml'
require 'ruby_parser'

class Object
  def to_jail
    Haml::Jail.new self
  end
end

module Haml
  class SafeModeError < RuntimeError; end

  class Jail
    attr_reader :source    
    def initialize(source)
      @source = source
    end

    def method_missing(method, *args)
      # could easily hook in a whitelisted approach for allowing access to 
      # certain methods here
      warn "calling #{method} on #{source.class.name}... allow this?"
      Jail.new @source.send(method, *args)
    end

    def to_s
      @source.to_s
    end

    def to_jail
      self
    end
  end    
end

Haml::Engine.class_eval do
  alias_method :render_without_jailed_locals, :render

  def render(scope = Object.new, locals = {}, &block)
    locals = jail_all(locals) if options[:safemode]
    render_without_jailed_locals(scope, locals, &block)
  end

  def jail_all(vars)
    Hash[*vars.collect{|name, value| [name, value.to_jail]}.flatten]
  end
end

Haml::Precompiler.class_eval do
  alias_method :push_script_without_safeguard, :push_script

  def push_script(text, flattened, close_tag = nil)
    flush_merged_text
    return if options[:suppress_eval]
    safeguard_script(text.strip) if options[:safemode]
    push_script_without_safeguard(text, flattened, close_tag)
  rescue Haml::SafeModeError => error
    warn error.message
  end

  def safeguard_script(code)
    nodes = RubyParser.new.parse(code).to_a.flatten
    # do we need to forbit anything else then access to constants 
    # and shell command backticks?
    if nodes.include?(:const)
      raise Haml::SafeModeError, "Safemode doesn't allow access to constants."
    elsif nodes.include?(:xstr)
      raise Haml::SafeModeError, "Safemode doesn't allow shell command execs."
    end
  end
end

template = <<EOC
%p I can access methods on locals
%p
  = 'piece of evaluated %s code' % lang.downcase.strip
%p I can interate:
%p 
  - (1..3).each do |i|
    = i
%p and I can branch:
%p 
  - if true
    Yep!
  - else
    Nope ... :(
%p But I can't access constants ...
= File.open('/etc/passwd'){|f| f.read}
%p ... or execute shell commands
= `ls -a`
EOC
haml = Haml::Engine.new(template, {:safemode => true})
puts haml.render(Object.new, :lang => 'ruby')  

This will output:


<p>I can access local stuff</p>
<p>
  piece of evaluated ruby code
</p>
<p>I can interate:</p>
<p>
  1
  2
  3
</p>
<p>and I can branch:</p>
<p>
  Yep!
</p>
<p>But I can't access constants ...</p>
<p>... or execute shell commands</p>
Safemode doesn't allow to access constants.
Safemode doesn't allow shell command execution.
calling downcase on String... allow this?
calling strip on String... allow this?

Leave a comment

18 Comments

  1. Peter Cooper said February 5th, 2008 at 10:26 PM  

    Nice try, but it’s far from that easy! Try:

    = system(‘touch /tmp/helloworld’)

    It works.

    Or what about:

    = (eval “Kernel”).load(‘whatever’)

    :)

  2. Sven said February 5th, 2008 at 10:39 PM  

    Hi Peter,

    thanks for the hints!

    Well, obviously this isn’t sufficient. But I believe it might be possible … and that’s what I’m so excited about :)

    Those lines you name produce the following trees:

    p RubyParser.new.parse("system('touch /tmp/helloworld')")
    s(:fcall, :system, s(:array, s(:str, "touch /tmp/helloworld")))
    
    p RubyParser.new.parse("(eval 'Kernel').load('whatever')")
    s(:call, s(:fcall, :eval, s(:array, s(:str, "Kernel"))), :load, 
    s(:array, s(:str, "whatever")))
    

    Both contain the :fcall token which obviously is something that should (and easily can) be forbidden, too.

    Unfortunately I haven’t been able to find any documentation for those node type names that RubyParser spits out. The RubyParser source might have some additional hints here.

    Do you happen to know more examples like these?

    Thanks again!

  3. Peter Cooper said February 5th, 2008 at 11:05 PM  

    (Your Mephisto is crashing when I send my full post, so I am hacking about till it posts.. this may mean my examples no longer work or may have extra spaces in them!)

    The reason I know about some of these is I’ve worked with Why’s “Sandbox” quite a bit and tried to make my own, and there are all sorts of horrible things you can do :( I am not so familiar with the internals RubyParser is providing access to, which makes this quite an interesting exercise, and one I might have a play with!

    Another nasty example:

    • self.freeze
  4. Peter Cooper said February 5th, 2008 at 11:06 PM  

    How about?

    = 5.send(:PUT A BACKTICK HERE, ‘ls’)

    Or a classic from C Erler:

    = $stdout. class.for_fd($stdout.class.sysopen(‘/ etc/ passwd’)). read(4096)

    You might find this old thread useful:

    http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-core/6604?6473-6830+split-mode-vertical

    If you are preventing the use of constants, you are actually solving a reasonable amount of the problem in a public sense but the gigantic warren hiding in the background is quite imposing.

  5. Sven said February 5th, 2008 at 11:18 PM  

    Mephisto is crashing? Woha!

    Would you mind to send me your full post by email so I can fix this?

    Also, I’d love to see your examples … maybe we just continue this discussion by email? I’m still more comfortable with my email client than a blog comment textarea ;)

    You’re right that for a real sandbox way more measurements might be necessary. (I’ve also looked at Why’s sandbox once, but obviously one can’t rely on it for a common templating engine … not yet at least?)

    For a template system I believe it’s easier because we can hook into the template locals/variables assignment process and whitelist allowed methods there (that’s what Liquid does and I think that should be adaptable).

    Also, something like self.freeze could be turned off by having Haml evaluate the code inside of a restrictive Object like Rails’ blankslate?

  6. Ryan Davis said February 7th, 2008 at 12:32 AM  

    Noooooooooooooooooooooooooooooooooooooooooooooo!

  7. Sven said February 7th, 2008 at 09:10 AM  

    Hi Ryan!

    LOL … I almost spoiled my coffee over the keyboard.

    Nooooo?

    So that means you’re less thrilled with this idea I guess. Mind to explain that a bit?

  8. Sven said February 7th, 2008 at 09:46 AM  

    Peter, your second comment was waiting for moderation and I missed that, sorry.

    Yeah, 1.send(‘system’, ‘ls -a’) actually works … interesting.

    Thanks!

  9. jack said January 23rd, 2011 at 10:46 AM  

    This looks very interesting indeed. I wish there were more exposure for this and more people out there using and talking about it. cheap vps

  10. QQQ said February 7th, 2011 at 06:33 PM  

    Finally we kissed and the passion scale went sky high and I knew I was onto a good thing - sex was a certainty free porn videos. She never hesitated when I began to fondle her breasts and she willingly exposed them for me mobile porn. They were firm and I suspected a breast enhancement but said nothing - they still felt good and I was enjoying them and gradually working my way further south free porn tube. She was a step ahead of me and before I could completely undress her she moved on me atk hairy and I was suddenly having my pants pulled down and I was enjoying one of he best cock sucking hairy pussy experiences I had ever had. ABB728019394

  11. taddyhelg said March 1st, 2011 at 11:34 PM  

    Very cool idea, but don’t you think you’re kind of re-inventing the wheel? products for premature ejaculation ditropan ditropan 5 mg

  12. ???? ?????? said April 20th, 2011 at 01:47 PM  

    I believe it’s easier because we can hook into the template locals/variables assignment process and whitelist allowed methods there ???? ??????

  13. morris said May 7th, 2011 at 10:53 AM  

    Liquid templates for Mephist what a wonderful idea

    sharepoint support services

  14. morris said May 7th, 2011 at 10:53 AM  

    Liquid templates for Mephist what a wonderful idea good one

    sharepoint support services

  15. Okey oyunu said May 12th, 2011 at 03:41 PM  

    Tüm dünya artik okey oyunu oynuyor. Yillardir bir çok oyun programi olmasina ragmen, içlerinden en güzeli olarak nitelendirebilecegimiz tek bir site göze çarpmaktadir. Diger tüm okey oyunu programlarinin aksine ücretsiz olmasi ve 3 boyutlu olarak hizmet vermesi mükemmel bir gelismedir. Sizlerde www.okey-oyunu.com adresinden bu essiz okey oyununu indirebilirsiniz. Kullanimi çok basit ve Türkçe dil seçenegi ile kolaylikla oyuna baslayabilirsiniz. Ister kendi ülkenizden, isterseniz dünyanin tüm farkli bölgelerinden dilediginiz oyun odalarini seçerek, oyuna hemen baslayabilirsiniz. Okey oyunu oynamak için artik arkadas bile aramaniza gerek kalmadan, bilgisayarinizdan 100 binlerce üye ile online olarak okey oyununu oynamanin zevkine varabilirsiniz.

  16. Hanns said May 13th, 2011 at 01:46 PM  

    Hahaha so funny..

    “So, with Liquid you can still sleep at night without any worries that some bastard might have included a bit of code into a theme that sends your password files to the russian mafia,”

    It better be safe! yay for Haml! Thanks sven :) Hanns at 240 litre wheelie bins

  17. porno said May 23rd, 2011 at 10:40 AM  

    good comment. thanks you friends.

    I’ve surfed the net more than three hours today, however, I haven’t found such useful information. Thanks a lot, it is really useful to me

  18. porno said May 23rd, 2011 at 10:41 AM  

    good comment. thanks you friends.

    I’ve surfed the net more than three hours today, however, I haven’t found such useful information. Thanks a lot, it is really useful to me

Sorry, comments are closed for this article.

artweb design
Sven Fuchs
Grünberger Str. 65
10245 Berlin, Germany


http://www.artweb-design.de

Fon +49 (30) 47 98 69 96
Fax +49 (30) 47 98 69 97