[racket] Escaping things in templates?

From: Michael W (mwilber at uccs.edu)
Date: Sun Oct 23 02:45:26 EDT 2011

[back to the list...]

Thanks for the clarification, Jay. xexpr->string is much cleaner.

What bothers me most is that I didn't even know this was
something to watch out for. I suspect new users are similarly
likely to be bitten, especially if they're coming from other
frameworks that protect them from potential issues like this.
(This is a testimonial, not a criticism.)

Here's a first stab at some potential docs for templates.scrbl.
(Sorry if it doesn't work; I've never written any scribble before
and am not sure how to test it without rebuilding all of racket):

 - - - - - - - - - - - - - - - - - - - - - - - - -

@section{Escaping}

Because templates are useful for many things (scripts, CSS, HTML,
etc), the web server does not know what kind of escaping to apply to
user-supplied input. This means that when templates are expanded, no
escaping is done by default. Beware of @emph{cross-site scripting}
vulnerabilities! For example, suppose a servlet serves the following
template where @racket[some-variable] is an input string supplied by
the client:

@verbatim[#:indent 2]{
 <html>
  <head><title>Fastest Templates in the West!</title></head>
  <body>
   @some-variable
  </body>
 </html>
}

If the server contains something like the following:

@racketblock{
 (let ([some-variable (get-input-from-user)])
   (include-template "static.htm"))
}

There is nothing to prevent an attacker from entering
@litchar["<script type=\"text/javascript\">...</script>"] to make the
template expand into:

@verbatim[#:indent 2]{
 <html>
  <head><title>Fastest Templates in the West!</title></head>
  <body>
   <script type="text/javascript">...</script>
  </body>
 </html>
}

Now the server will send the attacker's code to millions of innocent
users. To keep this from happening when serving HTML, use the
@xexpr->string function from the @xml module.

This can be done in the server logic:

@racketblock{
 (require xml)

 (let ([some-variable (xexpr->string (get-input-from-user))])
   (include-template "static.htm"))
}

Alternatively, make the template responsible for its own escaping:

@verbatim[#:indent 2]{
 <html>
  <head><title>Fastest Templates in the West!</title></head>
  <body>
   @(xexpr->string some-variable)
  </body>
 </html>
}

The improved version renders as:

@verbatim[#:indent 2]{
 <html>
  <head><title>Fastest Templates in the West!</title></head>
  <body>
    &lt;script type=\"text/javascript\"&gt;...&lt;/script&gt;
  </body>
 </html>
}

When writing templates, always remember to escape user-supplied input.

 - - - - - - - - - - - - - - - - - - - - - - - - -

3 hours ago, Jay McCarthy wrote:
> web-server/template and Scribble produce strings, that's why you
> create a response by plopping in the string from include-template. The
> Web Server has no idea what kind of string you making, so it imposes
> no special XML rules.
> 
> The template-less version your servlet does exactly what you seem to want:
> 
> #lang web-server/insta
> 
> (require web-server/templates)
> 
> (define (start req)
>   (let ([some-input
>          (if (exists-binding? 'some-input (request-bindings req))
>           (extract-binding/single 'some-input (request-bindings req))
>           "nothing yet")])
>     (response/xexpr
>      `(html
>        (head (title "Testing"))
>        (body
>         (h1 "You said:" ,some-input)
>         (form ([method "get"] [action "#"])
>               (input ([name "some-input"] [type "text"]))
>               (input ([type "submit"]))))))))
> 
> But if you want to use templates to produce XML strings and get XML
> quoting, then you have to quote explicitly by treating your user's
> string as if it is a string Xexpr (the representation for XML in the
> Web Server):
> 
> #lang web-server/insta
> 
> (require web-server/templates
>          xml)
> 
> (define (start req)
>   (let ([some-input
>          (xexpr->string
>           (if (exists-binding? 'some-input (request-bindings req))
>               (extract-binding/single 'some-input (request-bindings req))
>               "nothing yet"))])
>     (response/full 200 #"Okay" (current-seconds) TEXT/HTML-MIME-TYPE
>                    empty
>                    (list (string->bytes/utf-8 (include-template
> "static.htm"))))))
> 
> Notice the addition of the (require xml) and the single call to
> (xexpr->string ...) around the user's input.
> 
> Jay
> 
> On Sat, Oct 22, 2011 at 8:38 PM, Michael W <mwilber at uccs.edu> wrote:
> > Hello! Thanks for making racket! It's the coolest.
> >
> > The web server template documentation:
> > http://docs.racket-lang.org/web-server/templates.html has several
> > great examples of templates, but it doesn't mention whether
> > strings included in templates are entity-escaped XML or not.
> >
> > More seriously, searching for "escape" in the help desk doesn't
> > even mention anything useful. It looks like the only way of
> > creating templates that escape the variables included in them is
> > by adding (require xml/private/writer) to the top of your script
> > and saying @escape[foo escape-table] every single time you use a
> > variable in your templates! Surely there must be a better way?
> >
> > Am I doing it wrong? Take a look at this simple servlet, which
> > simply spits out what you type in a form:
> > https://gist.github.com/1306759 Try typing <u>test</u> for
> > example, or perhaps some javascript.
> >
> > Certain web frameworks try to implicitly protect you from these
> > kinds of mistakes. Take Django, for example.
> > https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs
> > The Django team are so upfront about their template system's
> > escaping behavior that it's literally the first thing they
> > mention in the first section of their documentation.
> >
> > Other template systems intentionally make it harder to include
> > unescaped content by using syntactic differentiation. Mustache,
> > for example, requires you to use triple braces like
> > {{{some-input}}} if you want to include some-input as unescaped.
> > https://github.com/janl/mustache.js/
> >
> > What's the best way of solving this? Obviously we can't change
> > scribble, but could we have, say, (include-template) add an
> > @escape[...] function to the namespace just before it evaluates
> > the template?
> >
> > If I'm missing something, please feel free to flame away. ;) If
> > not, I'd love to help fix this by writing a patch or some
> > documentation or something.
> >
> > Thanks again for such a nice framework.
> >
> > --
> > For the Future!
> >    _mike
> > _________________________________________________
> >  For list-related administrative tasks:
> >  http://lists.racket-lang.org/listinfo/users
> >
> 


-- 
Live long and prosper,
    _mike


Posted on the users mailing list.