9. listopadu 2020

Na konci srpna byla v oblíbeném PHP frameworku Nette objevena a obratem opravena zákeřná chyba. Přestože autor frameworku, David Grudl, udělal snad všechno možné i nemožné, někteří se o ní nedozvěděli včas a nestihli tak aktualizovat své weby a webové aplikace. Prozradím vám pár tipů nejen pro PHP, díky kterým dostanete echo o podobných problémech mezi prvními.

Chybu, kterou objevil Cyku Hong z Taiwanu, může útočník za určitých okolností využít ke vzdálenému spuštění kódu na některých webech pomocí speciálně sestaveného URL. Takové chyby spadají do kategorie Remote Code Execution (RCE) a zažijete s nimi spousty legrace, nebo možná taky ne, to podle toho, na které straně barikády stojíte.

David Grudl, tvůrce Nette, s chybou zatočil správně: opravené verze balíčků vzhledem k závažnosti té chyby vydal nejdříve „potichu“, přičemž opravil i již dávno nepodporované vykopávky typu Nette 2.0, poslal e-mail podporovatelům, měsíc a něco počkal a teprve poté informaci uveřejnil na blogu, na diskuzním fóru a v seznamu Common Vulnerabilities and Exposures, ve kterém dostala číslo CVE-2020-15227. (A když si něčím nebyl jistý, tak se mě zeptal 🤓)

Část upozornění, které podporovatelé dostali už 25. srpna

Ve velkém a bez Composeru

Týden po zveřejnění se začaly objevovat nejen nástroje na otestování i demonstraci zranitelnosti („Proof-of-Concept“, PoC), ale i vývojáři, kteří chybu do té doby z různých důvodů zatím nezaregistrovali. Když jsem to viděl, tak mě napadlo, že by řešení problému mohl pomoci jednoduchý skript, který na serveru najde zranitelné soubory a příp. je rovnou opraví náhradou jednoho řádku kódu za jiný.

Cílem byly převážně různé webhostingové firmy, které by mohly vytipovat své zákazníky a přímo je informovat, nebo jim to rovnou opravit. Napsal jsem 2 prográmky v shellu (fungují v Bashi v Linuxu i FreeBSD), David to později reimplementoval i v PHP. Lukáš V. mi dokonce za ty skripty jen tak, sám od sebe, poslal nějaké to kilčo, dík!

V Active24 chybu našli a opravili na více než 2500 webů, podobně reagoval i Blueboard, WEDOS a další

Tohle samo o sobě už by vám mohlo dát pár nápadů jak postupovat poté, co vám někdo ve vašem frameworku, balíčku nebo knihovně najde nějakou docela závažnou bezpečnostní chybu. Neméně důležité je ale informaci o chybě a nutnosti aktualizovat dostat také k uživatelům vašeho výtvoru. A jak se o tom mohou dozvědět co nejrychleji?

Podporujte a sledujte vývojáře

Pokud používáte nějaké knihovny nebo frameworky jako např. Nette nebo cokoliv jiného, tak máte několik možností, jak se dozvědět, že fakt musíte aktualizovat a tohle je asi ta nejdůležitější.

Podporujte vývojáře nástrojů, které používáte. Vy budete mít dobrý pocit a ti vývojáři, kromě jiného, budou na vás mít nějaký kontakt a mohou vám napsat, že byste měli aktualizovat. Win-win situace. Když jsme začali chybou v Nette, tak osobně Nette podporuji, v seznamu přispěvovatelů mě nejspíš nepřehlédnete. Najdete tam i Scotta Helmeho, protože Nette mj. používáme i na Report URI, na kterém s ním pracuji. David Grudl podporovatelům poslal info o tom, že by měli aktualizovat už 25. srpna, pár hodin po vydání opravených verzí verze, a o měsíc a něco dříve, než informace o chybě zveřejnil.

Sledujte twittery a blogy vývojářů a diskuzní fóra (viz příspěvek na blogu Nette a na diskuzním fóru). Pokud navíc vývojáři používají GitHub, tak ten vám může poslat notifikaci při vydání nové verze:

Můžete „watchovat“ jen nové verze (co všechno sledujete)

GitHub Dependabot

Pokud vyvíjíte na GitHubu a spravujete závislosti pomocí Composeru, tak můžete využít Dependabota – robota, který hlídá a automaticky aktualizuje všechny vaše závislosti. Dependabot zvládá nejen PHP, ale i jiné jazyky a správce balíčků. Nastavíte ho ve vašem repozitáři v menu Settings > Security & analysis:

Prozkoumejte i sekci Security > Dependabot alerts, ve které najdete všechna otevřená i vyřešená upozornění, která Dependabot vytvořil:

Tohle upozornění jsem kliknutím na Dismiss pustil z hlavy, kód nikde neběží

Jsou tam i odkazy na automaticky vytvořené pull requesty, viz příklad. Tenhle opuštěný projekt naštěstí nikde neběží, balíček nette/application zde tedy nemusím aktualizovat, pull request chci nechat v tomto stavu, abyste viděli, jak to případně bude vypadat u vás.

Automaticky vytvořený pull request s aktualizací závislosti

Kdybyste někdy potřebovali získat číslo CVE i pro nějaký váš výtvor a informaci o chybě tak dostat „do oběhu“, tak se vám bude hodit sekce Security Advisories. V seznamu zveřejněných upozornění balíčku nette/application najdete i ono CVE-2020-15227. Podobný záznam je i v databázi všech GitHubových upozornění.

