0%

HTTPS, Certificates, and Security Headers

Headers That Instruct a Browser

The connection is in order: the client knows who holds the other end. Now look at what arrives over it. The page of your service holds text that came from requests, such as book titles. In Chapter 7 you put one html.escape call between a title and the page. If one is ever missed, a script that someone stored in a title runs in the reader’s browser, with the reader’s .

One line of defense is not much for a mistake that is so easy to make. A second line has to live in the browser, because the browser is where the script would run. The server can ask for it with a .

A policy is a list of sources

The header is Content-Security-Policy. Its value is a policy: a of directives, separated by semicolons. Each directive names one kind of thing a page does and lists the sources that are allowed for it. This is the policy your service will send with its page. The header is one line; here it is broken after its name to fit the page:

content-security-policy:
    default-src 'self'; form-action 'self'; frame-ancestors 'none'

Split it the way a browser does. Run this:

Try it

The first line is default-src allows ["'self'"]. default-src is the directive for everything a page loads: scripts, images, style sheets. 'self', with its quotes, means the page’s own , as you defined an origin in Chapter 8: scheme, host, and . So default-src 'self' says that this page may load things from its own origin and from nowhere else.

A policy changes nothing on the server and nothing on the connection. The server sends the page as it is. The header is an instruction, and only a browser carries it out.

wire.py fetches the page and its policy header. What does the policy change?

What default-src ‘self’ stops

Suppose the escaping is missed once. These are the two versions of a title inside its <span class="title"> in the page, escaped and not:

&lt;script src="https://other.example/steal.js"&gt;&lt;/script&gt;
<script src="https://other.example/steal.js"></script>

The second line is a script element. Under default-src 'self', a browser that follows the standard refuses to load it, because https://other.example is not the page’s origin.

An attacker does not need another site, though. The script can sit in the page itself, between the tags, or in an attribute such as onclick. Those are inline scripts. An inline script is loaded from no file, so 'self' does not cover it. A policy allows inline scripts only when it says so, with the source 'unsafe-inline'. The name is a warning. Your policy does not contain it, so a browser runs no inline script and no inline event handler on your page.

Here is a model of that decision. It covers two rules of a long standard: where a script file comes from, and whether a script is inline. Run it:

Try it

The first two lines are https://reading.example/app.js True and https://other.example/steal.js False. This policy works for your page because page.html has no script and no styles of its own. A larger page that needs inline scripts lists each one in its policy with a nonce or a hash, and a new policy can be tried in report-only mode first. Those are names to recognize.

A missed html.escape lets <script>alert(1)</script> into the page, with the code between the tags. The response says default-src 'self'. What does a browser do with it?

What does the last line of the second block print?

What it does not cover

default-src is a fallback for the directives about loading. Two of the things a page can do are not loading, and they do not fall back to it.

The first is where a form on the page may send its fields. Injected markup could change the form’s target to another site. form-action 'self' keeps every form of the page pointed at its own origin.

The second is who may show the page inside a frame. Another site can put your page in an invisible frame and trick a reader into clicking on it, which is called clickjacking. frame-ancestors 'none' tells the browser that no page may frame this one. An older header, X-Frame-Options, asks for the same.

Which of these does default-src 'self' leave open?

A second lock, not a replacement

The policy does not make escaping optional. Without html.escape, a title can still add markup: a heading, a link, a form that asks for a password. No script runs, and the page still shows what an attacker wrote. Escaping keeps the page correct. The policy limits the damage when escaping is missed once. And the policy only helps in a browser that applies it.

With this policy in place, is html.escape in render_page() still needed?

You can read the policy now. No response of your service carries it yet. The next lesson adds it, with three more headers, in one .