Trusted Types

Školení, která pořádám

Poslední dobou pracuji hodně na Trusted Types, což je návrh, jak zabránit DOM XSS. Pomocí Trusted Types lze nastavit, že DOM API, která se dají použít pro spuštění kódu (jako např. přiřazení do innerHTML nebo zavolání document.write), už nebudou přijímat řetězec (který mohl být podstrčen útočníkem), ale jen jeden z Trusted Types (např. TrustedHTML u API pracujících s HTML), který jiná část aplikace bezpečně vytvořila.

Trusted Types už podporuje prohlížeč Google Chrome, pokud se spustí pomocí google-chrome-unstable --enable-blink-features=TrustedDOMTypes (přepínače fungují i se stabilní verzí Chrome, jen nejsou opraveny některé chyby). V ostatních prohlížečích lze použít polyfill. Trusted Types mají ambici stát se běžným webovým API dostupným ve všech prohlížečích a použitelným ve všech aplikacích.

Typy

Jednotlivé typy v Trusted Types jsou jen jednoduché obálky nad řetězci. Existují čtyři typy:

TrustedHTML
Fragment HTML kódu, přiřaditelný např. do innerHTML.
TrustedScript
Kus JavaScriptu, přiřaditelný např. do script.text.
TrustedScriptURL
URL, ze kterého se nahrává spustitelný kód, přiřaditelné např. do script.src.
TrustedURL
URL nespustitelného zdroje, přiřaditelné např. do a.href.

Hodnoty těchto typů se dají vytvořit jedině voláním funkcí v TrustedTypesPolicy, která se získá pomocí TrustedTypes.createPolicy(name, policy):

var unsafePolicy = TrustedTypes.createPolicy('unsafe', {
	// funkce vrací řetězec, ze kterého prohlížeč vytvoří TrustedHTML
	createHTML: function (s) {
		return s;
	}
});
el.innerHTML = unsafePolicy.createHTML('<b>test</b>');

Takto vytvořená policy sama o sobě moc užitečná není, vytvoří TrustedHTML z jakékoliv řetězce. Vtip je ale v tom, že referenci na ni si můžeme držet jen v kódu, o kterém víme, že je bezpečný, např. v goog.html.SafeHtml. Policy daného jména můžeme vytvořit jen jednu. Při zavolání TrustedTypes.createPolicy se jménem, které už jsme použili, dojde k chybě.

Kromě toho můžeme vytvořit i obecně použitelné policy, které např. sanitizují HTML kód, ověří, jestli spustitelné URL pochází z našeho serveru, nebo zkontrolují, jestli protokol obecného URL je HTTP nebo HTTPS. Takto obecně použitelné policy můžeme zveřejnit pomocí třetího parametru funkce createPolicy a následně je získat pomocí TrustedTypes.getExposedPolicy(name):

TrustedTypes.createPolicy('sanitize', {
	createHTML: function (html) {
		return sanitizeHTML(html);
	},
	createScriptURL: function (url) {
		const parsed = new URL(url, document.baseURI);
		if (parsed.origin == 'https://mycdn.example') {
			return URL.toString();
		}
		throw new TypeError('Invalid URL.');
	},
	createURL: function (url) {
		const parsed = new URL(url, document.baseURI);
		if (/^https?:$/.test(parsed.protocol)) {
			return URL.toString();
		}
		throw new TypeError('Invalid URL.');
	}
}, /* exposed= */ true);
var sanitizePolicy = TrustedTypes.getExposedPolicy('sanitize');

No a konečně lze definovat tzv. default policy, která se automaticky použije, pokud se DOM API zavolají s řetězcem:

TrustedTypes.createPolicy('default', {
	createHTML: function (html) {
		return 'default:' + html;
	}
}, /* exposed= */ true);
el.innerHTML = 'test'; // přiřadí 'default:test'

Default policy lze použít v přechodném období, kdy aplikace na Trusted Types přechází. Typicky vrátí řetězec nezměněný a případně provede nějaké logování. Po této přechodné fázi bych doporučoval default policy vypnout a všude pracovat s Trusted Types explicitně. Případně lze default policy použít pro knihovny třetích stran, které Trusted Types zatím nepodporují.

Jak byste asi uhodli, policy umožňuje definovat čtyři funkce odpovídající jednotlivým typům: createHTML, createScript, createScriptURL a createURL.

Vynucení

Trusted Types můžeme používat, aniž bychom prohlížeč museli nějak konfigurovat (pokud je podporuje nebo pokud jsme použili polyfill). Pokud ale chceme zvýšit bezpečnost, musíme nastavit, které policy dovolíme ve své aplikaci používat. To se v Chrome dělá pomocí hodnoty trusted-types v hlavičce Content-Security-Policy:

<?php
// povolí vytváření jakýchkoliv policies
header("Content-Security-Policy: trusted-types *");

// povolí vytvoření policies s názvy 'default' a 'sanitize'
header("Content-Security-Policy: trusted-types default sanitize");
?>

Hodnota * povolí jakékoliv policies. Při pokusu použít v omezených DOM API řetězec se zkusí zavolat default policy. Pokud neexistuje, dojde k chybě. Kromě * lze uvést i mezerami oddělené názvy povolených policies. Při pokusu vytvořit jinou policy dojde k chybě.

Do budoucna se pravděpodobna způsob vynucování Trusted Types přesune do samostatné hlavičky Trusted-Types. To konec konců určuje i specifikace.

Závěr

Trusted Types vypadá jako velmi nadějný způsob, jak vyřešit DOM XSS. Kromě toho máme samozřejmě i server XSS, ke kterému dochází, pokud nedůvěryhodný kód do stránky vypíše přímo server.

Moje práce na Trusted Types je velmi různorodá. Spočívá v úpravách specifikace a polyfillu, opravách kódu Chromium, rozšíření conformance pravidel, úpravách Closure Library, změnách JavaScript externs, změnách TypeScript deklarací a samozřejmě také úpravách aplikací Google, aby Trusted Types začaly používat. Projekt vede koto a kromě mě na něm pracuje ještě několik dalších vývojářů.

Jakub Vrána, Seznámení s oblastí, 28.1.2019, diskuse: 0 (nové: 0)

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.