Roave Security Advisories

Dalším nástrojem, který vám může pomoci odhalit neaktualizované PHP knihovny s bezpečnostními chybami je balíček Roave Security Advisories. Pojďme si ho ukázat v praxi, opět na mém starém a nepoužívaném projektu. Nejdříve nainstalujeme závislosti uvedené v souboru composer.lock, přičemž vhodnější by bylo použít composer update, ale tím bychom nainstalovali již opravenou verzi a to v tomto případě nechceme:

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
[...]
  - Installing nette/application (v2.3.7): Downloading (100%)
[...]

Verze 2.3.7 obsahuje bezpečnostní chybu (a to v řadě 2.3 až do verze 2.3.13 včetně), takže instalace balíčku Roave Security Advisories pomocí

composer require --dev roave/security-advisories:dev-latest

selže s tím, že jakože konflikt, protože nette/application máme ve verzi 2.3.7:

$ composer require --dev roave/security-advisories:dev-latest
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - roave/security-advisories dev-latest conflicts with nette/application[v2.3.7].
    - roave/security-advisories dev-latest conflicts with nette/application[v2.3.7].
    - roave/security-advisories dev-latest conflicts with nette/application[v2.3.7].
    - Installation request for roave/security-advisories dev-latest -> satisfiable by roave/security-advisories[dev-latest].
    - Installation request for nette/application (locked at v2.3.7, required as ~2.3.1) -> satisfiable by nette/application[v2.3.7].


Installation failed, reverting ./composer.json to its original content.

Museli bychom nejdříve aktualizovat nette/application na 2.3.14 nebo novější a pak půjde nainstalovat i roave/security-advisories:dev-latest. Od této chvíle nepůjde pomocí composer require nebo composer update nainstalovat žádný balíček se známou bezpečnostní chybou.

PHP Security Advisories Database

Roave Security Advisories nemá žádný kód, veškeré jeho know-how spočívá v sekci conflict v souboru composer.json, ve které jsou uvedené verze PHP balíčků s nějakou známou bezpečnostní chybou. Ty se tam dostávají z PHP Security Advisories Database (viz Contributing), která jde používat několika dalšími způsoby. Jde používat dokonce i jako GitHub Action (viz můj příklad), takže se kontrola bude provádět například při každém pushi.

Bylo by skvělé, kdyby tuto databázi uměly pro upozorňování svých zákazníků nějak využít i klasické sdílené hostingy.

disable_functions

Bránit se proti chybám, jako je právě vzdálené spuštění kódu, asi nejde jinak než včasnou aktualizací. Jasně, můžete si někde na firewallu zablokovat nějakou konkrétní URL adresu, ale to je spíš reaktivní opatření. Můžete ale aspoň zásadně snížit dopad útoku aneb je blbý, když někdo ve vaší aplikaci spustí nějaký PHP kód, ale je ještě mnohem blbější, když pomocí PHP spustí nějaký prográmek z příkazové řádky. Důrazně bych tedy doporučoval zakázat v PHP volání funkcí, které takové prográmky mohou spouštět. Jsou to tyto:

  • exec – spustí externí program
  • passthru – spustí externí program a zobrazí výstup včetně binárních dat
  • proc_open – spustí program a vrátí ukazatele na vstup a výstup
  • shell_exec – spustí program pomocí shellu a vrátí výstup jako řetězec
  • system – spustí externí program a zobrazí výstup
  • pcntl_exec – spustí program v prostoru aktuálního procesu
  • popen – vrátí ukazatel na spuštěný proces

Zakázat je můžete jen v php.ini (a to dokonce jen pro určitý server nebo cestu) pomocí direktivy disable_functions nebo v konfiguraci FPM poolu pomocí php_admin_value[disable_functions], viz příklad.

Pro kontrolu, jestli náhodou tyhle funkce nepotřebujete volat ve vašem kódu, použijte analyzátor PHP kódu PHPStan a má pravidla pro hledání volání nepovolených funkcí a metod, stačí ve vaší konfiguraci inklůdnout přibalený soubor disallowed-execution-calls.neon.

Nezapomeňte také sledovat můj Twitter, o závažných problémech se tam určitě dozvíte také. A rád vás přivítám i na mém školení webové bezpečnosti (nejbližší termín: 14.–17. prosince 2020 online).

A ještě jedna věc: pokud jste doposud Nette neaktualizovali, tak už asi můžete počítat s tím, že do vašeho webu nebo aplikace někdo nakladl nějaké ty shelly nebo zadní vrátka a měli byste spustit proces obnovy, vyčištění a případně i pátrání po uniklých datech. A pokud už máte aktualizováno, tak se můžete pobavit třeba nad Easter eggy na mém webu.

Aktualizace článku

20. listopadu 2020 GitHub přesunul nastavení upozornění na nové Releases do kategorie Custom

Michal Špaček

Michal Špaček

Vyvíjím webové aplikace, zajímá mě jejich bezpečnost. Nebojím se o tom mluvit veřejně, hledám hranice tak, že je posouvám. Chci naučit webové vývojáře stavět bezpečnější a výkonnější weby a aplikace.

Veřejná školení

Zvu vás na následující školení, která pořádám a vedu:

HTTPS pro vývojáře a správce
(8.–9. prosince 2020 )

Bezpečnost PHP aplikací
(14.–17. prosince 2020 )