Spring til indhold

Subresource Integrity

et hash en browser tjekker en fil mod

Hvad det er

Subresource Integrity lader en side sige på forhånd, præcis hvilke bytes et script eller et stilark skal indeholde, og får browseren til at kassere filen, hvis det, der kommer frem, er anderledes. Det forkortes som regel til SRI. Siden skriver en kryptografisk hashværdi af filen i en integrity-attribut på det script- eller link-element, der henter den. Når svaret kommer, kører browseren den samme hashfunktion over de bytes, den har modtaget, og sammenligner resultatet med værdien på elementet. Er de ens, kører filen eller styler siden som normalt. Er de forskellige, behandler browseren svaret som en netværksfejl: scriptet bliver aldrig kørt, og stilarket bliver aldrig anvendt.

Værdien består af navnet på en hashfunktion, en bindestreg og selve hashværdien i base64. Én kommando over den færdige fil laver den, og kører man den to gange, ser man, hvor lidt der skal til for at ændre svaret:

$ printf "alert('Hello, world.');" > hello.js
$ openssl dgst -sha384 -binary hello.js | openssl base64 -A
H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO
$ echo >> hello.js
$ openssl dgst -sha384 -binary hello.js | openssl base64 -A
qu8P7hCLFcBpas8BOnGmaANCkfXJIpFttDEaxOj0Pq9H6Q8NYjsMb+PVqwuJygVe

Med sha384- foran er det første output attributtens værdi for det lille script på én linje. Et enkelt linjeskift tilføjet til sidst giver en hashværdi, der intet har til fælles med den første, og en browser med den første værdi ville afvise den anden fil.

Specifikationen kender tre hashfunktioner, SHA-256, SHA-384 og SHA-512, alle fra SHA-2-familien, og kalder SHA-384 et godt udgangspunkt i skrivende stund. En attribut kan rumme flere hashværdier adskilt af mellemrum. Browseren vælger den stærkeste funktion blandt dem og godtager filen, når den stemmer med en hvilken som helst værdi lavet med den funktion. Det lader en side skifte til en stærkere algoritme eller godtage to udgaver af et bibliotek under et skifte, uden et tidsrum hvor siden kun bærer forkerte værdier.

Attributten findes på script og link, og samme værdi kan følge med i en Link-header i svaret, der forudindlæser en fil. Specifikationens gældende tekst er anden udgave, et W3C Working Draft fra arbejdsgruppen Web Application Security. Første udgave blev en W3C-anbefaling i 2016.

Hvorfor det betyder noget

En side, der henter et bibliotek fra et content delivery network eller fra en hvilken som helst vært, som ejerne ikke selv driver, giver den vært magt til at køre kode på siden. HTTPS beskytter filen på vej over nettet, men siger intet om, hvad værten valgte at sende i første omgang. Bliver der brudt ind i CDN’ets lager, bliver en konto, der udgiver til det, overtaget, eller bliver filen bag en velkendt adresse skiftet ud i stilhed, så kører hver side, der henter den, den nye kode med sidens fulde myndighed. Sidens egen kode har ingen mulighed for at se forskel.

SRI flytter den beslutning tilbage til siden. Hashværdien beregnes af sitets ejer over en fil, de har læst eller bygget, og den rejser inde i siden fra sitets egen server. En vært, der sender noget andet, får sin fil afvist i stedet for kørt. Siden mister en funktion, indtil nogen kigger efter, og det er en synlig fejl med en åbenlys årsag i stedet for en andens kode, der kører i stilhed.

Hashværdien låser også en version fast. En adresse som /lib/latest.js kan begynde at levere en ny udgave under en side når som helst, men hashværdien følger ikke med. En opgradering, ejeren aldrig valgte, bliver til en afvisning på ét bestemt element, meldt via en error-hændelse.

Det er værd at være præcis om, hvad SRI ikke gør. Den tjekker en fil mod en hashværdi, men den vurderer ikke, om filen er sikker. En hashværdi af et bibliotek, der allerede indeholder en fjendtlig linje, beskytter trofast den fjendtlige linje. Den lader inline-script være, for det er en opgave for en Content Security Policy. Og den stoler på den side, der bærer værdien, så selve siden skal stadig nå frem uændret.

Sådan ser det ud i praksis

