
Hi Habrahabr! This article will demonstrate the effect of using some important and exotic HTTP headers, most of which are related to security.
X-XSS-Protection
An XSS (cross-site scripting) attack is a type of attack in which malicious code can be injected into an attacked page.
For example, like this:
<h1>Hello, <script>alert('hacked')</script></h1>
This type of attack is easy to detect and the browser can easily deal with it: if the source code contains a part of the request, then this may be a threat.
')
And the
X-XSS-Protection header controls this browser behavior.
Accepted values:
- 0 filter off
- 1 filter included. If an attack is detected, the browser will remove the malicious code.
- one; mode = block . The filter is enabled, but if an attack is detected, the page will not be loaded by the browser.
- one; report = http: // domain / url . the filter is turned on and the browser clears the page of malicious code, while reporting an attempted attack. It uses the Chromium function to send a report on the violation of content protection policy (CSP) to a specific address.
Create a web server sandbox on node.js to see how this works.
var express = require('express') var app = express() app.use((req, res) => { if (req.query.xss) res.setHeader('X-XSS-Protection', req.query.xss) res.send(`<h1>Hello, ${req.query.user || 'anonymous'}</h1>`) }) app.listen(1234)
I will use Google Chrome 55.
No title
http://localhost:1234/?user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E
Nothing happens, the browser successfully blocks the attack. Chrome, by default, blocks the threat and reports this to the console.

He even highlights the problem area in the source code.

X-XSS-Protection: 0
http://localhost:1234/?user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E&xss=0
Oh no!

X-XSS-Protection: 1
http://localhost:1234/?user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E&xss=1
The page has been cleared due to an explicit title.

X-XSS-Protection: 1; mode = block
http://localhost:1234/?user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E&xss=1;%20mode=block
In this case, the attack will be prevented by blocking the page loading.

X-XSS-Protection: 1; report = http: // localhost: 1234 / report
http://localhost:1234/?user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E&xss=1;%20report=http://localhost:1234/report
The attack was prevented and the message was sent to the appropriate address.

X-Frame-Options
With this header you can protect yourself from the so-called Clickjacking [Clickjacking].
Imagine that an attacker has a YouTube channel and wants more subscribers.
He can create a page with the “Do not click” button, which will mean that everyone will click on it. But over the button there is an absolutely transparent iframe and in this frame there is a channel page with a subscription button. Therefore, when you click on the button, the user actually subscribes to the channel, unless of course he was logged into YouTube.
Let's demonstrate it.
First you need to install an
extension to ignore this header.
Create a simple page.
<style> button { background: red; color: white; padding: 10px 20px; border: none; cursor: pointer; } iframe { opacity: 0.8; z-index: 1; position: absolute; top: -570px; left: -80px; width: 500px; height: 650px; } </style> <button>Do not click his button!</button> <iframe src="https://youtu.be/dQw4w9WgXcQ?t=3m33s"></iframe>

As you can see, I placed a frame with a subscription right above the button (
z-index: 1 ) and therefore, if you try to click on it, the frame is actually pressed. In this example, the frame is not completely transparent, but this is corrected by the
opacity value
: 0 .
In practice, this will not work, because YouTube has the right title, but I hope the meaning of the threat is clear.
To prevent a page from being used in a frame, use the
X-Frame-Options header.
Accepted values:
- deny does not load the page at all.
- sameorigin does not load if the source does not match.
- allow-from: DOMAIN, you can specify the domain from which the page can be loaded into the frame.
We will need a web server for demonstration.
var express = require('express') for (let port of [1234, 4321]) { var app = express() app.use('/iframe', (req, res) => res.send(`<h1>iframe</h1><iframe src="//localhost:1234?h=${req.query.h || ''}"></iframe>`)) app.use((req, res) => { if (req.query.h) res.setHeader('X-Frame-Options', req.query.h) res.send('<h1>Website</h1>') }) app.listen(port) }
No title
Everyone will be able to embed our website at
localhost: 1234 into the frame.

X-Frame-Options: deny
The page generally can not be used in the frame.

X-Frame-Options: sameorigin
Only pages with the same source will be able to embed in the frame. The sources match if the domain, port, and protocol are the same.

X-Frame-Options: allow-from localhost : 4321
It seems that Chrome ignores this option, because there is a Content-Security-Policy header (it will be described below). It does not work in Microsoft Edge.
Below is Mozilla Firefox.

X-Content-Type-Options
This header prevents attacks with the substitution of MIME types (
<script src = "script.txt"> ) or unauthorized hotlink (
<script src = "https://raw.githubusercontent.com/user/repo/branch/file.js"> )
var express = require('express') var app = express() app.use('/script.txt', (req, res) => { if (req.query.h) res.header('X-Content-Type-Options', req.query.h) res.header('content-type', 'text/plain') res.send('alert("hacked")') }) app.use((req, res) => { res.send(`<h1>Website</h1><script src="/script.txt?h=${req.query.h || ''}"></script>`) }) app.listen(1234)
No title
http:
Although
script.txt is a text file with type
text / plain , it will run as a script.

