URL encoding and other hackery

Alexander Staubo alex at purefiction.net
Sun Feb 17 17:54:18 MSK 2008


On 2/17/08, Adam Doppelt <amd at urbanspoon.com> wrote:
> I am using nginx as the front end to a rails cluster. When rails
> generates a page I write the page to disk, where nginx can look for it
> later. I want to use something like this:

We're doing the same thing.

> if (-f $document_root/$uri)

This works:

    if (-f $request_filename) {
      break;
    }

> But I anticipate a few problems:
>
> 1) the uri might include ".." or similar hackery

It's up to you to ensure that the saved file corresponds to the file
name Nginx generates in $request_filename. Nginx will URL-decode the
path, which means that

  http://example.com/buttons/a%2f..%2fbutton.png

will resolve to

  $document_root/buttons/a/../button.png

which is expanded to

  $document_root/buttons/button.png

...which is the file name that Rails' cache_page method will use.

In other words it's possible to generate URLs which may end up outside
your designated document root. The risk is not Nginx that could try to
serve stuff beyond the document root, but that the Rails app might
write its cache file in unexpected places.

I don't know if cache_page validates the file name to ensure it's
within $RAILS_ENV/public. It certainly ought to. There's also the risk
that cache_page could overwrite *other* files within the public
directory, of course, such as stuff in public/images.

> 2) the uri might include query parameters

If so you will need to create an Nginx variable to appends the query
string to the end of the $request_filename. But a better option is to
rely on Rails routes. Here's a typical route we use for rendering
buttons:

  map.connect 'cache/button/:id', :controller => "theme", :action => 'button',
    :requirements => {:id => /.*/}

This will map a URL such as this:

  http://example.com/cache/button/style=green;text=Click+me.png

to a controller action as well as a nicely readable file name within
our cache directory. In the controller we parse the file name and do
the rendering:

  def button
    options = Button.parse_options(params[:id])
    button = Button.new(options)
    ...
    data = button.render.to_blob
    send_data(data, :type => button.content_type, :disposition => "inline")
    cache_page(data)
  end

Alexander.





More information about the nginx mailing list