Dette site bruger det på sit eget stilark. Sikkerhedssiden nævner Subresource Integrity på stilarket: bundtet får et fingeraftryk og en hash, når sitet bygges, så læserens browser kan verificere, at det kom frem uændret. Samme liste viser, hvorfor opsætningen kan forblive lille. Der er ét førstepartsscript på under en kilobyte og ingen tredjepartsforespørgsler, og en læser, der tæller værterne i browserens netværksfane, finder én, sitets egen.

Kolofonen beskriver den samme fil fra byggets side, som ét CSS-bundt, samlet og fingeraftrykt ved bygning og leveret med en SRI-hash. Fingeraftrykket lægger en hash af filens indhold ind i dens navn, så et ændret stilark får en ny adresse, og en browser aldrig forveksler en gammel kopi i cachen med den gældende. Integritetsværdien lader samtidig browseren tjekke de bytes, der ligger bag adressen. Bygget laver begge dele ud fra de samme bytes i samme trin, så hver udgivelse sender stilarket og dets hashværdi af sted sammen, og den hyppigste fejl nedenfor, en fil der ændres efter hashingen, har meget lidt plads til at opstå.

Hvor det går galt

Noget mellem bygget og browseren omskriver filen. Et CDN, der minificerer scripts, en proxy, der komprimerer svar om eller sprøjter noget ind i dem, eller et udrulningstrin, der kører efter hashingen, ændrer alle sammen bytes, og browseren afviser resultatet, selv om ingen har angrebet noget. Specifikationen lægger ansvaret hos mellemleddene: de skal holde hashværdien i trit med det, de leverer, eller levere originalen urørt, og den server, der leverer filen, bør sende Cache-Control: no-transform for at bede dem lade den være. Den sikre rækkefølge er at tage hashværdien til sidst, over præcis de bytes, der bliver leveret.

En fil fra en anden oprindelse kommer uden CORS. Specifikationen kræver, at et integritetstjek af en fil fra en anden oprindelse bruger CORS, og kalder et forsøg uden det en logisk fejl. Elementet skal derfor have crossorigin="anonymous", og værten skal svare med en header, der deler filen. Udelad én af delene, og browseren blokerer filen i stedet for at hente den ukontrolleret. Reglen beskytter andre sites: uden den kunne en angriber hente et privat svar med en gættet hashværdi og ud fra udfaldet lære for eksempel, om læseren er logget ind dér.

Hashværdien kommer over almindelig HTTP. SRI beskytter en side mod en kompromitteret filvært, aldrig mod nogen på netværksvejen. På en side, der leveres uden HTTPS, kan den, der kan omskrive filen undervejs, lige så godt omskrive eller slette hashværdien i siden, så specifikationen anbefaler kun at levere integritetsdata til en sikker kontekst.

Hashfunktionen er en, browseren springer over. Specifikationen kender kun SHA-256, SHA-384 og SHA-512 og anbefaler, at browsere afviser kendt svage funktioner som MD5 og SHA-1. Overraskelsen er, hvad der sker med en værdi uden for den liste: browseren ignorerer den, og en attribut, der kun består af ignorerede værdier, regnes for slet ikke at have nogen metadata, så filen indlæses ukontrolleret. Et byggeværktøj, der kan lave fingeraftryk med MD5, som Hugos kan, gør det let at sende sådan en værdi ud ved et uheld.

En afvist fil trækker siden med sig. Når tjekket fejler, sender browseren en error-hændelse på elementet, og scriptet eller stilarket mangler simpelthen. For et bibliotek, siden ikke kan køre uden, foreslår specifikationen at fange hændelsen og hente en reservekopi fra en betroet, om end langsommere, kilde, fx sitets egen server. Uden en sådan reserve virker beskyttelsen som tiltænkt, og læseren får alligevel en ødelagt side.

Kun de elementer, der bærer en hashværdi, bliver tjekket. En integrity-attribut på ét script siger intet om det næste, som nogen tilføjer uden, og en gennemgang af de scripts, der har en, afslører aldrig det, der mangler den. Udkastet tilføjer en svarheader, der dækker det fra serverens side. Integrity-Policy med blocked-destinations=(script) blokerer hvert eksternt script, der kommer uden integritetsdata, og Integrity-Policy-Report-Only lister dem først, så de kan rettes, før noget bliver blokeret.

Læs mere

Ordliste