X-Content-Type-Options: nosniff
http://localhost:1234/?h=nosniff
This time the types do not match and the file will not be executed.

Content-Security-Policy
This is a relatively young headline and helps reduce the risks of XSS attack in modern browsers by specifying in the header what resources can be loaded on the page.
For example, you can ask the browser not to execute inline skrpity and download files from only one domain. Inline skrpity can look not only as
<script> ... </ script> , but also as
<h1 onclick = "..."> .
Let's see how it works.
var request = require('request') var express = require('express') for (let port of [1234, 4321]) { var app = express() app.use('/script.js', (req, res) => { res.send(`document.querySelector('#${req.query.id}').innerHTML = ' ${req.query.id}-'`) }) app.use((req, res) => { var csp = req.query.csp if (csp) res.header('Content-Security-Policy', csp) res.send(` <html> <body> <h1>Hello, ${req.query.user || 'anonymous'}</h1> <p id="inline"> inline-?</p> <p id="origin"> origin-?</p> <p id="remote"> remote-?</p> <script>document.querySelector('#inline').innerHTML = ' inline-'</script> <script src="/script.js?id=origin"></script> <script src="//localhost:1234/script.js?id=remote"></script> </body> </html> `) }) app.listen(port) }
No title
It works the way you expected.

Content-Security-Policy: default-src 'none'
http://localhost:4321/?csp=default-src%20%27none%27&user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E
default-src applies the rule for all resources (images, scripts, frames, etc.), the value
'none' blocks everything. Below is a demonstration of what is happening and the errors shown in the browser.

Chrome refused to run any scripts. In this case, it will not even work to download
favicon.ico .
Content-Security-Policy: default-src 'self'
http://localhost:4321/?csp=default-src%20%27self%27&user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E
Now you can use resources from one source, but still you can not run external and inline-scripts.

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
http://localhost:4321/?csp=default-src%20%27self%27;%20script-src%20%27self%27%20%27unsafe-inline%27&user=%3Cscript%3Ealert(%27hacked%27)%3C/script%3E
This time we allowed the execution and inline-scripts. Please note that the XSS attack in the request was also blocked. But this will not happen if both
unsafe-inline and
X-XSS-Protection are simultaneously put
: 0 .

Other values
The site
content-security-policy.com beautifully shows many examples.
- default-src 'self' will only allow resources from one source
- script-src 'self' www.google-analytics.com ajax.googleapis.com will allow Google Analytics, Google AJAX CDN and resources from one source.
- default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; allow images, scripts, AJAX and CSS from one source and prevent the download of any other resources. For most sites, this is a good initial setup.
I did not check this, but I think the following headings are equivalent:
- frame-ancestors 'none' and X-Frame-Options: deny
- frame-ancestors 'self' and X-Frame-Options: sameorigin
- frame-ancestors localhost: 4321 and X-Frame-Options: allow-from localhost : 4321
- script-src 'self' without 'unsafe-inline' and X-XSS-Protection: 1
If you look at the headers of facebook.com or twitter.com, you will notice that these sites use a lot of CSP.
Strict-Transport-Security
HTTP Strict Transport Security (HSTS) is a security policy mechanism that protects a site from an unsafe connection attempt.
Let's say we want to connect to
facebook.com . If you do not type
https: // before the request, then the protocol, by default, will be selected HTTP and therefore the request will look like
http://facebook.com .
$ curl -I facebook.com HTTP/1.1 301 Moved Permanently Location: https://facebook.com/
After that, we will be redirected to a secure version of Facebook.
If you connect to a public WiFi point that belongs to an attacker, then the request can be intercepted and instead of facebook.com, the attacker can substitute a similar page to find out the login and password.
To protect against such an attack, you can use the above header, which tells the client to use the
https version of the site next time.
$ curl -I https://www.facebook.com/ HTTP/1.1 200 OK Strict-Transport-Security: max-age=15552000; preload
If a user was logged in to Facebook at home, and then tried to open it from an unsafe access point, then nothing threatens him, because browsers remember this header.
But what will happen if you connect to an insecure network for the first time? In this case, the defense will not work.
But browsers have a trump card in this case. They have a predefined list of domains for which you should only use HTTPS.
You can send your domain to
this address. There you can also find out if the title is used correctly.
Accepted values:
- max-age = 15552000 time, in seconds, which the browser should remember about the title.
- includeSubDomains If you specify this optional value, the header also applies to all subdomains.
- preload if the site owner wants the domain to fall into a predefined list supported by Chrome (and used by Firefox and Safari).
And if you need to switch to HTTP before the expiration of the
max-age, or if the
preload is set? You can set the value
max-age = 0 and then the rule for switching to the https version will stop working.
Public key pins
HTTP Public Key Pinning (HPKP) is a security policy mechanism that allows HTTPS sites to protect themselves from fraudulent or fraudulent certificates.
Accepted values:
- Pin-sha256 = "<sha256>" in quotes is Base64-encoded fingerprint Subject Public Key Information (SPKI). You can specify multiple pins for different public keys. In the future, some browsers may use other hashing algorithms besides SHA-256.
- max-age = <seconds> the time, in seconds, that the browser remembers that only the listed keys should be used to access the site.
- includeSubDomains if you specify this optional parameter, the header applies to all subdomains.
- report-uri = "<URL>" if you specify a URL, then in case of a key verification error, the corresponding message will be sent to the specified address.
Instead of the
Public-Key-Pins header, you can use the
Public-Key-Pins-Report-Only , in this case, only key matching error messages will be sent, but the browser will still load the page.
Facebook does this:
$ curl -I https://www.facebook.com/ HTTP/1.1 200 OK ... Public-Key-Pins-Report-Only: max-age=500; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; pin-sha256="q4PO2G2cbkZhZ82+JgmRUyGMoAeozA+BSXVXQWB8XWQ="; report-uri="http://reports.fb.com/hpkp/"
Why do you need it? Are not trusted certificate authorities (CA) enough?
An attacker can create a certificate for facebook.com and, by deceit, force the user to add it to their trust store or he can be an administrator.
Let's try to create a certificate for facebook.
sudo mkdir /etc/certs echo -e 'US\nCA\nSF\nFB\nXX\nwww.facebook.com\nno@spam.org' | \ sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/certs/facebook.key \ -out /etc/certs/facebook.crt
And make it trusted on the local system.
And now we will launch a web server using this certificate.
var fs = require('fs') var https = require('https') var express = require('express') var options = { key: fs.readFileSync(`/etc/certs/${process.argv[2]}.key`), cert: fs.readFileSync(`/etc/certs/${process.argv[2]}.crt`) } var app = express() app.use((req, res) => res.send(`<h1>hacked</h1>`)) https.createServer(options, app).listen(443)
Switch to server
echo 127.0.0.1 www.facebook.com | sudo tee -a /etc/hosts sudo node server.js facebook
Let's see what happened
$ curl https://www.facebook.com <h1>hacked</h1>
Fine.
curl confirms the certificate.
Since I already went to Facebook and Google Chrome saw his headlines, he should report the attack but allow the page, right?

