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 OCamlint
.<%02x my_hex_int %>
to print anint
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!
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:
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.