Anton Bachin 20f62d209e Convert most examples to opam
Reason examples still use esy.
2023-11-29 18:18:33 +03:00
..
2023-11-29 18:18:33 +03:00
2021-04-05 00:08:28 +03:00
2023-11-29 18:18:33 +03:00
2022-02-11 15:39:39 +03:00
2023-11-29 18:18:33 +03:00

7-template


Dream templates allow interleaving OCaml and HTML in a straightforward way, and help with XSS protection. After looking at a secure example, we will weaken and then exploit it.

let render param =
  <html>
  <body>
    <h1>The URL parameter was <%s param %>!</h1>
  </body>
  </html>

let () =
  Dream.run
  @@ Dream.logger
  @@ Dream.router [

    Dream.get "/:word"
      (fun request ->
        Dream.param request "word"
        |> render
        |> Dream.html);

  ]
$ cd example/7-template
$ opam install --deps-only --yes .
$ dune exec --root . ./template.exe

This requires a bit more setup in our dune file to run the template preprocessor:

(executable
 (name template)
 (libraries dream)
 (preprocess (pps lwt_ppx)))

(rule
 (targets template.ml)
 (deps template.eml.ml)
 (action (run dream_eml %{deps} --workspace %{workspace_root})))

The substitution, <%s param %>, uses Printf conversion specifications from the standard library. So, you can do things like this:

  • <%i my_int %> to print an OCaml int.
  • <%02x my_hex_int %> to print an int in hexadecimal, with at least two characters, left-padded with zeroes.

If your template file is mostly HTML, you can give it a name like template.eml.html, to trigger HTML syntax highlighting by your editor.


Security

The template automatically passes strings through Dream.html_escape before inserting them into the output. This only applies to formats that can emit dangerous characters: %s, %S, %c, %C, %a, and %t.

You can suppress the hidden call to Dream.html_escape using !; for example, <%s! param %>. You may want to do this if your data is already escaped, or if it is safe for some other reason. But be careful!


To show the danger, let's launch a script injection (XSS) attack against this tiny Web app! First, go to template.eml.ml, change the substitution to <%s! param %>, and restart the app. You can also make the edit in the playground. Then, visit this highly questionable URL:

http://localhost:8080/%3Cscript%3Ealert(%22Impossible!%22)%3C%2Fscript%3E

If you are using the playground, change the host and port accordingly.

This URL will cause our Web app to display an alert box, which we, as the developers, did not intend!

XSS example

Despite all the URL-escapes, you may be able to see that the URL contains a complete <script> tag that runs a potentially arbitrary script. Our app happily pastes that script tag into HTML, causing the script to be executed by our clients!

If you change the substitution back to <%s param %>, and visit that same URL, you will see that the app safely formats the script tag as text:

XSS prevented


In general, if you are not using the templater, you should pass any text that will be included in HTML through Dream.html_escape, unless you can guarantee that it does not contain the characters <, >, &, ", or '. Also, always use quoted attribute values — the rules for escaping unquoted attributes are much more invasive.

Likewise, escaping inline scripts and CSS is also more complicated, and not supported by Dream.



Next steps:

  • 8-debug shows how to turn on debug responses, and get more info about errors.
  • 9-error sets up a central error template for all errors.
  • r-template is a Reason syntax version of this example.

See also:

  • w-template-files moves the template into a separate .eml.html to avoid problems with editor support.
  • w-template-logic shows how to put control flow into templates.
  • w-tyxml shows how to use TyXML, a different templater that uses OCaml's type system to prevent emitting many kinds of invalid HTML.
  • r-tyxml if you are using Reason. You can use TyXML with JSX syntax server-side!
  • w-template-stream streams templates to responses, instead of building up complete response strings.

Up to the tutorial index