Nope Keys were not checked due to local root certificate [Public-key pinning bypassed]. It is interesting…
Well, what about
www.google.com ?
echo -e 'US\nCA\nSF\nGoogle\nXX\nwww.google.com\nno@spam.org' | \ sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/certs/google.key \ -out /etc/certs/google.crt sudo cp /etc/certs/*.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates certutil -A -t "C,," -n "Google" -d sql:$HOME/.pki/nssdb -i /etc/certs/google.crt echo 127.0.0.1 www.google.com | sudo tee -a /etc/hosts sudo node server.js google
The same result. I think this is a feature.
But in any case, if you don’t add these certificates to your local storage, you won’t be able to open sites, because there is no option to continue an insecure connection in Chrome or add an exception in Firefox.


Content-Encoding: br
The data is compressed using
Brotli .
The algorithm promises better compression than gzip and a comparable unarching speed.
Supported by Google Chrome .
Of course, for him there is a module in node.js.
var shrinkRay = require('shrink-ray') var request = require('request') var express = require('express') request('https://www.gutenberg.org/files/1342/1342-0.txt', (err, res, text) => { if (err) throw new Error(err) var app = express() app.use(shrinkRay()) app.use((req, res) => res.header('content-type', 'text/plain').send(text)) app.listen(1234) })
Initial size: 700 Kb
Brotli: 204 Kb
Gzip: 241 Kb


Timing-Allow-Origin
Using the
Resource Timing API, you can find out how long it took to process resources on a page.
Since load time information can be used to determine whether a user has visited a page before (taking note that resources can be cached), the standard is considered vulnerable if such information is given to any hosts.
<script> setTimeout(function() { console.log(window.performance.getEntriesByType('resource')) }, 1000) </script> <img src="http://placehold.it/350x150"> <img src="/local.gif">
It seems that if you do not specify
Timing-Allow-Origin , then you can get detailed information about the time of operations (domain search, for example) only for resources with one source.

You can use it like this:
- Timing-Allow-Origin: *
- Timing-Allow-Origin: http://foo.com http://bar.com
Alt-svc
Alternate Services [Alternative Services] allow resources to be located in different parts of the network and can be accessed using different protocol configurations.
This is used by Google:
- alt-svc: quic = ": 443"; ma = 2,592,000; v = "36,35,34"
This means that the browser, if it wishes, can use
QUIC , this is HTTP over UDP, through port 443 for the next 30 days (ma = 2592000 seconds, or 720 hours, that is, 30 days). I have no idea what the parameter
v , version means?
P3p
Below are a few P3P headers I have met:
Some browsers require third party cookies to support the P3P protocol to indicate privacy measures.
The organization that founded P3P, the World Wide Web Consortium (W3C), suspended work on the protocol several years ago because modern browsers do not fully support the protocol. As a result, P3P is outdated and does not include the technologies that are currently used on the network, so most sites do not support P3P.
I did not go too deep, but apparently the title is needed for IE8 to accept third party cookies.
For example, if privacy setting in IE is high, then all cookies from sites that do not have a compact privacy policy will be blocked, but those that have headers like the ones mentioned above will not be blocked.