Subresource Integrity
Subresource Integrity (SRI) is a security feature that enables browsers to verify that resources they fetch (for example, from a CDN) are delivered without unexpected manipulation. It works by allowing you to provide a cryptographic hash that a fetched resource must match.
How Subresource Integrity helps
Websites sometimes choose to rely on a third party such as a Content Delivery Network (CDN) to host some of their resources, rather than self-host all their resources. For example, a document served from https://example.com might include a resource from another location:
<script src="https://not-example.com/script.js"></script>
This comes with a risk, in that if an attacker gains control of the third-party host, then they can inject arbitrary malicious content into its files (or replace the files completely). This is referred to as a supply chain attack.
Subresource Integrity is a defense against attacks such as this, by ensuring that the files your web application fetches have exactly the contents that you expect them to have.
Using Subresource Integrity
You can use Subresource Integrity with:
<script>elements.<link>elements whoserelattribute value isstylesheet,preload, ormodulepreload.
Setting the integrity attribute
To use the feature, add the integrity attribute to the element. The value of integrity is a whitespace-separated list of cryptographic hashes of the content of the linked resource, where each hash is prefixed with an identifier for the hash algorithm used, followed by a dash, and ending with the actual base64-encoded hash value.
Currently the allowed prefixes are sha256, sha384, and sha512.
For example, the following markup adds an integrity attribute to a <script> element. The attribute contains two SHA-384 hashes, and two SHA-512 hashes:
<script
src="https://cdn.example.com/script.js"
integrity="
sha384-Tk2Yjg3YmYzMWNkZTdhMTFkM2FlNDg4ZjE3MzEzNTk3ZDlh
sha384-DEzZmZhMGFkMGQ0OTQ3MzZkNGY0OTg4NGIwN2ZiMMTM3YmQ
sha512-ZmQ5NjNiYWJjYTM3MjRhMGI4MTQzNWRmZTZkZGYyMzQyOGYYTZkYjBm
sha512-OGUwYThkZDc2YzFlZGI5MDEzZmZhMGFkMGQ0OTQ3MzZkNGYZTEzODk2"
crossorigin="anonymous"></script>
How browsers handle Subresource Integrity
When a browser encounters a <script> or <link> element with an integrity attribute, before executing the script or before applying any stylesheet specified by the <link> element, the browser must first compare the script or stylesheet to the expected hashes given in the integrity value.
The different hash functions have different strengths: from weaker to stronger, the order is SHA-256, SHA-384, SHA-512. When the browser downloads a resource with the integrity attribute set, it will first select the set of hashes that were generated using the strongest hash function present. That is, if the attribute contains values generated with SHA-256 and SHA-384, it will only use the hashes generated using SHA-384. It will ignore all other hashes.
The browser will then calculate the hash of the resource contents using the specified function, and compare the result with all the specified values: if the actual value matches any of the specified values, then the browser will load the resource, otherwise it will refuse to load the resource, and return a network error.
This means that developers can:
- Provide multiple values using different hash functions, and the browser will use only the strongest function provided.
- Provide multiple values using the same hash function, and the browser will validate the attribute if any of them match: this enables a developer to provide alternate versions of a resource, while still checking their integrity.
Subresource Integrity and CORS
Cross-origin requests that use subresource integrity must use the Cross-Origin Resource Sharing (CORS) protocol. That is, the server providing the resource must explicitly indicate to the browser that the requesting origin is allowed to access the resource. It does this by sending the appropriate Access-Control-Allow-Origin response header.
Often a CDN will use the wildcard value for this:
Access-Control-Allow-Origin: *
no-cors mode and the crossorigin attribute
As a consequence of the requirement to use CORS when requesting a resource with integrity, you must include the crossorigin attribute in your markup:
<script
src="https://cdn.example.com"
integrity="sha512-abcde"
crossorigin="anonymous"></script>
This is needed because, by default, a resource loaded from a document's HTML is loaded in no-cors mode:
<script src="https://cdn.example.com"></script>
<!-- loaded in no-cors mode -->
In no-cors mode, a cross-origin request will succeed even if the owner of the resource does not send the appropriate CORS headers, but the content of the response will not be shared with the requester. So a document can use a resource that it requested using no-cors, but isn't able to read it.
However, subresource integrity could enable an attacker to derive information about the content of a subresource, even when it's requested in no-cors mode. To do this, the attacker creates a page that:
- Requests the resource, providing a specific hash value as the
integrityvalue. - Monitors the success or failure of the resource load (for example, by listening for
errorevents).
The attacker then tricks the target user into loading the page: if the resource is successfully loaded, then the attacker knows that the resource has the contents that match the hash.
To prevent this attack, browsers will not allow no-cors requests to use subresource integrity, so a request like this will always fail:
<script src="https://cdn.example.com" integrity="sha512-abcde"></script>
Integrity policy
The Integrity-Policy and Integrity-Policy-Report-Only HTTP headers enable a document to enforce a policy regarding the integrity metadata requirements on loaded script and stylesheet subresources. In other words, the policy allows a website to require that the integrity attribute is specified for loaded resources.
When an Integrity-Policy header is specified, the browser blocks requests with no-cors mode or without an integrity attribute from being made, and will also report violations if a valid reporting endpoint is specified.
When an Integrity-Policy-Report-Only header is specified, the browser allows requests that violate the policy, but will report violations to the reporting endpoint (if a valid reporting endpoint is specified).
Developers would typically use Integrity-Policy-Report-Only as a first deployment step in their Integrity Policy journey, to ensure that all the scripts and stylesheets loaded in their documents have appropriate integrity metadata. Once they'd see that no violation reports are being received, they'd know that they can enable blocking using the Integrity-Policy header without risking user-facing breakage.
The header values are defined as structured field dictionaries with the following keys:
blocked-destinations-
Defines a list of request destinations to be blocked. The only allowed values are
scriptandstyle. sourcesOptional-
Defines a list of integrity sources. The default and only currently supported value is
inline. As a result, addingsources=(inline)to the header has a similar effect as omittingsources. endpointsOptional-
Defines a list of reporting endpoints. The reporting endpoints need to be defined in a
Reporting-Endpointsheader.
In cases where a request is blocked by an integrity policy, a Reporting API violation report is created with a type property of integrity-violation and the structure defined by IntegrityViolationReport that includes information such as the URL of the document and the blocked resource.
A typical report might look like this
{
"type": "integrity-violation",
"url": "https://example.com",
"body": {
"documentURL": "https://example.com",
"blockedURL": "https://example.com/main.js",
"destination": "script",
"reportOnly": false
}
}
Tools for generating SRI hashes
>SRI Hash Generator
The SRI Hash Generator is an online tool you can use to generate SRI hashes.
Using OpenSSL
You can generate SRI hashes from the command-line using OpenSSL with a command invocation such as:
cat FILENAME.js | openssl dgst -sha384 -binary | openssl base64 -A
In a Windows environment, you can create a tool for generating SRI hashes with the following code:
@echo off
set bits=384
openssl dgst -sha%bits% -binary %1% | openssl base64 -A > tmp
set /p a= < tmp
del tmp
echo sha%bits%-%a%
pause
To use that code:
- Save that code in a file named
sri-hash.batin the Windows SendTo folder in your environment (for example,C:\Users\USER\AppData\Roaming\Microsoft\Windows\SendTo). - Right-click a file in the File Explorer, select Send to…, and then select
sri-hash. You will see the integrity value in a command box. - Select the integrity value and right-click to copy it to the Clipboard.
- Press any key to dismiss the command box.
Note: If OpenSSL is not installed on your system, visit the OpenSSL project website for information about downloading and installing it. The OpenSSL project does not itself host binary distributions of OpenSSL, but does maintain an informal list of third-party distributions: https://github.com/openssl/openssl/wiki/Binaries.
Using shasum
You can generate SRI hashes using shasum with a command invocation such as:
shasum -b -a 384 FILENAME.js | awk '{ print $1 }' | xxd -r -p | base64
- The pipe-through
xxdstep takes the hexadecimal output fromshasumand converts it to binary. - The pipe-through
awkstep is necessary becauseshasumwill pass the hashed filename in its output toxxd. That can have disastrous consequences if the filename happens to have valid hex characters in it — becausexxdwill also decode that and pass it tobase64.
Examples
>Subresource Integrity with the <script> element
This example adds an integrity attribute to a <script> element. The attribute contains four hashes: two calculated using SHA-384, and the other two using SHA-512. The browser will:
- select the two SHA-512 hashes
- hash the file contents using SHA-512
If the result matches either of the two SHA-512 hashes listed, then the browser will load and execute the script: otherwise it will return a network error.
<script
src="https://cdn.example.com/script.js"
integrity="
sha384-Tk2Yjg3YmYzMWNkZTdhMTFkM2FlNDg4ZjE3MzEzNTk3ZDlh
sha384-DEzZmZhMGFkMGQ0OTQ3MzZkNGY0OTg4NGIwN2ZiMMTM3YmQ
sha512-ZmQ5NjNiYWJjYTM3MjRhMGI4MTQzNWRmZTZkZGYyMzQyOGYYTZkYjBm
sha512-OGUwYThkZDc2YzFlZGI5MDEzZmZhMGFkMGQ0OTQ3MzZkNGYZTEzODk2"
crossorigin="anonymous"></script>
Note:
For more details on the purpose of the crossorigin attribute, see CORS settings attributes.
Integrity enforcement with the Integrity-Policy header
You can add the Integrity-Policy header to your document to ensure that the external resources it loads (in this case, scripts) are loaded with integrity (and aren't loaded with no-cors mode)
Integrity-Policy: blocked-destinations=(script), endpoints=(integrity-endpoint, some-other-integrity-endpoint)
If you're unsure that all the external scripts have integrity metadata, you can enable the report-only version of the feature and start getting reports of violations.
You can do that with the Integrity-Policy-Report-Only header.
Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(integrity-endpoint, some-other-integrity-endpoint)