Bezpečnost PHP
Bezpečnost PHP: jak předcházet zranitelnostem PHP ve vašem kódu, závislostech v Composeru a buildu
Zabezpečení PHP znamená tři vrstvy. V kódu, který píšete: validujte vstupy, používejte parametrizované dotazy a escapování frameworku a nikdy nedeserializujte nedůvěryhodná data. V balíčcích Composeru, na kterých závisíte: spouštějte composer audit, commitujte composer.lock a blokujte nezabezpečené verze. V CI, které je instaluje: zaznamenávejte, co se skutečně nainstalovalo, a omezte, kam se build může připojit. Většina průvodců bezpečností PHP končí u první vrstvy. Tento pokrývá všechny tři.
Aktualizováno
- Váš kód Injection, XSS, deserializace, práce se soubory, session Výchozí nastavení frameworku, code review, PHPStan, Psalm, Semgrep
- Vaše závislosti v Composeru Známé zranitelnosti, opuštěné a škodlivé balíčky, odchylky od lockfilu, kód spouštěný při instalaci composer audit, politiky Composeru, Dependabot nebo Renovate
- Váš build Co CI skutečně nainstalovalo a spustilo, odkud stahovalo, kam se mohlo dostat Připnuté actions, tokeny s minimálními oprávněními, egress policy, SBOM z doby buildu
Na této stránce: PHP2Shell, zranitelnosti PHP, na kterých stále záleží, konfigurace a podporované verze, statická analýza, bezpečnost závislostí v Composeru, útoky na dodavatelský řetězec PHP, build v CI, SBOM a nástroje podle vrstev.
PHP2Shell: zranitelnost PHP, kterou je třeba opravit hned
21. října 2026 vydal projekt PHP verze PHP 8.5.12, 8.4.27, 8.3.36 a 8.2.35, které opravují dvě kritické zranitelnosti v samotném PHP, jež mohou umožnit vzdálené spuštění kódu bez autentizace. Obě mají hodnocení 9,2 podle CVSS v4.0 a společně jsou známé jako PHP2Shell:
- CVE-2026-109618: obsluha nedostatku paměti může přerušit PHP ve chvíli, kdy jsou data požadavku inicializována jen částečně, a úklid po požadavku pak může s neplatnými daty zacházet jako s platnými.
- CVE-2026-109619: stav patřící požadavku může přetrvat i po uvolnění jeho paměti a další požadavek obsluhovaný stejným workerem jej může použít.
Bezpečnostní upozornění PHP uvádí, že ohrožena je každá instalace obsluhující webové požadavky, včetně PHP-FPM, Apache s mod_php, php-cgi, FrankenPHP a LiteSpeed, a nejvyšší riziko přisuzuje mod_php. PHP 8.1 a starší od projektu PHP žádné nové vydání nedostanou. Vzhledem k závažnosti však bezpečnostní tým PHP zveřejnil minimální backporty ve zdrojovém kódu pro PHP 7.4, 8.0 a 8.1 pro ty, kdo si PHP sestavují ze zdrojového kódu nebo nemají dodavatele LTS, a některé distribuce a dodavatelé LTS dodávají vlastní backportované opravy. Co dělat:
- Aktualizujte na opravené vydání. Na 8.1 nebo starší přejděte na podporovanou větev, použijte backportovaný balíček od svého dodavatele, nebo pokud si PHP sestavujete sami, použijte oficiální backport ve zdrojovém kódu.
-
Znovu sestavte každý image kontejneru, který obsahuje PHP, a znovu ho nasaďte. Starý tag image spouští staré PHP,
dokud není nahrazen, proto kontrolujte, co je nasazeno, nejen co je v repozitáři:
docker run --rm IMAGE php -v. -
Pokud dnes aktualizovat nemůžete, použijte dočasná zmírňující opatření z
bezpečnostního upozornění PHP, například
session.upload_progress.enabled=0. Nastavte ho vphp.ini, v poolu FPM nebo v konfiguraci serveru, která se uplatní před parsováním požadavku, a restartujte všechny workery. Nespoléhejte přitom naini_set()ani na.user.inive FPM: upozornění před obojím varuje. PHP jasně uvádí, že tato opatření snižují expozici, ale instalaci nezabezpečí.
Všimněte si, co vám tu composer audit neřekne: PHP2Shell je v běhovém prostředí, ne v balíčku Composeru,
takže se v composer.lock nikdy neobjeví. PHP, které spouštíte, pochází ze základního image nebo z balíčků
operačního systému, což je třetí vrstva tohoto průvodce. Tabulku verzí, časovou osu a skript, který zkontroluje každý
tag image, najdete v článku
PHP2Shell: co opravit a jak najít každé zranitelné PHP.
Zranitelnosti PHP, na kterých stále záleží
Třídy zranitelností v aplikacích PHP se za deset let téměř nezměnily. Změnilo se to, že Laravel, Symfony a moderní PHP většinu z nich ve výchozím stavu uzavírají, takže skutečné chyby se dnes soustředí tam, kde kód tato výchozí nastavení obchází. Injection je A05 v OWASP Top 10:2025 a Software Supply Chain Failures je nově na pozici A03, což je téma druhé poloviny tohoto průvodce.
| Zranitelnost | Jak se projevuje v PHP | Oprava | Laravel a Symfony |
|---|---|---|---|
| SQL injection | Data z požadavku připojená do SQL, včetně únikových cest pro surové dotazy | Připravené dotazy PDO s vázanými parametry | Eloquent a Doctrine parametry vážou; whereRaw(), DB::raw() a skládané DQL ne |
| Cross-site scripting (XSS) | Vstup uživatele vypsaný do HTML bez escapování | Escapujte při výstupu pomocí htmlspecialchars() nebo šablonovacího enginu |
Blade {{ }} a Twig ve výchozím stavu escapují; {!! !!} a |raw ne |
| Cross-site request forgery (CSRF) | Formulář nebo AJAXový endpoint, který mění stav, přijme jakýkoli požadavek se session cookie | Token pro každou session kontrolovaný u každého POST, PUT, PATCH a DELETE, plus cookies SameSite |
Laravel kontroluje token na trasách web (@csrf ve formulářích); Symfony Forms ho ve výchozím stavu vkládají a kontrolují; trasy vyjmuté z kontroly žádný nemají |
Code injection přes eval() |
Data z požadavku nebo šablona či vzorec, které může uživatel upravit, se dostanou do eval() |
Nepoužívejte eval(); mapujte vstup na pevnou sadu operací, nebo pro potřebné výrazy použijte parser |
Vaše vlastní volání eval() žádný framework neochrání |
| Object injection | unserialize() nad cookies, daty z požadavku nebo položkami cache, které může útočník zapsat |
Pro data zvenčí používejte JSON; pokud deserializovat musíte, odmítejte objekty | Vaše vlastní volání unserialize() žádný framework neochrání |
| Vkládání souborů a nahrávání | Cesta sestavená ze vstupu předaná do include; nahrané soubory uložené pod kořenem webu s názvem od klienta |
Vkládané soubory povolujte přes allow-list; nahrané soubory ukládejte mimo kořen webu pod generovanými názvy | store() v Laravelu generuje názvy souborů; typ a velikost ověřujte na serveru |
| Type juggling | == při porovnávání tokenů nebo hashů, kde "0e123" == "0e456" platí |
===, hash_equals() pro tajné hodnoty, password_verify() pro hesla |
Pomocné funkce frameworku porovnávají bezpečně; vaše vlastní porovnání jsou na vás |
| Slabé session a cookies | ID session čitelná z JavaScriptu, posílaná přes HTTP nebo zachovaná i po přihlášení | Cookies Secure, HttpOnly a SameSite, strict mode, nové ID session po přihlášení | Oba při přihlášení vygenerují nové ID session |
| Server-side request forgery (SSRF) | file_get_contents() nebo cURL nad URL od uživatele, včetně wrapperů jako file:// a phar:// |
Povolte jen vybraná schémata a hostitele, blokujte interní adresy, vypněte allow_url_fopen, kde se nepoužívá |
Žádná výchozí ochrana |
| Command injection | Vstup předaný do exec(), shell_exec(), system() nebo proc_open() |
Vyhněte se shellu; předávejte argumenty jako pole, nebo každý escapujte pomocí escapeshellarg() |
Symfony Process a fasáda Process v Laravelu přijímají pole argumentů |
| Mass assignment | User::create($request->all()) umožní požadavku nastavit is_admin |
Vyjmenujte plnitelná pole, nebo předávejte jen validovaný vstup | Modely Eloquent jsou ve výchozím stavu chráněné; $guarded = [] ochranu vypne |
SQL injection: zranitelná a opravená verze
// Vulnerable: the id goes straight into the SQL string
$rows = $pdo->query("SELECT * FROM orders WHERE id = " . $_GET['id']);
// Fixed: a prepared statement with a bound parameter
$stmt = $pdo->prepare('SELECT * FROM orders WHERE id = ?');
$stmt->execute([$_GET['id']]);
$rows = $stmt->fetchAll();
// Laravel: whereRaw() takes SQL as written, where() binds the value
Order::whereRaw("status = '" . $request->input('status') . "'")->get(); // vulnerable
Order::where('status', $request->input('status'))->get(); // fixed
XSS: zranitelná a opravená verze
<?php // Plain PHP: escape on output
echo '<p>' . $comment . '</p>'; // vulnerable
echo '<p>' . htmlspecialchars($comment, ENT_QUOTES, 'UTF-8') . '</p>'; // fixed ?>
{{-- Blade: {{ }} escapes, {!! !!} prints raw HTML --}}
<p>{!! $comment !!}</p> {{-- vulnerable --}}
<p>{{ $comment }}</p> {{-- fixed --}}
{# Twig: autoescape is on, |raw turns it off #}
<p>{{ comment|raw }}</p> {# vulnerable #}
<p>{{ comment }}</p> {# fixed #}
Object injection: zranitelná a opravená verze
// Vulnerable: an attacker-controlled cookie becomes PHP objects, and any
// class with a __wakeup() or __destruct() method in vendor/ is a gadget
$prefs = unserialize($_COOKIE['prefs']);
// Fixed: JSON carries data, not objects
$prefs = json_decode($_COOKIE['prefs'], true, 512, JSON_THROW_ON_ERROR);
// If you must read serialized data, refuse objects
$prefs = unserialize($data, ['allowed_classes' => false]);
Deserializace phar, varianta, kdy souborová operace nad cestou phar:// deserializovala metadata archivu,
byla v PHP 8.0 omezena: metadata phar se
už nedeserializují při otevření
phar, ale jen když kód zavolá getMetadata(). Je to další důvod nechat PHP 7 za sebou.
Vkládání souborů: zranitelná a opravená verze
// Vulnerable: ?page=../admin/reset reaches any .php file on the disk
include __DIR__ . '/pages/' . $_GET['page'] . '.php';
// Fixed: only names on an allow-list can be included
$pages = ['home' => 'home.php', 'pricing' => 'pricing.php'];
$file = $pages[$_GET['page'] ?? 'home'] ?? 'home.php';
include __DIR__ . '/pages/' . $file;
Konfigurace a zabezpečení běhového prostředí
I bezpečná aplikace může prozradit informace přes své běhové prostředí. Tato nastavení php.ini vycházejí
z OWASP PHP
Configuration Cheat Sheet:
| Nastavení | Hodnota v produkci | Proč |
|---|---|---|
display_errors |
Off (s log_errors = On) |
Stack trace ukazují cesty, dotazy a někdy i tajné hodnoty |
expose_php |
Off |
Přestane v hlavičkách odpovědí prozrazovat přesnou verzi PHP |
allow_url_include |
Off |
Brání include vzdálených URL; od PHP 7.4 zastaralé |
allow_url_fopen |
Off, pokud aplikace nestahuje URL souborovými funkcemi |
Zužuje prostor pro SSRF přes file_get_contents() |
disable_functions |
Například exec, shell_exec, system, passthru, proc_open, popen |
Útočníkovi, který spustí kód, vezme shell; uveďte jen to, co vaše aplikace nikdy nevolá |
open_basedir |
Adresář aplikace | Omezuje cesty, které mohou souborové funkce PHP otevřít; pojistka, ne izolační hranice |
| Cookies session | session.cookie_secure = 1, session.cookie_httponly = 1, session.cookie_samesite = Lax nebo Strict, session.use_strict_mode = 1 |
Drží ID session mimo nešifrované HTTP a mimo dosah JavaScriptu a odmítá ID, která server nevydal |
file_uploads |
Off, pokud aplikace nepřijímá nahrávání |
Odstraní celou vstupní cestu |
Zůstaňte na podporované verzi PHP
PHP dává každé větvi podle php.net dva roky aktivní podpory a poté dva roky pouze bezpečnostních oprav. PHP 8.1 dosáhlo konce podpory 31. prosince 2025 a PHP 8.2 následuje 31. prosince 2026. Větev po konci podpory nedostává od projektu PHP žádná nová vydání, a právě do této situace PHP2Shell postavil uživatele 8.1: jedinou oficiální opravou pro ně byl backport ve zdrojovém kódu.
- PHP 8.1 Konec podpory, skončila 31. 12. 2025
- PHP 8.2 Pouze bezpečnostní opravy, do 31. 12. 2026
- PHP 8.3 Pouze bezpečnostní opravy, do 31. 12. 2027
- PHP 8.4 Aktivní podpora, do 31. 12. 2026
- PHP 8.5 Aktivní podpora, do 31. 12. 2027
Produkční nastavení Laravelu a Symfony
-
Vypnutý debug.
APP_DEBUG=falsev Laravelu,APP_ENV=prodaAPP_DEBUG=0v Symfony. Ladicí stránka v produkci ukazuje konfiguraci a hodnoty prostředí. -
Klíče a tajné hodnoty. Držte
APP_KEY(Laravel) aAPP_SECRET(Symfony) mimo repozitář a pokud uniknou, vyměňte je.APP_PREVIOUS_KEYSv Laravelu umožňuje klíč vyměnit bez odhlášení všech uživatelů. -
Důvěryhodné proxy. Za load balancerem uveďte proxy, kterým důvěřujete (middleware trusted proxies
v Laravelu,
framework.trusted_proxiesv Symfony), aby klient nemohl hlavičkamiX-Forwarded-*podvrhnout svou IP adresu nebo HTTPS.
Statická analýza pro PHP (SAST)
Statická analýza čte váš kód, aniž by ho spouštěla. Většinu týmů PHP pokrývají tři open source nástroje:
-
PHPStan hledá typové chyby a bugy na stále přísnějších úrovních. Není to bezpečnostní skener, ale
mnoho bezpečnostních chyb začíná jako záměna typů nebo nezkontrolovaný
null. -
Psalm přidává taint analýzu:
sleduje data ze zdrojů jako
$_GETa$_COOKIEaž do míst použití jakoecho,includea SQL dotazy a hlásí problémy jakoTaintedSqlaTaintedHtml. Aktuální Psalm ji spouští spolu s kontrolou typů;--taint-analysisji zapne pro jedno spuštění a--report=results.sarifzapíše SARIF pro GitHub code scanning. - Semgrep podporuje PHP jako obecně dostupný jazyk, s komunitními pravidly pro běžné chyby v PHP a s analýzou toku dat napříč funkcemi v komerční úrovni.
SAST je u legacy PHP ze strukturálních důvodů hlučné: dynamické include, proměnné proměnné, magické metody jako
__get() a __call() a konvence frameworků, které vedou data přes kontejnery a fasády, jež
analyzátor nedokáže sledovat. Nástroj pak buď hádá a hlásí falešně pozitivní nálezy, nebo to vzdá a skutečné toky
přehlédne.
Na starém kódu se nejlépe zavádí postupně jako západka. Zaznamenejte dnešní nálezy jako baseline
(vendor/bin/phpstan analyse --generate-baseline zapíše phpstan-baseline.neon; Psalm má
--set-baseline), nechte build selhat jen kvůli novým nálezům a zvyšujte úroveň, jak baseline splácíte.
Od týmů s dlouho žijícím kódem v PHP slýcháme, že AI autofix funguje dobře u aktualizací závislostí a u nálezů v kódu
je nevyrovnaný, takže vygenerované opravy nálezů SAST kontrolujte stejně pečlivě jako jakoukoli jinou změnu. SAST a
SCA odpovídají na různé otázky; viz SAST vs SCA.
Bezpečnost závislostí v Composeru
Aplikace v Laravelu nebo Symfony instaluje desítky balíčků Composeru, většinou tranzitivních. Composer v posledních několika vydáních získal skutečné bezpečnostní funkce; tato část vychází z Composeru 2.11, aktuálního vydání.
composer audit
composer audit porovnává nainstalované balíčky s bezpečnostními upozorněními z
API Packagist.org, které sbírá
upozornění ze zdrojů jako GitHub Advisory Database a FriendsOfPHP/security-advisories. Hlásí také opuštěné balíčky a
od Composeru 2.10 i verze balíčků označené jako malware. Všechny volby najdete v
dokumentaci Composeru.
composer audit # audit what is installed in vendor/
composer audit --locked # audit composer.lock, whatever is in vendor/
composer audit --no-dev # skip require-dev packages
composer audit --direct # direct dependencies only (Composer 2.11)
composer audit --format=json # table, plain, json or summary
composer audit --abandoned=report # ignore, report or fail on abandoned packages
composer audit --ignore-severity=low
Od Composeru 2.10 je návratový kód 0, když nejsou žádné problémy, a 1, když cokoli v auditu
neprošlo, takže funguje jako brána v CI bez parsování. Opuštěné balíčky ve výchozím stavu auditem neprojdou.
Akceptované nálezy patří do composer.json i s odůvodněním, ne do příznaku v CI, který si nikdo nepamatuje.
Composer 2.10 nahradil většinu staré konfigurace audit blokem policy:
{
"config": {
"policy": {
"advisories": {
"ignore-id": {
"CVE-1234": "Not affected: we never call the vulnerable parser."
}
},
"abandoned": {
"audit": "report"
}
}
}
}
Starší klíče (audit.ignore, audit.abandoned, audit.block-insecure) stále fungují
jako záloha, dokud odpovídající sekce policy chybí, ale jsou zastaralé. Převeďte všechna nastavení
upozornění najednou: jakmile existuje policy.advisories, každý klíč audit.* týkající se
upozornění se ignoruje.
Blokování nezabezpečených verzí
Od Composeru 2.9 (listopad 2025) composer update a composer require odmítají verze balíčků,
ke kterým existuje bezpečnostní upozornění. Dříve šlo o nastavení audit.block-insecure, dnes je to
policy.advisories.block, ve výchozím stavu zapnuté. Composer 2.10 přidal blokování malwaru z malwarového
feedu Packagistu, které platí i pro composer install. Composer 2.11 přidal volitelnou politiku
cooldown, která nově vydané verze zadrží po vámi zvolenou dobu, například "7 days", protože
mnoho škodlivých verzí se odhalí a odstraní během hodin nebo dní.
V CI záleží na dvou detailech. Zaprvé se blokování podle upozornění uplatní při vyhodnocování verzí: verze, která už je
v composer.lock, se po zveřejnění upozornění pomocí composer install stále nainstaluje, takže
jako bránu stále potřebujete composer audit. Zadruhé --no-blocking vypne všechny politiky pro
jeden příkaz, a proto stojí za to ho ve vašich pipelines vyhledat.
Před Composerem 2.9 se nezabezpečené verze obvykle blokovaly pomocí
roave/security-advisories, balíčku, který
deklaruje conflict s každou známou zranitelnou verzí, sestaveného z GitHub Advisory Database a
FriendsOfPHP. Stále funguje a stále pomáhá na starších verzích Composeru; na Composeru 2.9 nebo novějším pokrývá
vestavěné blokování stejná upozornění bez dalšího balíčku.
Disciplína lockfilu
- U aplikací commitujte
composer.lock, aby každé prostředí instalovalo stejné verze. -
V CI a při nasazení používejte
composer install, nikdycomposer update, který za běhu vyhodnocuje nové verze. - Produkční buildy instalujte s
--no-dev, aby se testovací nástroje nikdy nedodaly. -
Spouštějte
composer validate --strict: selže, kdyžcomposer.lockneodpovídá aktuálnímucomposer.json.
Lockfile zaznamenává, co Composer vyhodnotil. Viz co lockfile nezaznamenává.
Kód spouštěný při instalaci: pluginy, skripty a autoloadované soubory
Composer spouští kód z vašich závislostí na více místech, než většina týmů čeká, a je tak obdobou instalačních skriptů npm ve světě PHP:
-
Pluginy běží přímo uvnitř Composeru. Od Composeru 2.2 musí nastavení
allow-pluginsvyjmenovat každý plugin, který smí běžet, a ve výchozím stavu nepovoluje žádný. Novou položku v něm berte jako novou závislost s přístupem k shellu. -
Skripty se spouštějí jen z vašeho kořenového
composer.json, ne ze závislostí. Často ale spouštějí vaši aplikaci. -
Autoloadované soubory se spustí pokaždé, když se načte autoloader. Ve standardní aplikaci v
Laravelu končí
composer installpříkazemphp artisan package:discover, který načte autoloader, takže autoloadovanéfileszávislosti se spustí ve vašem CI jobu už během instalace.
Pokud job pluginy ani skripty nepotřebuje, --no-plugins --no-scripts zabrání spuštění kódu závislostí
během instalace; potřebné kroky pak spusťte explicitně.
Zdroje repozitářů a dependency confusion
Composer umí instalovat z Packagist.org, ze soukromých repozitářů Composeru jako Private Packagist, z repozitářů VCS
(Git URL) a z lokálních cest. Míchání soukromých a veřejných zdrojů je místo, kde vzniká
dependency confusion: pokud název vašeho soukromého balíčku existuje i na
Packagist.org s vyšší verzí, který dostanete? V Composeru 2 je
každý repozitář ve výchozím
stavu kanonický: jakmile Composer najde balíček v repozitáři, dál v seznamu ho nehledá, a Packagist.org je vždy
poslední. Nastavení "canonical": false u repozitáře tuto ochranu odstraní, proto ho používejte jen
záměrně. Repozitáře VCS stahují to, co hostitel Gitu pro daný tag poskytne, takže je v lockfilu připněte na commit a
jejich změny kontrolujte.
Opuštěné a neudržované balíčky
Legacy prostředí PHP obsahují balíčky, jejichž správci se přesunuli jinam. Packagist označí balíček jako opuštěný, když
to jeho správce oznámí, často i s náhradou, a composer audit u něj ve výchozím stavu selže. Mnoho
neudržovaných balíčků však formálně opuštěno nikdy není. U všeho, co zpracovává vstupy, autentizaci nebo kryptografii,
sledujte datum posledního vydání a náhradu naplánujte dřív, než vás k ní donutí zranitelnost.
Bezpečné aktualizace, když je testů málo
Nejtěžší je velká legacy aplikace se zranitelnou závislostí a málem testů. Co funguje:
-
Aktualizujte balíček, který zranitelný balíček stahuje. Většina nálezů je tranzitivních; spusťte
composer why vendor/package, abyste viděli řetězec, a nejdřív aktualizujte nadřazený balíček v rámci jeho omezení. -
Aktualizujte jednu věc po druhé.
composer update vendor/package --with-dependenciesudrží změnu dost malou na to, aby se dala zkontrolovat v diffucomposer.lock. - Nejdřív udělejte smoke testy kritických cest. Přihlášení, pokladna, hlavní endpointy API: hrstka end-to-end kontrol zachytí většinu rozbitých aktualizací.
- Kolem toho, čeho se aktualizace dotýká, přidejte charakterizační testy. Zaznamenejte, co kód dělá dnes, abyste si všimli, když to aktualizace změní.
- Zavádějte postupně a sledujte chybovost dřív, než se změna dostane ke všem zákazníkům.
Co composer audit přehlédne
Lockfile je plán, ne záznam buildu. composer audit čte vendor/ nebo
composer.lock, takže nevidí:
- Samotné PHP a jeho rozšíření, která pocházejí ze základního image nebo z balíčků OS (příkladem je PHP2Shell).
- Rozšíření PECL a systémové knihovny nainstalované v Docker image.
- Nástroje, které vaše CI stahuje pomocí
curl, actions, které spouští, a binárky, které stáhne plugin nebo skript. - Škodlivý kód, ke kterému zatím neexistuje upozornění ani označení jako malware.
Stejná omezení platí pro npm audit a pip-audit v jejich ekosystémech.
Útoky na dodavatelský řetězec PHP
Čtyři incidenty ukazují, kam útoky na dodavatelský řetězec v PHP skutečně míří a co by každý z nich zachytilo.
Březen 2021: škodlivé commity do php-src
28. března 2021 byly do repozitáře php-src pod jmény dvou hlavních vývojářů odeslány dva škodlivé commity. Tým PHP oznámil, že přestane provozovat vlastní server Git a kanonickými se stanou repozitáře na GitHubu. Jeho navazující zjištění ukázalo, že commity byly odeslány přes HTTPS s autentizací heslem, možná s přihlašovacími údaji z uniklé databáze uživatelů, a ne přes kompromitovaný server. Co by to zachytilo: nic ve vaší vlastní pipeline. Útok mířil na samotný jazyk, což připomíná, že běhové prostředí PHP je také závislost.
Duben 2021: command injection v Composeru (CVE-2021-29472)
Chyba ve způsobu, jakým Composer zpracovával URL repozitářů Mercurial, umožňovala command injection. Packagist uvedl, že Packagist.org a Private Packagist byly teoreticky ohroženy prostřednictvím podvržených URL balíčků, nedomníval se, že by chyba byla zneužita, a opravil ji v Composeru 2.0.13 a 1.10.22. Co by to zachytilo: udržování samotného Composeru aktuálního na počítačích vývojářů a v image pro CI. Správce balíčků je součástí vašeho dodavatelského řetězce.
Květen 2023: převzetí účtů na Packagistu
1. května 2023 se útočník přihlásil do čtyř neaktivních účtů na Packagist.org pomocí hesel uniklých jinde, které
dohromady spravovaly 14 balíčků, a přesměroval balíčky na forkované repozitáře. Forky obsahovaly změněný popis a žádný
škodlivý kód.
Packagist
je obnovil druhý den ráno. Co by to zachytilo: commitnutý composer.lock, protože
composer install používá zdroje zaznamenané v lockfilu, a kontrola diffů lockfilu kvůli nečekaným URL,
kterou doporučuje i sám Packagist. Pro správce: jedinečná hesla a dvoufaktorové ověření.
2026: unesené tagy u laravel-lang a intercom-php
V roce 2026 útočníci s odcizeným přístupem zveřejnili škodlivá vydání existujících balíčků. Packagist ve svém
přehledu
bezpečnosti dodavatelského řetězce jmenuje intercom/intercom-php (30. dubna) a balíčky
laravel-lang (22. května). V případě laravel-lang ukazovaly tagy verzí na commity ve škodlivém forku a
payload, soubor src/helpers.php
načítaný
autoloaderem Composeru, stahoval nástroj na krádež přihlašovacích údajů zaměřený na cloudové klíče, klíče SSH a
soubory .env. Aikido napočítalo 233 kompromitovaných verzí. Co by to zachytilo: jakmile
jsou verze označeny, politika malwaru v Composeru 2.10 je zablokuje i při composer install; předtím
zabrání zcela novým verzím v instalaci doba cooldown, egress policy zastaví payload, aby se nedostal ke svému serveru,
a záznam každého buildu ukáže, které buildy nainstalovaly špatnou verzi.
Packagist od té doby oznámil, že odmítá přetagované stabilní verze, zveřejňuje transparentní log změn vlastnictví a tagů a od března 2026 označuje malware na Packagist.org.
Zabezpečení buildu PHP v CI
Build je místo, kde se setkává váš kód, vaše závislosti a vaše tajné klíče, a proto tam škodlivý balíček napáchá nejvíc škody. Workflow GitHub Actions pro produkční build:
name: build
on: [push, pull_request]
permissions:
contents: read # least privilege for GITHUB_TOKEN
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
with:
php-version: "8.4"
tools: composer:v2
coverage: none
- run: composer validate --strict
- id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ hashFiles('composer.lock') }}
- run: composer install --no-dev --no-interaction --no-progress --optimize-autoloader
- run: composer audit --no-dev
-
Token s minimálními oprávněními.
permissions: contents: readzabrání kompromitovanému kroku odeslat kód nebo vydání pomocíGITHUB_TOKEN. Viz oprávnění tokenu GitHub. - Připnuté actions. Plný SHA commitu nelze přesunout tak jako tag; komentář zachová verzi čitelnou pro Dependabot nebo Renovate.
-
Ukládejte do cache stažené soubory, ne
vendor/. Cache stažených souborů Composeru zajistí, žecomposer installv každém jobu běží proti lockfilu. Obnovenémuvendor/se věří tak, jak je, a cache lze napříč workflow otrávit; viz cache v GitHub Actions. - Zaznamenejte, co se nainstalovalo. Lockfile říká, co se nainstalovat má. SBOM zaznamenaná z buildu říká, co se nainstalovalo, včetně běhového prostředí PHP, balíčků OS a stažení mimo Composer. Viz SBOM z doby buildu.
- Omezte, kam se build může připojit. Build s Composerem potřebuje Packagist, GitHub a váš soukromý repozitář. Egress policy, která povolí jen je, zabrání nástroji na krádež přihlašovacích údajů odeslat, co našel.
Generování SBOM pro projekt v PHP
Standardní open source možností je
plugin CycloneDX pro Composer. Jako
plugin potřebuje položku v allow-plugins:
composer require --dev cyclonedx/cyclonedx-php-composer
composer CycloneDX:make-sbom --output-format=JSON --output-file=sbom.json
Čte composer.json spolu s nainstalovanými balíčky nebo s composer.lock, takže přesně uvádí
vaše závislosti v Composeru a nic jiného. Běhové prostředí PHP, jeho rozšíření, balíčky apt nebo apk ze základního
image a nástroje, které job stáhl, v ní nejsou. SBOM z doby buildu zaznamenává i je, protože vychází z toho, co job
stáhl, a ne ze souborů Composeru. Formáty a to, co by SBOM měla obsahovat, popisuje článek
co je SBOM.
Nástroje pro bezpečnost PHP podle vrstev
| Vrstva | Nástroje | Co pokrývají | Co přehlédnou |
|---|---|---|---|
| Kód | PHPStan, Psalm, Semgrep | Bugy a typové chyby, toky nedůvěryhodných dat, známé nebezpečné vzory | Zranitelné závislosti, běhové prostředí, build |
| Závislosti | composer audit, politiky Composeru, roave/security-advisories, Dependabot nebo Renovate |
Zveřejněná upozornění, opuštěné a označené balíčky, pull requesty s aktualizacemi | Váš vlastní kód a cokoli mimo composer.lock |
| Image | Skenery kontejnerů | Balíčky OS a jazyků v sestaveném image | Co build stáhl a neponechal, a image, které nikdo neskenoval |
| SBOM | Plugin CycloneDX pro Composer | Balíčky Composeru jako SBOM CycloneDX | Všechno mimo Composer |
| Build | CRACI | Co každý job GitHub Actions skutečně nainstaloval, z Composeru i ze všech ostatních zdrojů, s monitorováním v čase | Váš kód v PHP (bez SAST); systémy CI jiné než GitHub Actions |
Více o image: skenování kontejnerů. Méně a lepších nálezů napříč všemi vrstvami: jak omezit šum zranitelností.
Bezpečnost PHP a CRACI
CRACI je runner pro GitHub Actions, který zaznamenává, co každý job skutečně nainstaluje. Změňte runs-on
na craci a každý balíček Composeru, který job stáhne, se zaznamená s package URL
(pkg:composer/vendor/package), spolu s balíčky ze správce balíčků OS, image kontejnerů, zdroji Git a
dalšími staženími ve stejném jobu, včetně balíčků obnovených z cache CI. Každá komponenta v SBOM byla v buildu.
CRACI tyto komponenty porovnává se známými zranitelnostmi a při zveřejnění nových upozornění znovu kontroluje SBOM toho, co jste dodali, takže CVE zveřejněná příští rok vás stále dovede k buildům, které ji obsahují. Policy gates mohou build kvůli nálezům zastavit, včetně buildu, který obsahuje konkrétní CVE, a egress policy omezuje, kam se job může připojit.
CRACI váš kód v PHP neskenuje: SAST záměrně nenabízí. Pro vrstvu kódu si ponechte PHPStan, Psalm nebo Semgrep. Podívejte se na každý balíček, který váš build PHP instaluje.
Kdy CRACI není řešením
-
Nasazujete přes FTP nebo na sdílený hosting bez CI. Není co zaznamenat. Začněte s
composer audita commitnutýmcomposer.lock. - Vaše riziko leží hlavně ve vlastním kódu, například v legacy aplikaci plné surového SQL. Na prvním místě je SAST, code review a testy.
- Sestavujete mimo GitHub Actions, v GitLab CI nebo Bitbucket Pipelines. CRACI dnes podporuje GitHub Actions; GitLab CI a Jenkins jsou v plánu vývoje.
Bezpečnost PHP: časté dotazy
Jak zabezpečit kód v PHP?
Pracujte ve třech vrstvách. V kódu validujte vstupy, používejte připravené dotazy, spoléhejte na escapování výstupu ve frameworku a nikdy nedeserializujte nedůvěryhodná data. V závislostech commitujte composer.lock, spouštějte composer audit v CI a nechte zapnuté blokování nezabezpečených verzí v Composeru. V buildu připněte actions, omezte token, omezte síťový přístup a zaznamenávejte, co job nainstaloval.
Jaké jsou nejčastější zranitelnosti PHP?
SQL injection, cross-site scripting, cross-site request forgery, code injection přes eval(), object injection přes unserialize(), vkládání souborů a nebezpečné nahrávání, type juggling s ==, slabá nastavení session, SSRF přes URL wrappery, command injection a mass assignment. Zranitelné závislosti v Composeru a neopravené verze PHP, jako chyby PHP2Shell, jsou stejně častou příčinou incidentů.
Jaký je nejlepší bezpečnostní skener pro PHP?
Žádný jednotlivý nástroj bezpečnost PHP nepokryje. Pro svůj kód použijte statický analyzátor jako PHPStan, Psalm nebo Semgrep, pro závislosti v Composeru composer audit, pro image skener kontejnerů a pro to, co CI skutečně nainstalovalo, záznam z doby buildu.
Jak funguje composer audit a co přehlédne?
Porovnává nainstalované balíčky, nebo s --locked composer.lock, s bezpečnostními upozorněními z API Packagist.org a hlásí opuštěné balíčky a balíčky označené jako malware. Když cokoli auditem neprojde, skončí s kódem 1. Nevidí samotné PHP, rozšíření PECL, balíčky OS ve vašem image ani nic, co CI stáhne mimo Composer.
Jak v CI blokovat nezabezpečené balíčky Composeru?
Používejte Composer 2.9 nebo novější, který ve výchozím stavu blokuje verze s upozorněním během update a require (policy.advisories.block). Protože zamčená zranitelná verze se stále nainstaluje, spouštějte po composer install také composer audit a nechte job selhat podle jeho návratového kódu. V CI nikdy nepoužívejte --no-blocking.
Je Laravel ve výchozím stavu bezpečný?
Většinou ano. Blade escapuje výstup, Eloquent váže parametry dotazů, modely jsou chráněné proti mass assignment a ID session se při přihlášení generuje znovu. Tato výchozí nastavení přestanou fungovat, když kód používá {!! !!}, whereRaw() se vstupem, $guarded = [] nebo APP_DEBUG=true v produkci, nebo když unikne APP_KEY.
Jak aktualizovat závislosti v legacy aplikaci v PHP s málem testů?
Aktualizujte nadřazený balíček, který zranitelný balíček stahuje, vždy jeden balíček pomocí composer update vendor/package --with-dependencies. Udělejte smoke testy kritických cest, přidejte charakterizační testy kolem toho, čeho se aktualizace dotýká, a zavádějte postupně.
Jak vygenerovat SBOM pro projekt v PHP?
Plugin CycloneDX pro Composer (cyclonedx/cyclonedx-php-composer) ji vygeneruje z vašich balíčků Composeru příkazem composer CycloneDX:make-sbom. Uvádí jen balíčky Composeru. SBOM z doby buildu zaznamenává také běhové prostředí PHP, balíčky OS a další stažení v buildu.
Co je PHP2Shell?
PHP2Shell je název pro CVE-2026-109618 a CVE-2026-109619, dvě kritické zranitelnosti v samotném PHP, které mohou umožnit vzdálené spuštění kódu bez autentizace. Jsou opravené v PHP 8.5.12, 8.4.27, 8.3.36 a 8.2.35, vydaných 21. října 2026. Viz PHP2Shell.
Podívejte se na každý balíček, který váš build PHP instaluje
Spusťte jedno workflow GitHub Actions na CRACI a porovnejte jeho zaznamenanou SBOM se svým composer.lock.
Rezervujte si demo