PHP triky

Weblog o elegantním programování v PHP pro mírně pokročilé

Trusted Types

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)

Adminer 4.7.1

Nová verze Admineru opravuje několik drobných problémů předchozí verze:

Jakub Vrána, Adminer, 24.1.2019, diskuse: 3 (nové: 3)

Adminer 4.7.0

Největší změnou Admineru 4.7.0 je přidání ovladače pro databázi ClickHouse, i když ta asi moc používaná není. Další změny jsou drobnější:

Málem jsem přidal i podporu generovaných sloupců v MySQL, ale pull request byl plný nesouvisejících změn. To je poměrně běžný problém – pokud posíláte pull request do jakéhokoliv projektu, soustřeďte se jen na to, čeho chcete dosáhnout. Pokud narazíte na nesouvisející problém, vytvořte samostatný pull request. Je velké utrpení prohlížet diff a tápat, proč asi nějaká změna (která navíc vypadá jako k horšímu) byla provedena a jestli je nezbytně nutná.

Jakub Vrána, Adminer, 24.11.2018, diskuse: 3 (nové: 3)

Adminer 4.6.3

Nejdůležitější změnou v Admineru 4.6.3 je zakázání příkazu LOAD DATA LOCAL INFILE v MySQL. S jeho pomocí jde totiž načíst obsah libovolného souboru, ke kterému má přístup uživatel, pod kterým běží webový server.

Podobně zásadní změnou je zákaz používání databází bez hesla. Už v minulosti jsem zakázal přístup bez hesla k SQLite, teď to platí pro všechny databáze. Chápu, že třeba na vývojových serverech nepřístupných zvenčí to může být trochu opruz, ale detekce toho, zda jde o vývojový server, by byla nespolehlivá (např. za reverzní proxy), proto jsem to zakázal všude. Máte tři možnosti, jak se s tímto omezením vypořádat.

Další změny jsou drobnější:

Jakub Vrána, Adminer, 28.6.2018, diskuse: 15 (nové: 15)

Dokazatelná bezpečnost

Na konferenci devel.cz jsem přednášel o tom, jak v Google o programech dokazujeme, že nemají XSS a některé další zranitelnosti. Slajdy a záznam jsou teď volně k dispozici.

S přednáškou jsem byl spokojen, jen jsem mohl uvést víc příkladů. Tak alespoň odkážu ukázku conformance pravidla.

Jakub Vrána, Výuka, 22.6.2018, diskuse: 2 (nové: 2)

Starší články naleznete v archivu.

avatar © 2005-2019 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.