https://www.michalspacek.cz/exporty/clankyMichal Špaček: Všechny články2023-11-22T17:29:18+01:00Michal Špačekhttps://www.michalspacek.cz/co-znamena-origin-site-etld-etld-plus-1-public-suffix-a-psl2023-11-20T22:30:00+01:002023-11-22T17:29:18+01:00<p>Říkáme tomu různě: weby, stránky, domény, servery, sajty, internety a
spoléháme se na to, že tomu ten druhej bude rozumnět. Možná a možná ne,
ale to se dá vždy doladit doplňujícím „počkej, jak jako server,
nemyslíš spíš web?“ Jenže různé specifikace a technické dokumenty tuto
vymoženost nemohou využít a tak se snaží věci nazývat správnými jmény
a jednotně. A to navíc tak skvěle, že pojmy jako origin, site, same origin,
same site, <abbr title="effective top-level domain">eTLD</abbr> a public suffix
se normálně ani nepřekládají, protože pak by tomu zas nikdo nerozuměl.
A jak s tím souvisí atraktivita subdomén?</p>Co znamená origin, site, <abbr
title="effective top-level domain">eTLD</abbr>, eTLD+1, public suffix
a PSL?<h3>Aktualizace článku</h3><ul><li><em><strong>22.11.</strong> Přidáno krátké info o cross-origin a cross-site požadavcích</em></li></ul><p>Zjistil jsem, že skoro v každém článku a přednášce některý
z těch termínů používám a než abych to pokaždé vysvětloval (a navíc
pokaždé jinak zjednodušeně), tak jsem se rozhodl z toho udělat tenhle
článek, na který budu moci odkazovat. Skoro celý by se dal vyjádřit
i následujícím obrázkem:</p>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/domain-etld-site-origin.png"
alt="Adresa https://www.cam.ac.uk a vyjádření TLD, eTLD, eTLD+1, Site, Origin"
width="720" height="288">
<p>Obrázek sice řekne víc než tisíc slov, ale tenhle článek jich má dva
tisíce deset</p>
</div>
<p>Do tajů URL a všech jeho částí nebudeme zabíhat, to bychom tu byli
ještě přiští století. Vysvětlíme si jen to co je v nadpisu, protože na
základě toho browser stanovuje určité hranice.</p>
<p>Představte si nějaké to jednoduché URL, třeba:</p>
<pre>https://www.example.com/foo/bar</pre>
<h2 id="domena">Doména</h2>
<p>Tohle je možná spíš jen pro opáčko, ale bude se nám to hodit později.
Doména v URL výše je <code>www.example.com</code> a má několik
úrovní:</p>
<ul>
<li>Top-level doména (<abbr title="top-level domain">TLD</abbr>) je
<code>com</code></li>
<li>Doména druhé úrovně (<abbr title="second-level domain">2LD</abbr>) je
<code>example.com</code></li>
<li>Třetí úrovně (<abbr title="third-level domain">3LD</abbr>) je
<code>www.example.com</code></li>
<li>A tak dále</li>
</ul>
<p>Místo domény může být i IP adresa, ale pro jednoduchost zůstaneme
u domén.</p>
<h2 id="registrovatelna-domena">Registrovatelná doména</h2>
<p>Registrovatelná doména je ta část domény, kterou si můžete
zaregistrovat nebo pronajmout ať už od registrátora domén nebo od jiného
subjektu, který poskytuje např. blogy na subdoménách. Odhlédhneme-li od
nějakého dalšího delegování, tak všechny subdomény pod tou
registrovatelnou, včetně ní, patří stejné organizaci.</p>
<p>Dalo by se také říci, že registrovatelná doména je to, co píšete do
registračního formuláře. Když chcete mít web na
<code>www.michalspacek.cz</code>, tak si taky registrujete jen
<code>michalspacek.cz</code>. Ovšem ne vždy se registrovatelná doména rovná
doméně druhé úrovně.</p>
<p>To je případ třeba Velké Británie, kde si univerzity a další
akademické instituce mohou <a
href="https://www.jisc.ac.uk/forms/acuk-domain-request-form">registrovat</a>
domény, které shodně končí na <code>ac.uk</code> (<em>ac</em> jako
<em>academia</em>) a liší se až doménou třetí úrovně. Příkladem budiž
dvě <a
href="https://en.wikipedia.org/wiki/Oxford%E2%80%93Cambridge_rivalry">rivalské</a>
univerzity <a href="https://www.ox.ac.uk/">Oxford</a> (<code>ox.ac.uk</code>) a
<a href="https://www.cam.ac.uk/">Cambridge</a> (<code>cam.ac.uk</code>). Další
časté koncovky jsou <code>co.uk</code> (pro komerční subjekty) a
<code>net.uk</code> (poskytovatelé připojení k Internetu), celkem jich je <a
href="https://en.wikipedia.org/wiki/.uk#Active">skoro 20</a>.</p>
<p>Stejné je to i v mnoha jiných zemích, často jsou k vidění např.
subdomény <code>….gov.něco</code> pro různé vládní organizace. Podobně
to platí i pro domény různých úložišť, jako třeba
<code>….s3.amazonaws.com</code> pro službu Amazon S3 nebo domény jako
<code>….blogspot.com</code>, <code>….blogspot.no</code>,
<code>….blogspot.com.au</code> a tuna dalších pro gůglovo Blogger.</p>
<div>
<table>
<thead>
<tr>
<th>Doména</th>
<th>Registrovatelná doména</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>example.com</code></td>
<td><code>example.com</code></td>
</tr>
<tr>
<td><code>www.example.com</code></td>
<td><code>example.com</code></td>
</tr>
<tr>
<td><code>foo.bar.example.com</code></td>
<td><code>example.com</code></td>
</tr>
<tr>
<td><code>ox.ac.uk</code></td>
<td><code>ox.ac.uk</code></td>
</tr>
<tr>
<td><code>www.ox.ac.uk</code></td>
<td><code>ox.ac.uk</code></td>
</tr>
<tr>
<td><code>talks.ox.ac.uk</code></td>
<td><code>ox.ac.uk</code></td>
</tr>
<tr>
<td><code>blogs.law.ox.ac.uk</code></td>
<td><code>ox.ac.uk</code></td>
</tr>
<tr>
<td><code>my.foo.s3.amazonaws.com</code></td>
<td><code>foo.s3.amazonaws.com</code></td>
</tr>
<tr>
<td><code>foo.blogspot.com.au</code></td>
<td><code>foo.blogspot.com.au</code></td>
</tr>
<tr>
<td><code>www.foo.blogspot.com.au</code></td>
<td><code>foo.blogspot.com.au</code></td>
</tr>
</tbody>
</table>
</div>
<p>Registrovatelným doménám se někdy hovorově říká i „naked domain“,
„base domain“, „root domain“ nebo třeba „apex domain“. Tyto výrazy
se snad nikdy nepřekládají do češtiny, protože pod některými těmi
výrazy by si mnozí jistě představili něco zcela jiného.</p>
<p>Registrovatelná doména je <a
href="https://url.spec.whatwg.org/#host-registrable-domain">definována v URL
standardu</a>.</p>
<h2 id="efektivni-top-level-domena-etld-a-etld-1">Efektivní top-level doména
(<abbr title="effective top-level domain">eTLD</abbr>) a <abbr
title="effective top-level domain">eTLD</abbr>+1</h2>
<p>Efektivní top-level doména, zkráceně <abbr
title="effective top-level domain">eTLD</abbr>, je doména o úroveň výše
než registrovatelná doména. eTLD je doména, pod kterou se registrují
další domény, které obvykle mají různé vlastníky. Často se <abbr
title="effective top-level domain">eTLD</abbr> rovná samotné <abbr
title="top-level domain">TLD</abbr>, ale v mnoha případech tomu tak není.
Pohledem na doménu není bohužel možné jednoduše algoritmicky ani
regulárním výrazem zjistit její efektivní top-level nebo registrovatelnou
doménu, ale existuje seznam <abbr
title="effective top-level domain">eTLD</abbr>, do nějž se můžeme
(strojově) podívat, viz dále.</p>
<p>Efektivní top-level doména <em>plus jedna</em> neboli <em><strong><abbr
title="effective top-level domain">eTLD</abbr>+1</strong></em>, je pak to samé
jako registrovatelná doména.</p>
<div>
<table>
<thead>
<tr>
<th>Doména</th>
<th><abbr title="effective top-level domain">eTLD</abbr></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>example.com</code></td>
<td><code>com</code></td>
</tr>
<tr>
<td><code>www.example.com</code></td>
<td><code>com</code></td>
</tr>
<tr>
<td><code>foo.bar.example.com</code></td>
<td><code>com</code></td>
</tr>
<tr>
<td><code>ox.ac.uk</code></td>
<td><code>ac.uk</code></td>
</tr>
<tr>
<td><code>www.ox.ac.uk</code></td>
<td><code>ac.uk</code></td>
</tr>
<tr>
<td><code>talks.ox.ac.uk</code></td>
<td><code>ac.uk</code></td>
</tr>
<tr>
<td><code>blogs.law.ox.ac.uk</code></td>
<td><code>ac.uk</code></td>
</tr>
<tr>
<td><code>foo.blogspot.com.au</code></td>
<td><code>blogspot.com.au</code></td>
</tr>
<tr>
<td><code>www.foo.blogspot.com.au</code></td>
<td><code>blogspot.com.au</code></td>
</tr>
</tbody>
</table>
</div>
<p><abbr title="effective top-level domain">eTLD</abbr> nemůže být
registrovatelná, jinými slovy: registrovatelná doména z <abbr
title="effective top-level domain">eTLD</abbr> je <em>nic</em>.</p>
<h2 id="public-suffix-list-psl">Public suffix list (PSL)</h2>
<p><abbr title="public suffix list">PSL</abbr> je seznam efektivních top-level
domén, <abbr title="effective top-level domain">eTLD</abbr>, akorát tady jim
říkají „public suffix“, protože jsou to v podstatě přípony veřejně
přímo registrovatelných domén. Soubor je dostupný z webu <a
href="https://publicsuffix.org/">publicsuffix.org</a> (<a
href="https://publicsuffix.org/list/public_suffix_list.dat">přímý link</a>) a
aktualizuje se pomocí „pull requestů“ <a
href="https://github.com/publicsuffix/list">na GitHubu</a>.</p>
<p>Část kódu, která se snaží najít <abbr
title="effective top-level domain">eTLD</abbr> třeba pro
<code>www.foo.blogspot.com.au</code>, se do toho seznamu musí podívat a odzadu
zjistit jestli:</p>
<ul>
<li>Je <code>au</code> „public suffix“? <a
href="https://github.com/publicsuffix/list/blob/9f004d9fc2f9e3fef3682c9245653fa669c7ed0a/public_suffix_list.dat#L222">Je</a>,
podobně jako jiné národní domény (<abbr
title="country code top-level domain">ccTLD</abbr>).</li>
<li>Je <code>com.au</code> „public suffix“? <a
href="https://github.com/publicsuffix/list/blob/9f004d9fc2f9e3fef3682c9245653fa669c7ed0a/public_suffix_list.dat#L224">Je</a>.</li>
<li>Je <code>blogspot.com.au</code> „public suffix“? Taky <a
href="https://github.com/publicsuffix/list/blob/9f004d9fc2f9e3fef3682c9245653fa669c7ed0a/public_suffix_list.dat#L13489">je</a>.</li>
<li>Je <code>foo.blogspot.com.au</code> „public suffix“? Ne, není.</li>
<li>Je <code>www.foo.blogspot.com.au</code> „public suffix“? Taky ne.</li>
</ul>
<p>Výsledkem tedy je, že efektivní <abbr title="top-level domain">TLD</abbr>
(<abbr title="effective top-level domain">eTLD</abbr>) je
<code>blogspot.com.au</code>, registrovatelná doména a zároveň <abbr
title="effective top-level domain">eTLD</abbr>+1 je
<code>foo.blogspot.com.au</code>.</p>
<p>Ve skutečnosti je to trochu složitější, protože v seznamu se
vyskytují i záznamy s <code>*</code> a <code>!</code>, jako <a
href="https://github.com/publicsuffix/list/blob/9f004d9fc2f9e3fef3682c9245653fa669c7ed0a/public_suffix_list.dat#L752-L753">např.</a>
<code>*.ck</code> (<code>[cokoliv].ck</code> je <abbr
title="effective top-level domain">eTLD</abbr>) a <code>!www.ck</code> (…
kromě <code>www.ck</code>), ale to pro jednoduchost vynecháme.</p>
<p>Je také potřeba projít celou doménu, nestačí se zastavit na prvním
„ne“, protože by se klidně mohlo stát, že se na „public suffix“
narazí až někde později: <abbr
title="effective top-level domain">eTLD</abbr> domény
<code>soubory.s3.amazonaws.com</code> je <code>s3.amazonaws.com</code>, protože
stejně <a
href="https://github.com/publicsuffix/list/blob/9f004d9fc2f9e3fef3682c9245653fa669c7ed0a/public_suffix_list.dat#L837">jako
<code>com</code></a>, tak i <a
href="https://github.com/publicsuffix/list/blob/9f004d9fc2f9e3fef3682c9245653fa669c7ed0a/public_suffix_list.dat#L11678"><code>s3.amazonaws.com</code>
je „public suffix“</a>, ale jen <code>amazonaws.com</code> není.</p>
<p>Prohlížeče <a href="https://publicsuffix.org/learn/">používají</a>
public suffix list nejčastěji k omezení cookies, aby nešly nastavit
s platností pro všechny domény s příponou <code>.co.uk</code> apod.
Firefox seznam používá i ke zvýraznění domén v adresním řádku.</p>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/firefox119-url-domain-highlight.png"
alt="URL řádek s adresou https://www.cambridgestudents.cam.ac.uk ve Firefoxu 119, eTLD+1 je zvýrazněna"
width="496" height="60">
<p>Zvýraznění <abbr title="effective top-level domain">eTLD</abbr>+1 ve
Firefoxu 119</p>
</div>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/chrome119-url-domain-highlight.png"
alt="URL řádek s adresou https://www.cambridgestudents.cam.ac.uk v Chrome 119, eTLD+1 bez zvýraznění"
width="468" height="69">
<p>Pro srovnání stejná doména v Chrome 119</p>
</div>
<p>Public suffix list používají i certifikační autority při vydávání
tzv. wildcard certifikátů pro HTTPS, nesmí totiž vydat certifikát pro
např. <code>*.co.uk</code>. Můžete ho použít i vy, třeba k varování
vašich uživatelů ve smyslu „tohle asi nechcete dělat s touto doménou“,
ideálně ale k ničemu dalšímu. <a
href="https://publicsuffix.org/learn/">Knihovny</a> existují pro spoustu
jazyků, osobně mám zkušenost s knihovnou <a
href="https://github.com/jeremykendall/php-domain-parser">PHP Domain Parser</a>,
která umí použít i lokálně cachovaný seznam.</p>
<h2 id="origin">Origin</h2>
<p>Pojmem <em>origin</em> se označuje ta část URL, která obsahuje protokol
(přesněji schéma), doménu a port.</p>
<div>
<table>
<thead>
<tr>
<th>URL</th>
<th><em>Origin</em></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>https://www.example.com/foo</code></td>
<td><code>https://www.example.com</code></td>
</tr>
<tr>
<td><code>https://www.example.com:303/foo</code></td>
<td><code>https://www.example.com:303</code></td>
</tr>
</tbody>
</table>
</div>
<p>Všimněte si, že <em>origin</em> nekončí lomítkem, tím už naopak
začíná cesta (<em>path</em>) <code>/foo</code>.</p>
<h2 id="same-origin">Same origin</h2>
<p>Pojmem <em>same-origin policy</em> označujeme několik různých omezení,
která dovolují dvou věcem nějak dohromady fungovat jen a pouze pokud mají
„same origin“ vztah, zjednodušeně řečeno pokud mají stejné
<em>origin</em>y.</p>
<p><em>Same-origin policy</em> se používá i např. k omezení JavaScriptu
běžícího v <code>iframe</code> (typicky třeba jako součást nějaké
reklamy), aby nemohl krást, pardon, číst z rodičovského dokumentu např.
pomocí <code>window.parent.document.getElementById('username')</code>, pokud
ten <code>iframe</code> a rodičovský dokument nemají stejný <em>origin</em>.
V takovém případě to nelze ani obráceně, rodičovský dokument nemůže
přistupovat k obsahu <code>iframe</code> (např. pomocí
<code>document.getElementById('iframe').contentWindow.document.getElementById('username')</code>).</p>
<p>✔️</p>
<ul>
<li><code>https://example.com/foo</code> a <code>https://example.com/bar</code>
jsou „same origin“</li>
<li><code>https://foo.bar.baz.test/foo</code> a
<code>https://foo.bar.baz.test/bar</code> jsou „same origin“, na doméně
nezáleží, může to být <code>.com</code> i <code>.cokoliv</code>, jen musí
být v obou případech stejná</li>
</ul>
<p>❌</p>
<ul>
<li><code>https://example.com/foo</code> a
<code>https://www.example.com/bar</code> nejsou „same origin“
(<code>example.com</code> a <code>www.example.com</code> není stejná
doména)</li>
<li><code>https://example.com/foo</code> a <code>http://example.com/bar</code>
nejsou „same origin“ (<code>https</code> a <code>http</code> nejsou stejné
protokoly)</li>
<li><code>https://example.com:4431/foo</code> a
<code>https://example.com:4432/bar</code> nejsou „same origin“ (mají
rozdílné porty)</li>
</ul>
<p>Další informace o <em>originu</em> i <em>same originu</em> podané
o něco formálnějším jazykem se dozvíte <a
href="https://html.spec.whatwg.org/multipage/browsers.html#origin">v HTML
specifikaci</a>.</p>
<h2 id="site">Site</h2>
<p>Pojmem „site“ se často obecně až skoro hovorově označuje celý web,
stránky, server, nebo tak něco. Pokud se ovšem ponoříme do trochu
striktnějších vod, tak <em>site</em> znamená podmnožinu URL, do které
patří protokol (schéma) a registrovatelná doména.</p>
<div>
<table>
<thead>
<tr>
<th>URL</th>
<th><em>Site</em></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>https://www.example.com/foo/bar</code></td>
<td><code>https</code> a <code>example.com</code></td>
</tr>
<tr>
<td><code>https://neco.nek.de.example.com/</code></td>
<td><code>https</code> a <code>example.com</code></td>
</tr>
<tr>
<td><code>http://www.ox.ac.uk/</code></td>
<td><code>http</code> a <code>ox.ac.uk</code></td>
</tr>
<tr>
<td><code>https://staff.admin.ox.ac.uk/</code></td>
<td><code>https</code> a <code>ox.ac.uk</code></td>
</tr>
<tr>
<td><code>https://www.cam.ac.uk/</code></td>
<td><code>https</code> a <code>cam.ac.uk</code></td>
</tr>
<tr>
<td><code>https://my.foo.s3.amazonaws.com/</code></td>
<td><code>https</code> a <code>foo.s3.amazonaws.com</code></td>
</tr>
<tr>
<td><code>https://foo.blogspot.com.au/</code></td>
<td><code>https</code> a <code>foo.blogspot.com.au</code></td>
</tr>
<tr>
<td><code>https://www.foo.blogspot.com.au/</code></td>
<td><code>https</code> a <code>foo.blogspot.com.au</code></td>
</tr>
</tbody>
</table>
</div>
<h2 id="same-site">Same site</h2>
<p>Dvě URL nebo dva <em>originy</em> jsou „same site“, pokud se rovnají
jejich <em>site</em>s, tedy když mají stejné protokoly (schéma) a
registrovatelné domény. Na rozdíl od <em>same originu</em>, v tomto
případě nezáleží na portu ani na celé doméně.</p>
<p>✔️</p>
<ul>
<li><code>https://example.com/foo</code> a <code>https://example.com/bar</code>
jsou „same site“ (i „same origin“), jejich <em>site</em> je
<code>https</code> a <code>example.com</code></li>
<li><code>https://example.com/foo</code> a
<code>https://foo.bar.example.com/bar</code> jsou „same site“ (ale ne
„same origin“), <em>site</em> je shodně <code>https</code>
a <code>example.com</code></li>
<li><code>https://www.example.com/foo</code> a
<code>https://foo.bar.example.com/bar</code> jsou „same site“, <em>site</em>
obou je <code>https</code> a <code>example.com</code></li>
<li><code>https://www.cam.ac.uk/</code> a
<code>https://www.cambridgestudents.cam.ac.uk/</code> jsou „same site“,
jejich <em>site</em> je <code>https</code> a <code>cam.ac.uk</code></li>
</ul>
<p>❌</p>
<ul>
<li><code>https://example.com/foo</code> a <code>http://example.com/bar</code>
mají různé protokoly a tak nejsou „same site“ přestože jejich
registrovatelná doména je stejná</li>
<li><code>https://www.ox.ac.uk/</code> a <code>https://www.cam.ac.uk/</code>
nejsou „same site“, mají různé registrovatelné domény
<code>ox.ac.uk</code> a <code>cam.ac.uk</code></li>
<li><code>https://example.com/</code> a <code>https://example.net/</code> nejsou
„same site“, mají různé registrovatelné domény <code>example.com</code>
a <code>example.net</code></li>
<li><code>https://example.com./</code> a <code>https://example.com/</code>
nejsou „same site“, mají různé registrovatelné domény
<code>example.com.</code> a <code>example.com</code>, ta tečka na konci je
podstatná</li>
</ul>
<p>„Same site“ kontroly používá např. browser pro omezení cookies,
které v požadavku buď odešle, nebo ne, podle toho, zda jste na odkaz
kliknuli na stejném <em>site</em> jako je <em>site</em> cílové adresy, dle
nastavení cookie atributu <code>SameSite</code>.</p>
<p>Pojmy <em>site</em> i <em>same site</em> jsou detailněji popsány v <a
href="https://html.spec.whatwg.org/multipage/browsers.html#sites">HTML
specifikaci</a>. Za zmínku stojí snad i to, že existuje <em>„same site“
porovnávání bez schématu</em> (<a
href="https://html.spec.whatwg.org/multipage/browsers.html#schemelessly-same-site">schemelessly
same site</a>), při němž se porovnávají jen registrovatelné domény. To se
kdysi používalo pro cookies, ale pak se přešlo na „schemeful same site“
porovnávání, ve kterém schéma také hraje roli, a kterému dnes říkáme
prostě jen „same site“.</p>
<h2 id="cross-origin-a-cross-site-pozadavky">Cross-origin a cross-site
požadavky</h2>
<p>Požadavkům, které vznikly na jednom <em>originu</em>, ale načítají
něco z jiného <em>originu</em>, říkáme „cross-origin“ požadavky, je
to v podstatě opak „same originu“.</p>
<p>Podobně „cross-site“ požadavky, opak „same site“, vznikly na jednom
<em>site</em>, ale načítají něco z jiného <em>site</em>. Jen bacha na to,
že někde, např. v názvech útoků jako Cross-Site Scripting (XSS) nebo
Cross-Site Request Forgery (CSRF), se „site“ používá v tom obecném
významu, ne ve výše uvedeném významu „schéma + registrovatelná
doména“.</p>
<h2 id="radeji-same-origin">Raději <em>same origin</em></h2>
<p>Koncept <em>site</em> a public suffixů a jejich seznamu tedy vyžaduje
nějakou manuální práci, aby co nejvíce odpovídal aktuální realitě, a
trochu tak připomíná toho <a href="https://xkcd.com/2347/">jednoho borce
z Nebrasky</a>. Pokud byste psali nějakou specifikaci, tak je vhodnější
použít spíš <em>origin</em> a <em>same origin</em>, jinak se definice
<em>site</em> může lišit browser od browseru, protože každý z nich může
používat jinou verzi <abbr title="public suffix list">PSL</abbr>.</p>
<p>Ten sice spravuje Mozilla, tvůrce Firefoxu, takže ty „velké“
prohlížeče i ty „menší“ na nich postavené budou asi v pohodě, ale
i tak je dobré zvážit to, jestli chcete bezpečnost vašeho díla založit
na tom, jestli někdo přidá řádek do nějakýho souboru a jestli si ten
soubor někdo jiný včas stáhne. <a
href="https://github.com/sleevi/psl-problems#should-i-use-the-public-suffix-listetld1-for-">Nechcete</a>.</p>
<p>Použití „same site“ také zvyšuje atraktivitu subdomén pro
útočníky. Převzetí subdomén („subdomain takeover“) je <a
href="https://developer.mozilla.org/en-US/docs/Web/Security/Subdomain_takeovers">reálný
útok</a>, kterým útočníci mohou obejít různá omezení používající
„same site“ porovnávání. K tomu všemu jim může stačit i jen
zapomenutá subdoména, která se na nic nepoužívá.</p>
<p>Raději tedy <em>same origin</em>.</p>https://www.michalspacek.cz/prepisovani-obsahu-http-odpovedi-v-chrome2023-10-05T00:00:00+02:002023-10-13T03:37:30+02:00<p>Prohlížeč Chrome (a další, např. Edge) umožňuje přepisovat obsah
i hlavičky HTTP odpovědí. O <a
href="https://www.michalspacek.cz/prepisovani-http-hlavicek-v-odpovedich-v-chrome">přepisování
HTTP hlaviček</a> pro testovací účely jsem psal <a
href="https://www.michalspacek.cz/prepisovani-http-hlavicek-v-odpovedich-v-chrome">již
dříve</a>, jak přepsat i samotné tělo odpovědi si ukážeme níže. Od
Chrome 117 (vyšel v září 2023) se to navíc značně zjednodušilo.</p>Přepisování obsahu HTTP odpovědí v Chrome<h3>Aktualizace článku</h3><ul><li><em><strong>13.10.</strong> Chrome 118 přidal sloupec <em>Has overrides</em>
i filtr <code>has-overrides</code></em></li></ul><p>Zkusíme si přepsat tělo nějaké ukázkové stránky, třeba
example.com:</p>
<ol>
<li>Otevřete Developer Tools (např. pomocí F12)</li>
<li>Přejděte do záložky <em>Network</em></li>
<li>Jděte na <a href="https://example.com">example.com</a></li>
<li>Vyberte odpověď, kterou chcete změnit</li>
<li>Klikněte pravým tlačítkem a zvolte <em>Override content</em></li>
</ol>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/override-content.png"
alt="Přepisování obsahu v DevTools v záložce Network" width="650"
height="767"></div>
<p>Možností jak zapnout přepisování je více, jde to třeba
i z podzáložky Response:</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/override-content-response.png"
alt="Přepisování obsahu v DevTools v záložce Response" width="600"
height="360"></div>
<h2 id="povoleni-pristupu">Povolení přístupu</h2>
<p>Pokud jste zatím žádné přepisování nenastavovali, tak po vás bude
Chrome chtít vybrat adresář, do kterého má to přepisování ukládat.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/select-override-folder.png"
alt="Dialog pro vybrání adresáře se zobrazuje v/nad DevTools" width="500"
height="130"></div>
<p>Zvolte nějaký prázdný adresář nebo nějaký nový vytvořte a
nezapomeňte do něj povolit přístup. Výběr a povolení se děje v jiných
proužcích, ten druhý je pod adresním řádkem, a je docela snadné ho
přehlédnout, věřte mi.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/devtools-requests-full-access.png"
alt="DevTools požaduje přístup do vybraného adresáře" width="630"
height="150"></div>
<h2 id="prepsani-obsahu">Přepsání obsahu</h2>
<p>Samotné úpravy můžete provádět v Developer Tools v záložce
<em>Sources</em> v podzáložce <em>Overrides</em> (klikněte na
<code>»</code> kdyby nebyla vidět), nezapomeňte upravený soubor vždy
uložit třeba pomocí <code>Ctrl</code>+<code>S</code>.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/overriden-content-in-sources.png"
alt="Přepisovaný obsah v Sources" width="650" height="270"></div>
<p>Můžete také použít váš oblíbený editor a přímo upravovat soubory
uložené v adresáři vybraném výše, změny se promítnou i do Chrome.</p>
<p>Všimněte si, že přepisovaný obsah je v DevTools označen modrou tečkou
na ikonce souboru a na zapnuté přepisování upozorní ikonka ⚠️
v záložce <em>Network</em>:</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/override-content-network.png"
alt="Modrá tečka na ikonce a žlutý trojúhelník na záložce" width="360"
height="200"></div>
<p>Od Chrome 118 můžete v záložce <em>Network</em> přidat sloupeček
<em>Has overrides</em> (klikněte pravým na záhlaví sloupců) a využít i
<code>has-overrides</code> filtr:</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/has-overrides.png"
alt="Filtr has-overrides a sloupeček Has overrides" width="720"
height="290"></div>
<p>Zkuste si tedy zapnout přepisování souboru <code>index.html</code> na
doméně <code>example.com</code>, změňte třeba obsah značky
<code>H1</code>, soubor uložte a stránku načtěte znovu. Pokud máte
otevřené Developer Tools, tak byste měli vidět vaši úpravu. U mě to
dopadlo takto, přičemž tohle se na té originální stránce opravdu
nepíše 😁</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/override-content-example.png"
alt="example.com s přepsaným HTML kódem" width="640" height="280"></div>
<p>Se zavřenými DevTools se vše chová normálně, k žádnému
přepisování nedochází i když ho předtím nakonfigurujete.</p>
<h2 id="prepsani-jsonu">Přepsání JSONu</h2>
<p>Je možné přepsat nejen HTML, ale také JSON nebo obrázky. U JSONu se to
dělá stejně, v <em>Networku</em> vyberte odpověď, kterou chcete upravit,
pod pravým tlačítkem zvolte <em>Override content</em> a JSON upravte dle
libosti.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/override-content-json.png"
alt="Úprava JSONu pro přepis" width="670" height="270"></div>
<p>Já jsem to zkusil třeba na testovací aplikaci <a
href="https://httpbin.org/">httpbin.org</a>, kde jsem pro jednoduchost vybral
vyzkoušení HTTP metody <code>POST</code> a pak jsem přepsal obsah odpovědi z
<code>https://httpbin.org/post</code>. Při dalším AJAXovém požadavku, tady
vyvolaném kliknutím na <em>Execute</em>, si Chrome sám sobě odpověděl
lokálně uloženým obsahem, což se projevilo hláškou „can't parse
JSON“, protože jsem ten soubor upravil schválně trochu nešťastně.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/overriden-content-json.png"
alt="Upravený a přepsaný JSON v odpovědi" width="600" height="430"></div>
<h2 id="prepsani-obrazku">Přepsání obrázků</h2>
<p>Přepisování obsahu obrázků je v podstatě stejné, jen vybraný soubor
nelze editovat přímo v Chrome. Ten ho po zvolení <em>Override content</em>
uloží do adresáře s obsahem pro lokální přepis do umístění, které
odpovídá cestě v URL. Obrázek pak můžete upravit nebo nahradit jak budete
potřebovat a při dalším načtení se pak použije ten uložený soubor,
pokud budete mít otevřené DevTools.</p>
<h2 id="zruseni-prepisovani">Zrušení přepisování</h2>
<p>V <em>Sources</em> > <em>Overrides</em> můžete přepisování
checkboxem vypnout, pravým tlačítkem a volbou <em>Delete</em> smazat
konkrétní přepis, případně smazat celou konfiguraci kliknutím na 🚫,
což znamená <em>zruš povolení přístupu k adresáři s informacemi
o přepisech</em>.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-content-overrides/delete-override-content.png"
alt="Smazání přepisování" width="440" height="530"></div>
<p>Občas některé aplikace spoléhají na to, že ze serveru dostanou
<em>něco</em> a tohle přepisování jim tu představu může trochu nabourat.
Hodit se to může pro testování, ale také třeba pro natáčení různých
videí, kdy chcete ukázat nějakou funkčnost, ke které se nedá jednoduše
doklikat.</p>https://www.michalspacek.cz/platnost-https-certifikatu-od-uzivatelem-pridanych-ca-je-v-podstate-omezena-na-2-roky2023-08-18T23:30:00+02:002023-08-18T23:30:00+02:00<p>Od roku 2020 je platnost HTTPS certifikátů omezena na 1 rok, přesněji
398 dní, o historii a důvodech jsem tu <a
href="https://www.michalspacek.cz/maximalni-delka-platnosti-https-certifikatu-bude-zkracena-na-1-rok">již
psal</a>. Ale tohle omezení platí jen pro certifikáty vydané nějakou
veřejnou certifikační autoritou (CA), tedy takovou, kterou do úložiště
důvěryhodných kořenových certifikátů v operačním systému a
prohlížečích přidal výrobce.</p>Platnost HTTPS certifikátů od uživatelem přidaných CA je v podstatě
omezena na 2 roky<p>Maximální délka platnosti certifikátů 1 rok neplatí pro certifikáty
vystavené privátní kořenovou autoritou („root CA“), kterou přidal
uživatel nebo správce počítače ať už ručně, nebo pomocí programů jako
např. <a
href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/certutil"><code>certutil</code></a>
nebo <a href="https://github.com/FiloSottile/mkcert"><code>mkcert</code></a>.
Ostatně o této výjimce píše i Apple na stránce, na které informuje
o <a href="https://support.apple.com/en-us/HT211025">zkrácení na
398 dní</a>: <em>This change will not affect certificates issued from
user-added or administrator-added Root CAs</em>.</p>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/user-added-certification-authorities/macos-keychain-access-user-added-ca.png"
alt="" width="704" height="273">
<p>Uživatelem přidaná certifikační autorita v Keychain Access
na macOS</p>
</div>
<p>Jenže když v Safari zkusíte načíst stránku, která používá např.
5letý certifikát vystavený takovou privátní CA, tak prohlížeč stránku
nezobrazí a napíše jen:</p>
<blockquote>
<p><em>Safari cannot open the page because it could not establish a secure
connection to the server.</em></p>
</blockquote>
<p>Nic víc, žádný 🔓 <em>This Connection Is Not Private</em>, žádný
vysvětlení, žádný „pusťte mě dál, (ne)vím co dělám“ jako třeba
v případě expirovaného nebo <em>self-signed</em> certifikátu.</p>
<h2 id="omezeni-v-safari">Omezení v Safari</h2>
<p>To naznačuje, že v Safari existuje nějaké omezení pro maximální
délku platnosti i pro certifikáty vystavené privátními autoritami.
Vygůglovat něco rozumnýho a vyzdrojovanýho se mi vůbec nedařilo a tak jsem
našel klíč ke své privátní CA (jmenuje se „exploited CA“), nahodil
<code>openssl</code>, web server, <em>meka</em> a kalkulačku a rozhodl jsem se
to empiricky zjistit. Certifikát s platností 398 dní v Safari fungoval,
800 dní (2×398 + 4 dny) taky, ale s platností 1592 dní (4×398)
už ne.</p>
<p>O šestnáct certifikátů později jsem <a
href="https://cs.wikipedia.org/wiki/Bin%C3%A1rn%C3%AD_vyhled%C3%A1v%C3%A1n%C3%AD">metodou
půlení intervalu</a> mezi 800 a 1592 došel k certifikátu s platností
825 dní. To číslo se mi asi zdálo nějaký podezřelý a tak možná proto
mě přesně v tu chvíli napadlo, že by pro privátní CA mohl přece platit
předchozí limit, který… nebudu vás napínat, byl 825 dní. Apple o tom
samozřejmě <a href="https://support.apple.com/en-us/HT210176">píše
v dokumentaci</a>:</p>
<blockquote>
<p><em>All TLS server certificates must comply with these new security
requirements in iOS 13 and macOS 10.15 […] TLS server certificates must have
a validity period of 825 days or fewer (as expressed in the NotBefore and
NotAfter fields of the certificate).</em></p>
</blockquote>
<p>Důležité je tam to „all TLS certificates“, ale tuhle stránku jsem
pochopitelně předtím nenašel. A i kdyby jo, tak by mi ta souvislost
stejně asi nedošla.</p>
<p>Certifikát s platností 825 dní tak samozřejmě fungoval, ten
s platností 826 dní už ne.</p>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/user-added-certification-authorities/safari-private-ca-cert-validity-2-years.png"
alt="" width="720" height="456">
<p>Certifikát pro <code>snafu.cz</code> platný 825 dní, vystavený mnou
přidanou CA, v prohlížeči certifikátů v Safari</p>
</div>
<h2 id="zadne-omezeni-v-chrome-i-firefoxu">(Žádné) omezení v Chrome
i Firefoxu</h2>
<p>Chrome i Firefox ale klidně zobrazí stránku s certifikátem od
privátní CA, který platí 10 let, tyhle browsery žádné takové omezení
pro uživatelem přidané certifikační autority nemají.</p>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/user-added-certification-authorities/chrome-cert-viewer-validity-10-years.png"
alt="" width="480" height="419">
<p>10letý certifikát (3650 dní) v prohlížeč certifikátů v Chrome,
pole <em>Common Name</em> si nevšímejte 😅</p>
</div>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/user-added-certification-authorities/firefox-cert-viewer-validity-10-years.png"
alt="" width="360" height="630">
<p>10letý certifikát (3650 dní) v prohlížeč certifikátů ve
Firefoxu</p>
</div>
<p>Ale jestli chcete mít např. vývojové prostředí dostupné i pro Safari,
<strong>vaše interní certifikační autorita musí vystavovat certifikáty
s platností maximálně 825 dní</strong>, což jsou 2 roky, 3 měsíce a
pár dní. A podobně jako u toho posledního zkrácení za to „může“
Apple 😊</p>
<p>Ostatně i proto si dovolím tvrdit, že platnost certifikátů od
uživatelem přidaných autorit je fakticky omezena na 2 roky i přesto, že
tomu tak je jen v jednom prohlížeči. U aktuálního omezení platnosti na
398 dní pro certifikáty od veřejných CA se doporučuje vystavovat
certifikáty s platnosti nejdéle 397 dní, nejspíš kdyby náhodou něco
někde špatně počítalo vteřiny při změně času apod., a tak i v tomto
případě bych doporučil nastavit maximálně 824 dní. A nejlépe si to
zjednodušit nebo dokonce automatizovat tak, abyste mohli v klidu vystavovat
certifikáty s platností maximálně rok nebo klidně i méně.</p>https://www.michalspacek.cz/prepisovani-http-hlavicek-v-odpovedich-v-chrome2023-05-04T00:00:00+02:002023-10-05T19:02:38+02:00<p>V prohlížeči Chrome (a dalších, např. v Edge) je od verze
113 možné přepisovat HTTP hlavičky v odpovědích ze serveru a lze také
nějakou novou přidat. To se může hodit pokud potřebujete upravit třeba
nějakou bezpečnostní hlavičku pro testování, protože k přepisu dojde
ještě před zpracováním věcí jako např. <abbr
title="Content Security Policy">CSP</abbr>, takže pro konkrétní stránku
můžete upravit <em>Content Security</em> politiku přímo
v prohlížeči.</p>Přepisování HTTP hlaviček v odpovědích v Chrome<h3>Aktualizace článku</h3><ul><li><em><strong>5.10.</strong> Od Chrome 117 existuje jednodušší cesta</em></li></ul><p>Nástrojů, které vám dovolí udělat to samé nebo dokonce i něco
navíc, je celkem dost, například <a
href="https://www.telerik.com/fiddler">Fiddler</a>, <a
href="https://portswigger.net/burp/documentation/desktop/tools/proxy">Burp
Proxy</a> nebo rozšíření <a href="https://tamper.dev/">Tamper Dev</a>. Mít
tu možnost ale rovnou v browseru se vždycky hodí, navíc s podporou
výrobce, protože občas si nechcete něco dalšího instalovat nebo dokonce
ani nemůžete.</p>
<p>Pojďme si to ukázat, zkusíme upravit hlavičku Content Security Policy
(CSP), kterou posílá moje aplikace, kterou používám <a
href="https://canhas.report/">na ukázky <abbr
title="Content Security Policy">CSP</abbr> a reportování</a>:</p>
<ol>
<li>Načtěte <a href="https://canhas.report/">canhas.report</a></li>
<li>Otevřete DevTools (F12)</li>
<li>Jděte do záložky <em>Network</em></li>
<li>V ukázkové aplikaci klikněte na první demo „<a
href="https://canhas.report/csp-report-uri">Content Security Policy
<code>report-uri</code></a>“</li>
<li>V DevTools vyberte URL dokumentu (bude zobrazeno jako
<code>csp-report-uri</code>)</li>
<li>Klikněte pravým tlačítkem a vyberte <em>Override headers</em></li>
<li>V pravé části můžete editovat hlavičky</li>
</ol>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/override-headers.png"
alt="Kontextové menu a hlavičky odpovědi v záložce Network v DevTools v Chrome 117 a novějším"
width="720" height="700">
<p>Kontextové menu s položkou <em>Override headers</em>, ve stejném menu
můžete vybrat i <em>Override content</em>, o tom píšu <a
href="https://www.michalspacek.cz/prepisovani-obsahu-http-odpovedi-v-chrome">v dalším článku</a></p>
</div>
<p>Další možnost, jediná před vydáním Chrome 117, je že rovnou najedete
myší na hlavičku <code>Content-Security-Policy</code> v odpovědi, měla by
se vám objevit ikonka tužky, klikněte na ni:</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/override-header.png"
alt="Hlavičky odpovědi v záložce Network v DevTools" width="710"
height="320"></div>
<h2 id="povoleni-pristupu">Povolení přístupu</h2>
<p>Chrome potřebuje povolit přístup k adresáři, ve kterém budou
ukládány přepisované informace, ale naštěstí jen jednou, pokud ovšem
konfiguraci nevymažete.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/select-folder.png"
alt="Dialog pro vybrání adresáře se zobrazuje v/nad DevTools" width="500"
height="86"></div>
<p>Informace co se má kde a jak přepsat jsou uloženy ve formátu JSON
v souborech <code>.headers</code> v adresářové struktuře, která zrcadlí
URL. Soubory můžete ručně editovat, smazat je, vytvářet je dle libosti a
jakékoliv změny se v podstatě ihned promítnou i ve Chrome. Ukázkový
soubor <code>.headers</code> vypadá tahle:</p>
<pre>[
{
"applyTo": "*",
"headers": [
{
"name": "header-name-1",
"value": "header value"
}
]
},
{
"applyTo": "csp-report-uri",
"headers": [
{
"name": "content-security-policy",
"value": "default-src 'none'; script-src 'nonce-7Q+TqJ9I61ubCQN+ZSLrQoif' 'self' 'report-sample'; style-src 'self'; report-uri https://degum.has.report/report"
}
]
}
]</pre>
<p>Povolení přístupu má dva kroky. V prvním vyberete adresář a ve
druhém do něj musíte povolit přístup pro DevTools. Každý z těch
dialogů je ale umístěn jinde, ten první je nahoře v okně DevTools, ten
druhý je na nahoře na stránce.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/devtools-requests-full-access.png"
alt="DevTools požaduje přístup do vybraného adresáře" width="720"
height="122"></div>
<p>Pokud byste byli jako <em>někdo</em>, ehm, tak by se vám stalo, že si toho
druhého nevšimnete a pak budete dlouze zírat do konzole na „Unable to add
filesystem: <permission denied>“.</p>
<h2 id="prepisovani-a-pridavani-hlavicek">Přepisování a přidávání
hlaviček</h2>
<p>Jakmile vyberete adresář a povolíte do něj přístup, tak se můžete
vrhnout na přepisování hlavičky. Dejme tomu, že původní hlavička <abbr
title="Content Security Policy">CSP</abbr> by vypadalo třeba takhle:</p>
<pre>Content-Security-Policy: default-src 'none'; img-src 'self' https://www.michalspacek.cz; script-src [...]</pre>
<p>Což znamená, že prohlížeč defaultně (<code>default-src</code>)
nestáhne nic a obrázky (<code>img-src</code>) jen z
<code>https://canhas.report</code> (<code>'self'</code>) a z
<code>https://www.michalspacek.cz</code> a tak dále. Zkusme teď tohle:</p>
<ol>
<li>Z hlavičky odstraňte
<code>img-src 'self' https://www.michalspacek.cz;</code>, což způsobí, že
prohlížeč nestáhne žádný obrázek (protože
<code>default-src 'none'</code>)</li>
<li>Potvrďte to Enterem a všimněte si, že ta přepsaná hlavička je
zvýrazněna a že se i trochu změnil font</li>
<li>Po reloadu stránky na ní uvidíte jen samé nenačtené obrázky</li>
<li>V konzoli DevTools uvidíte „Refused to load the image ‚<URL>‘
because it violates the following Content Security Policy directive:
"default-src ‚none‘“. Note that ‚img-src‘ was not explicitly set, so
‚default-src‘ is used as a fallback."</li>
<li>…</li>
<li>Právě jste úspěšně přepsali hlavičku v odpovědi 🎉</li>
</ol>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/overridden-response-header.png"
alt="Zvýrazněná přepsaná HTTP hlavička v odpovědi" width="700"
height="140"></div>
<p>V konzoli také najdete upozornění, že prohlížeč odmítl spustit
JavaScript: „Refused to execute inline script because it violates the
following Content Security Policy directive: "script-src ‚nonce-[…]‘“,
to je proto, že náhodný tzv. <em>nonce</em> v HTML značkách
<code><script></code> teď nesouhlasí s tím napevno uvedeným v té
přepsané hlavičce, ale pojďme to pro tentokrát ignorovat.</p>
<p>Přepisování hlaviček funguje pouze pokud jsou DevTools otevřené. Pro
odstranění nějakého konkrétního přepisu klikněte na ikonku odpaďáku
vedle té přepsané hlavičky a stránku reloadněte:</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/remove-override.png"
alt="Ikonka pro odstranění přepisu" width="500" height="97"></div>
<p>V DevTools můžete taky přidat nějakou úplně novou HTTP hlavičku do
odpovědi, pokud byste to potřebovali:</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/add-header.png"
alt="Tímto tlačítkem přidáte novou hlavičku" width="320"
height="323"></div>
<p>Pokud v odpovědi byly nějaké HTTP hlavičky přepsány, tak ve sloupci
<em>Status</em> uvidíte před stavovým HTTP kódem puntík. Pokud je
přepisování jako takové zapnuto, tak Chrome navíc v pravé části
<em>Response Headers</em> zobrazí odkaz „Header overrides“.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/header-overrides.png"
alt="Odkaz Header overrides v Response Headers" width="550" height="140"></div>
<h2 id="vsechna-prepisovani">Všechna přepisování</h2>
<p>Pokud na ten odkaz kliknete, tak se otevřou záložky <em>Sources</em> a
<em>Overrides</em> (ta může být skrytá pod tlačítkem <code>»</code>),
což je další místo, kde můžete přepisování upravovat. Levá část
ukazuje adresářovou strukturu, ve které uvidíte soubor
<code>.headers</code>, který můžete pravým kliknutím smazat. Přepisování
můžete také kompletně vypnout v té samé části a pokud to uděláte, tak
zmizí i ikonka ⚠️ z <em>Network</em> tabu. Kliknutím na 🚫 vymažete
konfiguraci, což ve skutečnosti znamená <em>zruš povolení přístupu
k adresáři s informacemi o přepisech</em>.</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/chrome-header-overrides/sources-overrides.png"
alt="Záložka Sources, Overrides v DevTools" width="720" height="400"></div>
<p>Tady taky můžete nastavit i další lokální přepisování, nejenom
hlavičky, takže browser může sám sobě odpovědět nějakým místním
souborem, aniž by ho stahoval z nějaké URL. Jak zapnout <a
href="https://www.michalspacek.cz/prepisovani-obsahu-http-odpovedi-v-chrome">přepisování
kompletního obsahu jsem sepsal v dalším článku</a>.</p>
<p>Aktuální odpověď již nemůže být přepsána, na to už je příliš
pozdě, ale úpravou hlaviček podle návodu výše vlastně jen vytvoříte
nově přepisování pro tu samou URL, ačkoliv to na první pohled může
vypadat jinak.</p>
<p>Můj Chrome se už na verzi 113 aktualizoval a nová verze je už pomalu na
cestě i do vašeho počítače, pokud už tam nedorazila. Přepisování
odpovědí a hlaviček v nich je docela fajn fíčura, přál bych si, aby se
brzy objevila i v dalších prohlížečích.</p>https://www.michalspacek.cz/kontrola-zranitelnych-balicku-pomoci-composer-audit2023-01-25T18:00:00+01:002023-01-27T11:24:30+01:00<p>Když se v nějaké vámi používané PHP knihovně objeví bezpečnostní
chyba, tak máte několik možností jak se o ní dozvědět dříve, než bude
pozdě. V jednom z předchozích článků jsem popisoval <em>PHP Security
Advisories Database</em> a její použití pomocí <em>Roave Security
Advisories</em> i několik dalších způsobů. Všechny zmíněné ale
vyžadují mít nebo používat nějaký extra balíček nebo nástroj.</p>Kontrola zranitelných balíčků pomocí <code>composer audit</code><h3>Aktualizace článku</h3><ul><li><em><strong>27.1.</strong> Parametr <code>--locked</code> zajistí, že <code>composer audit</code> se
bude dělat na základě obsahu <code>composer.lock</code></em></li></ul><p>Kromě balíčku <a
href="https://github.com/Roave/SecurityAdvisories"><em>roave/security-advisories</em></a>,
který díky svému dlouhému seznamu zranitelných knihoven a jejich verzí
nedovolí takový balíček nainstalovat a upozorní na něj, používám
i GitHub Action s názvem <a
href="https://github.com/marketplace/actions/the-php-security-checker">The PHP
Security Checker</a>, což je vlastně jen githubí omáčkou obalený <a
href="https://symfony.com/doc/current/setup.html#checking-security-vulnerabilities">security
check ze Symfony CLI</a>. O všech těchto věcech jsem již psal <a
href="https://www.michalspacek.cz/at-vas-bezpecnostni-chyby-nenachytaji-na-svestkach">ve
svém dřívějším článku</a> včetně ukázek.</p>
<p>Pokud ale nechcete nic dalšího a rádi byste využili něco, co nejspíš
už používáte, tak mám pro vás dobrou zprávu. Od verze 2.4 (<a
href="https://blog.packagist.com/composer-2-4/">vyšla v létě 2022</a>) se
Composer té databáze dotazuje rovnou sám, po každém
<code>composer require</code>. Kontrolu lze také vyvolat ručně pomocí
<code>composer audit</code>. K dotazování používá <a
href="https://packagist.org/apidoc#list-security-advisories">vlastní API</a>,
viz třeba <a
href="https://packagist.org/api/security-advisories/?packages[]=guzzlehttp/guzzle">seznam
zranitelných verzí balíčku <em>guzzlehttp/guzzle</em></a>.</p>
<p>Kontrola se provádí i po spuštění <code>composer update</code> (v obou
případech lze přeskočit pomocí <code>--no-audit</code>), ale po
<code>composer install</code> standardně ne – důvodem možná je obvykle
automatizované spouštění, kdy výsledek stejně nikdo nevidí a možná taky
to, že častá instalace, např. při spouštění testů, by posílala
zbytečně moc požadavků na ono zmíněné API. Nicméně pokud i přesto
chcete, tak kontrolu po instalaci můžete vynutit pomocí parametru
<code>--audit</code>.</p>
<h2 id="po-composer-require">Po <code>composer require</code></h2>
<p>Pojďme se podívat na to, jak to vypadá. Dejme tomu, že bych teď chtěl
nějakou tu zranitelnou verzi nainstalovat, konkrétně jsem si vybral Guzzle
verze 7.4.4 – Composer to sice udělá, ale bude remcat a upozorní mě,
všimněte si posledních dvou řádků:</p>
<pre>$ composer require guzzlehttp/guzzle:7.4.4
./composer.json has been created
Running composer update guzzlehttp/guzzle
Loading composer repositories with package information
Updating dependencies
Lock file operations: 8 installs, 0 updates, 0 removals
- Locking guzzlehttp/guzzle (7.4.4)
- Locking guzzlehttp/promises (1.5.2)
- Locking guzzlehttp/psr7 (2.4.3)
- Locking psr/http-client (1.0.1)
- Locking psr/http-factory (1.0.1)
- Locking psr/http-message (1.0.1)
- Locking ralouphie/getallheaders (3.0.3)
- Locking symfony/deprecation-contracts (v3.2.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 8 installs, 0 updates, 0 removals
- Installing symfony/deprecation-contracts (v3.2.0): Extracting archive
- Installing psr/http-message (1.0.1): Extracting archive
- Installing psr/http-client (1.0.1): Extracting archive
- Installing ralouphie/getallheaders (3.0.3): Extracting archive
- Installing psr/http-factory (1.0.1): Extracting archive
- Installing guzzlehttp/psr7 (2.4.3): Extracting archive
- Installing guzzlehttp/promises (1.5.2): Extracting archive
- Installing guzzlehttp/guzzle (7.4.4): Extracting archive
2 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
4 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
Found 2 security vulnerability advisories affecting 1 package.
Run composer audit for a full list of advisories.</pre>
<p>S barvičkami v terminálu je to o dost výraznější:</p>
<div><img
src="https://www.michalspacek.cz/i/images/blog/composer/require-vulnerable.png"
alt="Poslední 2 řádky z výstupu composer require" width="630"
height="49"></div>
<p>Formát lze sice změnit, viz níže, ale i tak by se to dalo jednoduše
přehlídnout. Zvlášť třeba pokud byste spustili
<code>composer require</code> s parametrem <code>--quiet</code>, „nic
nevypisuj“.</p>
<h2 id="composer-audit"><code>composer audit</code></h2>
<p>Proto je asi důležitější ten úplně poslední řádek, který říká,
že bychom měli spustit <code>composer audit</code>:</p>
<pre>$ composer audit
Found 2 security vulnerability advisories affecting 1 package:
+-------------------+----------------------------------------------------------------------------------+
| Package | guzzlehttp/guzzle |
| CVE | CVE-2022-31091 |
| Title | Change in port should be considered a change in origin |
| URL | https://github.com/guzzle/guzzle/security/advisories/GHSA-q559-8m2m-g699 |
| Affected versions | >=7,<7.4.5|>=4,<6.5.8 |
| Reported at | 2022-06-20T22:24:00+00:00 |
+-------------------+----------------------------------------------------------------------------------+
+-------------------+----------------------------------------------------------------------------------+
| Package | guzzlehttp/guzzle |
| CVE | CVE-2022-31090 |
| Title | CURLOPT_HTTPAUTH option not cleared on change of origin |
| URL | https://github.com/guzzle/guzzle/security/advisories/GHSA-25mq-v84q-4j7r |
| Affected versions | >=7,<7.4.5|>=4,<6.5.8 |
| Reported at | 2022-06-20T22:24:00+00:00 |
+-------------------+----------------------------------------------------------------------------------+</pre>
<p>To už je o něco lepší, že? Rovnou vidíme, který balíček má
nějakou zranitelnost a jakou. Dokonce i návratová hodnota je nenulová,
<em>došlo k nějaké chybě</em>, takže se to dá použít i v různých
skriptech:</p>
<pre>$ echo $?
1</pre>
<p>Když se pokusím nainstalovat nejnovější verzi bez známých
bezpečnostních problémů, tak to bude vypadat takto:</p>
<pre>$ composer require guzzlehttp/guzzle
[...]
No security vulnerability advisories found
Using version ^7.5 for guzzlehttp/guzzle
[...]</pre>
<p>Výstup z <code>composer audit</code> pak bude vypadat následovně, včetně
nulové návratové hodnoty, což znamená <em>vše oukej</em>:</p>
<pre>$ composer audit
No security vulnerability advisories found
$ echo $?
0</pre>
<p>Pro správnou funkčnost <code>composer audit</code> musí být balíčky
standardně nainstalovány. Pokud ale použijete parametr <code>--locked</code>
(<code>composer audit --locked</code>), tak balíčky nainstalovány být
nemusí a kontrola se provede jen na základě obsahu
<code>composer.lock</code>.</p>
<p>Pokud byste z nějakého důvodu nechtěli kontrolovat balíčky v sekci
<code>require-dev</code>, tak můžete použít <code>--no-dev</code>. Osobně
bych ale kontroloval vše.</p>
<h2 id="jako-github-action">Jako GitHub Action</h2>
<p>Díky nenulové návratové hodnotě při nalezení známého
bezpečnostního problému v nějakém používaném balíčku lze
<code>composer audit</code> spouštět jednoduše například i v GitHub
Actions. A přesně tak to <a
href="https://github.com/spaze/michalspacek.cz/blob/5ef1f83318b197ae8dcea832e0f3e5cdeb3d2b6d/.github/workflows/composer-vulns.yml#L23-L27">používám
i já</a>:</p>
<pre
class="yaml"><code>composer-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: composer audit</code></pre>
<p>Tuhle kontrolu spouštím každé dvě hodiny, abych se o případném
problému a nutnosti aktualizovat dozvěděl včas. Na nějaké důležitější
aplikaci, než je můj web, bych to klidně spouštěl častěji, třeba každou
hodinu.</p>
<div class="figure"><img
src="https://www.michalspacek.cz/i/images/blog/github/actions-dependency-vulnz-checker.png"
alt="Výsledek spouštění kontroly balíčků každé 2 hodiny na GitHub Actions"
width="365" height="350">
<p>Spouštění kontroly pomocí GitHub Actions</p>
</div>
<h2 id="format-vystupu">Formát výstupu</h2>
<p>Composer nabízí několik různých formátů vypisovaných informací
o známých zranitelnostech:</p>
<ul>
<li><code>table</code>: tabulka viz výše, standardně se použije
pro <code>composer audit</code></li>
<li><code>plain</code>: obyčejný textový výpis, bez tabulky</li>
<li><code>json</code>: hádejte</li>
<li><code>summary</code>: jen krátká informace o tom, jestli se něco
nalezlo, nebo ne; standardně pro <code>composer require</code>,
<code>composer update</code>, <code>composer install</code></li>
</ul>
<p>Formát můžete změnit parametrem:</p>
<ul>
<li><code>--audit-format=FORMAT</code> pro <code>composer require</code>,
<code>composer update</code>, <code>composer install</code></li>
<li><code>--format=FORMAT</code> pro <code>composer audit</code></li>
</ul>
<p>Dodám snad už jen to, že Composer můžete aktualizovat pomocí
<code>composer self-update</code>.</p>