Vuonna 2026 raportoitiin päivässä 131% enemmän CVE:itä kuin edellisenä vuonna.

PHP-tietoturva

PHP-tietoturva: näin estät PHP-haavoittuvuudet koodissasi, Composer-riippuvuuksissasi ja buildissasi

PHP:n suojaaminen tarkoittaa kolmea kerrosta. Kirjoittamassasi koodissa: validoi syötteet, käytä parametrisoituja kyselyjä ja kehyksen escapointia äläkä koskaan deserialisoi epäluotettavaa dataa. Composer-paketeissa, joista olet riippuvainen: aja composer audit, tallenna composer.lock versionhallintaan ja estä turvattomat versiot. CI:ssä, joka asentaa ne: tallenna, mitä todella asennettiin, ja rajoita, mihin buildi voi yhdistää. Useimmat PHP-tietoturvaoppaat pysähtyvät ensimmäiseen kerrokseen. Tämä kattaa kaikki kolme.

Päivitetty

  1. Koodisi Injektiot, XSS, deserialisointi, tiedostojen käsittely, istunnot Kehysten oletukset, koodikatselmointi, PHPStan, Psalm, Semgrep
  2. Composer-riippuvuutesi Tunnetut haavoittuvuudet, hylätyt ja haitalliset paketit, lukitustiedoston ajautuminen, asennuksenaikainen koodi composer audit, Composerin käytännöt, Dependabot tai Renovate
  3. Buildisi Mitä CI todella asensi ja ajoi, mistä se haki ja mihin se pääsi Kiinnitetyt actionit, vähimpien oikeuksien tokenit, egress-käytäntö, buildin aikana tallennettu SBOM
PHP-tietoturvan kolme kerrosta ja kunkin kerroksen työkalut

Tällä sivulla: PHP2Shell, PHP-haavoittuvuudet, joilla on yhä merkitystä, konfiguraatio ja tuetut versiot, staattinen analyysi, Composer-riippuvuuksien tietoturva, PHP:n toimitusketjuhyökkäykset, CI-buildi, SBOM:t ja työkalut kerroksittain.

PHP2Shell: PHP-haavoittuvuus, joka on paikattava nyt

PHP-projekti julkaisi 21.10.2026 versiot PHP 8.5.12, 8.4.27, 8.3.36 ja 8.2.35 korjatakseen kaksi kriittistä haavoittuvuutta itse PHP:ssä. Ne voivat mahdollistaa etäkoodin suorittamisen ilman tunnistautumista. Kummankin CVSS v4.0 -pistemäärä on 9.2, ja yhdessä ne tunnetaan nimellä PHP2Shell:

  • CVE-2026-109618: muistin loppumisen käsittely voi keskeyttää PHP:n, kun pyynnön data on alustettu vasta osittain, ja pyynnön siivous voi sitten käsitellä virheellistä dataa kelvollisena.
  • CVE-2026-109619: pyynnön omistama tila voi säilyä sen muistin vapauttamisen jälkeen, ja saman workerin käsittelemä seuraava pyyntö voi käyttää sitä.

PHP:n tietoturvatiedotteen mukaan vaarassa on jokainen web-pyyntöjä palveleva asennus, mukaan lukien PHP-FPM, Apache ja mod_php, php-cgi, FrankenPHP ja LiteSpeed, ja suurimman riskin se arvioi koskevan mod_php:tä. PHP 8.1 ja sitä vanhemmat eivät saa PHP-projektilta uutta julkaisua. Vakavuuden vuoksi PHP:n tietoturvatiimi julkaisi kuitenkin minimaaliset lähdekoodin backportit PHP 7.4:lle, 8.0:lle ja 8.1:lle niille, jotka buildaavat PHP:n lähdekoodista tai joilla ei ole LTS-toimittajaa, ja osa jakeluista ja LTS-toimittajista toimittaa omia backport-korjauksiaan. Mitä tehdä:

  • Päivitä korjattuun julkaisuun. Jos käytössä on 8.1 tai vanhempi, päivitä tuettuun haaraan, ota käyttöön toimittajasi backport-paketti tai asenna virallinen lähdekoodin backport, jos buildaat PHP:n itse.
  • Buildaa uudelleen jokainen konttikuva, joka sisältää PHP:n, ja ota se uudelleen käyttöön. Vanha kuvatagi ajaa vanhaa PHP:tä, kunnes se korvataan, joten tarkista, mitä on käytössä, eikä vain sitä, mitä repositoriossa on: docker run --rm IMAGE php -v.
  • Jos et voi päivittää tänään, ota käyttöön väliaikaiset lievennykset PHP:n tietoturvatiedotteesta, kuten session.upload_progress.enabled=0. Aseta se php.ini-tiedostoon, FPM-pooliin tai palvelinkonfiguraatioon, joka on voimassa ennen pyynnön jäsentämistä, ja käynnistä jokainen worker uudelleen. Älä luota tässä ini_set()-kutsuun tai FPM:n .user.ini-tiedostoon: tiedote varoittaa molemmista. PHP sanoo selvästi, että nämä lievennykset pienentävät altistumista mutta eivät tee asennuksesta turvallista.

Huomaa, mitä composer audit ei voi tässä kertoa: PHP2Shell on ajoympäristössä, ei Composer-paketissa, joten se ei koskaan näy composer.lock-tiedostossa. Ajamasi PHP tulee peruskuvastasi tai käyttöjärjestelmän paketeista, mikä on tämän oppaan kolmas kerros. Versiotaulukko, aikajana ja skripti, joka tarkistaa jokaisen kuvatagin: PHP2Shell: mitä paikata ja miten löytää jokainen haavoittuva PHP.

PHP-haavoittuvuudet, joilla on yhä merkitystä

PHP-sovellusten haavoittuvuusluokat ovat tuskin muuttuneet vuosikymmeneen. Muuttunutta on se, että Laravel, Symfony ja moderni PHP sulkevat useimmat niistä oletuksena, joten todelliset bugit kasautuvat kohtiin, joissa koodi kiertää nämä oletukset. Injektio on kohta A05 OWASP Top 10:2025 -listalla, ja Software Supply Chain Failures on uutena kohdassa A03, joka on tämän oppaan jälkimmäisen puoliskon aihe.

Haavoittuvuus Miten se näkyy PHP:ssä Korjaus Laravel ja Symfony
SQL-injektio Pyynnön data liitetään SQL:ään, myös raakakyselyjen ohitusreittien kautta PDO:n valmistellut lauseet sidotuilla parametreilla Eloquent ja Doctrine sitovat parametrit; whereRaw(), DB::raw() ja yhdistelty DQL eivät
Cross-site scripting (XSS) Käyttäjän syöte tulostetaan HTML:ään ilman escapointia Escapoi tulostettaessa htmlspecialchars()-funktiolla tai mallimoottorilla Bladen {{ }} ja Twig escapoivat oletuksena; {!! !!} ja |raw eivät
Cross-site request forgery (CSRF) Tilaa muuttava lomake tai AJAX-päätepiste hyväksyy minkä tahansa pyynnön, jossa on istuntoeväste Istuntokohtainen token, joka tarkistetaan jokaisessa POST-, PUT-, PATCH- ja DELETE-pyynnössä, sekä SameSite-evästeet Laravel tarkistaa tokenin web-reiteillä (@csrf lomakkeissa); Symfony Forms sisältää ja tarkistaa sen oletuksena; tarkistuksesta pois jätetyillä reiteillä sitä ei ole
Koodi-injektio eval()-kutsun kautta Pyynnön data tai käyttäjän muokattavissa oleva malli tai kaava päätyy eval()-kutsuun Älä käytä eval()-kutsua; kuvaa syöte kiinteään joukkoon operaatioita tai käytä jäsennintä tarvitsemillesi lausekkeille Mikään kehys ei suojaa omia eval()-kutsujasi
Objekti-injektio unserialize() evästeille, pyynnön datalle tai välimuistimerkinnöille, joihin hyökkääjä voi kirjoittaa Käytä JSONia ulkopuoliselle datalle; jos sinun on pakko deserialisoida, hylkää objektit Mikään kehys ei suojaa omia unserialize()-kutsujasi
Tiedostojen sisällyttäminen ja lataukset Syötteestä muodostettu polku välitetään include-lauseelle; lataukset tallennetaan web-juuren alle asiakkaan tiedostonimellä Salli sisällytettävät tiedostot sallittujen listalla; tallenna lataukset web-juuren ulkopuolelle generoiduilla nimillä Laravelin store() generoi tiedostonimet; validoi tyyppi ja koko palvelimella
Tyyppimuunnokset (type juggling) == vertaa tokeneita tai tiivisteitä, jolloin "0e123" == "0e456" on tosi ===, hash_equals() salaisuuksille, password_verify() salasanoille Kehysten apufunktiot vertaavat turvallisesti; omat vertailusi ovat sinun vastuullasi
Heikot istunnot ja evästeet Istuntotunnisteet ovat JavaScriptin luettavissa, kulkevat HTTP:n yli tai säilyvät kirjautumisen yli Secure-, HttpOnly- ja SameSite-evästeet, strict mode, uusi istuntotunniste kirjautumisen jälkeen Molemmat luovat istuntotunnisteen uudelleen kirjautumisessa
Server-side request forgery (SSRF) file_get_contents() tai cURL käyttäjän antamaan URL-osoitteeseen, myös wrappereilla kuten file:// ja phar:// Salli skeemat ja palvelimet sallittujen listalla, estä sisäiset osoitteet, poista allow_url_fopen käytöstä, jos sitä ei tarvita Ei oletussuojausta
Komentoinjektio Syöte välitetään funktioille exec(), shell_exec(), system() tai proc_open() Vältä shelliä; välitä argumentit taulukkona tai escapoi kukin escapeshellarg()-funktiolla Symfony Process ja Laravelin Process-fasadi hyväksyvät argumenttitaulukon
Massasijoitus (mass assignment) User::create($request->all()) antaa pyynnön asettaa kentän is_admin Luettele täytettävät kentät tai välitä vain validoitu syöte Eloquent-mallit ovat oletuksena suojattuja; $guarded = [] poistaa suojauksen

SQL-injektio: haavoittuva ja korjattu

// 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: haavoittuva ja korjattu

<?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 #}

Objekti-injektio: haavoittuva ja korjattu

// 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]);

Phar-deserialisointia, muunnelmaa, jossa phar://-polkuun kohdistuva tiedosto-operaatio deserialisoi arkiston metatiedot, rajattiin PHP 8.0:ssa: phar-metatietoja ei enää deserialisoida, kun phar avataan, vaan vain, kun koodi kutsuu getMetadata()-metodia. Se on yksi syy lisää jättää PHP 7 taakse.

Tiedostojen sisällyttäminen: haavoittuva ja korjattu

// 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;

Konfiguraation ja ajoympäristön koventaminen

Turvallinenkin sovellus voi vuotaa ajoympäristönsä kautta. Nämä php.ini-asetukset noudattavat OWASP PHP Configuration Cheat Sheetiä:

Asetus Tuotantoarvo Miksi
display_errors Off (ja log_errors = On) Pinojäljet paljastavat polkuja, kyselyjä ja joskus salaisuuksia
expose_php Off Lopettaa tarkan PHP-version mainostamisen vastausotsakkeissa
allow_url_include Off Estää etä-URL-osoitteiden include-sisällyttämisen; vanhentunut PHP 7.4:stä alkaen
allow_url_fopen Off, jos sovellus ei hae URL-osoitteita tiedostofunktioilla Kaventaa SSRF:ää file_get_contents()-funktion kautta
disable_functions Esimerkiksi exec, shell_exec, system, passthru, proc_open, popen Vie shellin hyökkääjältä, joka saa koodia ajoon; luettele vain funktiot, joita sovelluksesi ei koskaan kutsu
open_basedir Sovelluksen hakemisto Rajaa polut, joita PHP:n tiedostofunktiot voivat avata; suojakaide, ei eristysraja
Istuntoevästeet session.cookie_secure = 1, session.cookie_httponly = 1, session.cookie_samesite = Lax tai Strict, session.use_strict_mode = 1 Pitää istuntotunnisteet poissa salaamattomasta HTTP:stä ja JavaScriptin ulottuvilta ja hylkää tunnisteet, joita palvelin ei ole myöntänyt
file_uploads Off, jos sovellus ei vastaanota latauksia Poistaa kokonaisen syötepolun

Pysy tuetussa PHP-versiossa

PHP antaa jokaiselle haaralle kaksi vuotta aktiivista tukea ja sen jälkeen kaksi vuotta pelkkiä tietoturvakorjauksia, kertoo php.net. PHP 8.1:n elinkaari päättyi 31.12.2025, ja PHP 8.2 seuraa 31.12.2026. Elinkaarensa päähän tullut haara ei saa PHP-projektilta uusia julkaisuja, ja juuri siihen asemaan PHP2Shell asetti 8.1:n käyttäjät: heidän ainoa virallinen korjauksensa oli lähdekoodin backport.

  • PHP 8.1 End of life, päättyi 31.12.2025
  • PHP 8.2 Vain tietoturvakorjaukset, 31.12.2026 asti
  • PHP 8.3 Vain tietoturvakorjaukset, 31.12.2027 asti
  • PHP 8.4 Aktiivinen tuki, 31.12.2026 asti
  • PHP 8.5 Aktiivinen tuki, 31.12.2027 asti
PHP:n tuetut versiot (päivämäärät php.netistä)

Laravelin ja Symfonyn tuotantoasetukset

  • Debug pois päältä. APP_DEBUG=false Laravelissa, APP_ENV=prod ja APP_DEBUG=0 Symfonyssa. Tuotannossa näkyvä debug-sivu paljastaa konfiguraation ja ympäristömuuttujien arvot.
  • Avaimet ja salaisuudet. Pidä APP_KEY (Laravel) ja APP_SECRET (Symfony) poissa repositoriosta ja vaihda ne, jos ne vuotavat. Laravelin APP_PREVIOUS_KEYS antaa vaihtaa avaimen kirjaamatta jokaista käyttäjää ulos.
  • Luotetut välityspalvelimet. Kuormantasaajan takana luettele luottamasi välityspalvelimet (Laravelin trusted proxies -middleware, Symfonyn framework.trusted_proxies), jottei asiakas voi väärentää IP-osoitettaan tai HTTPS:ää X-Forwarded-*-otsakkeilla.

PHP:n staattinen analyysi (SAST)

Staattinen analyysi lukee koodiasi ajamatta sitä. Kolme avoimen lähdekoodin työkalua riittää useimmille PHP-tiimeille:

  • PHPStan löytää tyyppivirheitä ja bugeja kasvavilla tiukkuustasoilla. Se ei ole tietoturvaskanneri, mutta moni tietoturvabugi alkaa tyyppisekaannuksena tai tarkistamattomana null-arvona.
  • Psalm lisää taint-analyysin: se seuraa dataa lähteistä, kuten $_GET ja $_COOKIE, kohteisiin, kuten echo, include ja SQL-kyselyt, ja raportoi ongelmia, kuten TaintedSql ja TaintedHtml. Nykyinen Psalm ajaa sen tyyppitarkistuksen yhteydessä; --taint-analysis ottaa sen käyttöön yksittäiselle ajolle, ja --report=results.sarif kirjoittaa SARIF-tiedoston GitHubin code scanningia varten.
  • Semgrep tukee PHP:tä yleisesti saatavilla olevana kielenä, ja siinä on yhteisön säännöt yleisille PHP-virheille sekä funktiorajat ylittävä datavirta-analyysi kaupallisessa versiossa.

SAST tuottaa vanhassa PHP:ssä paljon kohinaa rakenteellisista syistä: dynaamiset include-lauseet, muuttujamuuttujat, maagiset metodit, kuten __get() ja __call(), sekä kehysten käytännöt, jotka kuljettavat dataa säiliöiden ja fasadien läpi, joita analysaattori ei pysty seuraamaan. Työkalu joko arvaa ja raportoi vääriä positiivisia tai luovuttaa ja ohittaa todellisia datavirtoja.

Vanhaan koodikantaan sen käyttöönotto toimii parhaiten räikkänä. Tallenna nykyiset löydökset lähtötasoksi (vendor/bin/phpstan analyse --generate-baseline kirjoittaa phpstan-baseline.neon-tiedoston; Psalmissa on --set-baseline), kaada buildi vain uusista löydöksistä ja nosta tasoa sitä mukaa kuin lähtötaso pienenee. Kuulemme pitkäikäisiä PHP-koodikantoja ylläpitäviltä tiimeiltä, että tekoälyn autofix toimii hyvin riippuvuuksien versionnostoissa ja epätasaisesti koodilöydöksissä, joten tarkista SAST-löydösten generoidut korjaukset yhtä huolellisesti kuin minkä tahansa muun muutoksen. SAST ja SCA vastaavat eri kysymyksiin; katso SAST vs. SCA.

Composer-riippuvuuksien tietoturva

Laravel- tai Symfony-sovellus asentaa kymmeniä Composer-paketteja, joista useimmat ovat transitiivisia. Composer on saanut viime julkaisuissa todellisia tietoturvaominaisuuksia; tämä osio vastaa Composer 2.11:tä, nykyistä julkaisua.

composer audit

composer audit tarkistaa asentamasi paketit tietoturvatiedotteita vasten, jotka tulevat Packagist.orgin API:sta. Se kerää tiedotteita muun muassa GitHub Advisory Databasesta ja FriendsOfPHP/security-advisoriesista. Lisäksi se raportoi hylätyt paketit ja Composer 2.10:stä alkaen haittaohjelmiksi merkityt pakettiversiot. Kaikki valinnat löydät Composerin dokumentaatiosta.

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

Composer 2.10:stä alkaen paluukoodi on 0, kun ongelmia ei ole, ja 1, kun jokin ei läpäissyt tarkistusta, joten se toimii CI-porttina ilman tulosteen jäsentämistä. Hylätyt paketit kaatavat tarkistuksen oletuksena.

Hyväksytyt löydökset kuuluvat composer.json-tiedostoon perusteluineen, ei CI-lippuun, jota kukaan ei muista. Composer 2.10 korvasi suurimman osan vanhasta audit-konfiguraatiosta policy-lohkolla:

{
    "config": {
        "policy": {
            "advisories": {
                "ignore-id": {
                    "CVE-1234": "Not affected: we never call the vulnerable parser."
                }
            },
            "abandoned": {
                "audit": "report"
            }
        }
    }
}

Vanhat avaimet (audit.ignore, audit.abandoned, audit.block-insecure) toimivat yhä varavaihtoehtona niin kauan kuin vastaava policy-osio puuttuu, mutta ne ovat vanhentuneita. Siirrä kaikki tiedoteasetukset kerralla: kun policy.advisories on olemassa, jokainen tiedotteisiin liittyvä audit.*-avain ohitetaan.

Turvattomien versioiden estäminen

Composer 2.9:stä (marraskuu 2025) alkaen composer update ja composer require kieltäytyvät pakettiversioista, joista on tietoturvatiedote. Asetus oli ennen audit.block-insecure ja on nyt policy.advisories.block, oletuksena päällä. Composer 2.10 lisäsi haittaohjelmien eston Packagistin haittaohjelmasyötteestä, ja se koskee myös composer install -komentoa. Composer 2.11 lisäsi valinnaisen cooldown-käytännön, joka pidättää juuri julkaistuja versioita valitsemasi ajan, kuten "7 days", koska monet haitalliset versiot löydetään ja poistetaan tuntien tai päivien kuluessa.

CI:ssä kaksi yksityiskohtaa on tärkeää. Ensinnäkin tiedotteisiin perustuva esto koskee versioiden ratkaisemista: composer.lock-tiedostossa jo oleva versio asentuu edelleen composer install -komennolla tiedotteen julkaisun jälkeen, joten tarvitset yhä composer audit -tarkistuksen porttina. Toiseksi --no-blocking poistaa kaikki käytännöt käytöstä yhdeltä komennolta, joten sitä kannattaa etsiä putkistasi.

Ennen Composer 2.9:ää tavallinen tapa estää turvattomat versiot oli roave/security-advisories, paketti, joka ilmoittaa conflict-ristiriidan jokaisen tunnetun haavoittuvan version kanssa ja on koottu GitHub Advisory Databasesta ja FriendsOfPHP:stä. Se toimii yhä ja auttaa edelleen vanhemmissa Composer-versioissa; Composer 2.9:ssä ja uudemmissa sisäänrakennettu esto kattaa samat tiedotteet ilman lisäpakettia.

Lukitustiedoston kurinalaisuus

  • Tallenna sovellusten composer.lock versionhallintaan, jotta jokainen ympäristö asentaa samat versiot.
  • Käytä CI:ssä ja käyttöönotoissa composer install -komentoa, älä koskaan composer update -komentoa, joka ratkaisee uudet versiot lennossa.
  • Asenna tuotantobuildit valinnalla --no-dev, jotta testityökalut eivät koskaan päädy toimitukseen.
  • Aja composer validate --strict: se epäonnistuu, kun composer.lock ei ole ajan tasalla composer.json-tiedoston kanssa.

Lukitustiedosto (lockfile) kirjaa, mitä Composer ratkaisi. Katso mitä lukitustiedosto ei kirjaa.

Asennuksenaikainen koodi: lisäosat, skriptit ja automaattisesti ladattavat tiedostot

Composer ajaa riippuvuuksiesi koodia useammassa kohdassa kuin useimmat tiimit odottavat, mikä tekee siitä PHP:n vastineen npm:n asennusskripteille:

  • Lisäosat (plugins) ajetaan itse Composerin sisällä. Composer 2.2:sta alkaen allow-plugins -asetuksessa on lueteltava jokainen lisäosa, jonka ajaminen sallitaan, ja oletuksena se ei salli yhtäkään. Kohtele uutta merkintää siellä kuin uutta riippuvuutta, jolla on shell-pääsy.
  • Skriptit ajetaan vain juuritason composer.json-tiedostosta, eivät riippuvuuksista. Usein ne kuitenkin käynnistävät sovelluksesi.
  • Automaattisesti ladattavat tiedostot ajetaan aina, kun autoloader ladataan. Tavallisessa Laravel-sovelluksessa composer install päättyy komentoon php artisan package:discover, joka lataa autoloaderin, joten riippuvuuden automaattisesti ladattavat files-tiedostot suoritetaan CI-jobissasi asennuksen aikana.

Jos jobi ei tarvitse lisäosia tai skriptejä, --no-plugins --no-scripts estää riippuvuuksien koodin ajamisen asennuksen aikana; aja tarvitsemasi vaiheet erikseen sen jälkeen.

Repositoriolähteet ja dependency confusion

Composer voi asentaa Packagist.orgista, yksityisistä Composer-repositorioista, kuten Private Packagistista, VCS-repositorioista (Git-URL-osoitteet) ja paikallisista poluista. Yksityisten ja julkisten lähteiden sekoittaminen on dependency confusionin ydin: jos yksityisen pakettisi nimi on myös Packagist.orgissa suuremmalla versiolla, kumman saat? Composer 2:ssa jokainen repositorio on kanoninen oletuksena: kun Composer löytää paketin repositoriosta, se ei etsi sitä listalta alempaa, ja Packagist.org on aina viimeisenä. "canonical": false repositoriossa poistaa tämän suojan, joten tee se vain tarkoituksella. VCS-repositoriot hakevat sen, mitä Git-palvelin tarjoaa tagille, joten kiinnitä ne lukitustiedostossa committiin ja tarkista niiden muutokset.

Hylätyt ja ylläpitämättömät paketit

Vanhoissa PHP-ympäristöissä on paketteja, joiden ylläpitäjät ovat siirtyneet muualle. Packagist merkitsee paketin hylätyksi, kun sen ylläpitäjä niin ilmoittaa, usein korvaajan nimeten, ja composer audit kaatuu siihen oletuksena. Monia ylläpitämättömiä paketteja ei kuitenkaan koskaan virallisesti hylätä. Katso viimeisimmän julkaisun päivämäärä kaikesta, mikä käsittelee syötteitä, tunnistautumista tai kryptografiaa, ja suunnittele korvaajat ennen kuin haavoittuvuus pakottaa siihen.

Turvallinen päivittäminen, kun testejä on vähän

Vaikein tapaus on suuri vanha sovellus, jossa on haavoittuva riippuvuus ja vähän testejä. Mikä toimii:

  1. Päivitä paketti, joka tuo haavoittuvan mukanaan. Useimmat löydökset ovat transitiivisia; aja composer why vendor/package nähdäksesi ketjun ja päivitä ensin ylävirran paketti sen rajoitteen sisällä.
  2. Päivitä yksi asia kerrallaan. composer update vendor/package --with-dependencies pitää muutoksen niin pienenä, että sen voi tarkistaa composer.lock-tiedoston diffistä.
  3. Savutestaa kriittiset polut ensin. Kirjautuminen, kassa, tärkeimmät API-päätepisteet: kourallinen päästä päähän -tarkistuksia löytää useimmat päivitysten rikkoutumiset.
  4. Lisää karakterisointitestejä sen ympärille, mihin päivitys koskee. Kirjaa, mitä koodi tekee nyt, jotta huomaat, kun päivitys muuttaa sitä.
  5. Ota käyttöön vaiheittain ja seuraa virhemääriä ennen kuin muutos tavoittaa jokaisen asiakkaan.

Mitä composer audit ei näe

Lukitustiedosto on suunnitelma, ei kirjaus buildista. composer audit lukee vendor/-hakemiston tai composer.lock-tiedoston, joten se ei näe:

  • PHP:tä itseään ja sen laajennuksia, jotka tulevat peruskuvastasi tai käyttöjärjestelmän paketeista (PHP2Shell on yksi esimerkki).
  • Docker-kuvaan asennettuja PECL-laajennuksia ja järjestelmäkirjastoja.
  • Työkaluja, jotka CI hakee curl-komennolla, actioneita, joita se ajaa, ja binäärejä, joita lisäosa tai skripti lataa.
  • Haitallista koodia, jolla ei vielä ole tietoturvatiedotetta tai haittaohjelmamerkintää.

Samat rajoitukset koskevat npm auditia ja pip-auditia niiden ekosysteemeissä.

PHP:n toimitusketjuhyökkäykset

Neljä tapausta näyttää, mihin PHP:hen kohdistuvat toimitusketjuhyökkäykset todella osuvat ja mikä olisi havainnut kunkin.

Maaliskuu 2021: haitalliset commitit php-srciin

28.3.2021 php-src-repositorioon pushattiin kaksi haitallista committia kahden ydinkehittäjän nimissä. PHP-tiimi ilmoitti lopettavansa oman Git-palvelimensa ylläpidon ja tekevänsä GitHub-repositorioista kanonisia. Sen jatkoselvitys totesi, että commitit oli pushattu HTTPS:n yli salasanatunnistautumisella, mahdollisesti vuotaneesta käyttäjätietokannasta peräisin olevilla tunnuksilla, eikä vaarantuneen palvelimen kautta. Mikä olisi havainnut sen: ei mikään omassa putkessasi. Hyökkäys kohdistui itse kieleen, mikä muistuttaa, että PHP-ajoympäristökin on riippuvuus.

Huhtikuu 2021: Composerin komentoinjektio (CVE-2021-29472)

Virhe siinä, miten Composer käsitteli Mercurial-repositorioiden URL-osoitteita, mahdollisti komentoinjektion. Packagist kertoi, että Packagist.org ja Private Packagist olivat teoriassa alttiina muokattujen paketti-URL-osoitteiden kautta, ettei se uskonut haavoittuvuutta hyödynnetyn ja että se korjattiin versioissa Composer 2.0.13 ja 1.10.22. Mikä olisi havainnut sen: itse Composerin pitäminen ajan tasalla kehittäjien koneilla ja CI-kuvissa. Paketinhallinta on osa toimitusketjuasi.

Toukokuu 2023: Packagist-tilien kaappaukset

1.5.2023 hyökkääjä kirjautui neljälle passiiviselle Packagist.org-tilille muualla vuotaneilla salasanoilla. Tilit hallitsivat yhteensä 14 pakettia, ja hyökkääjä ohjasi paketit forkattuihin repositorioihin. Forkeissa oli muutettu kuvaus eikä haitallista koodia. Packagist palautti ne seuraavana aamuna. Mikä olisi havainnut sen: versionhallintaan tallennettu composer.lock, koska composer install käyttää lukitustiedostoon kirjattuja lähteitä, sekä lukitustiedoston diffien tarkistaminen odottamattomien URL-osoitteiden varalta, kuten Packagist itse suosittelee. Ylläpitäjille: yksilölliset salasanat ja kaksivaiheinen tunnistautuminen.

2026: kaapatut tagit laravel-langissa ja intercom-php:ssä

Vuonna 2026 hyökkääjät julkaisivat varastetuilla käyttöoikeuksilla haitallisia julkaisuja olemassa olevista paketeista. Packagist mainitsee intercom/intercom-php-paketin (30. huhtikuuta) ja laravel-lang-paketit (22. toukokuuta) toimitusketjun tietoturvaa koskevassa päivityksessään. Laravel-langin tapauksessa versiotagit osoittivat haitallisen forkin committeihin, ja hyötykuorma, src/helpers.php-tiedosto, jonka Composerin autoloader latasi, haki tunnusvarastajan, joka kohdistui pilviavaimiin, SSH-avaimiin ja .env-tiedostoihin. Aikido laski 233 vaarantunutta versiota. Mikä olisi havainnut sen: kun versiot on merkitty, Composer 2.10:n haittaohjelmakäytäntö estää ne jopa composer install -komennossa; sitä ennen cooldown-jakso pitää upouudet versiot poissa, egress-käytäntö estää hyötykuormaa tavoittamasta palvelintaan, ja kunkin buildin kirjaus näyttää, mitkä buildit asensivat huonon version.

Sittemmin Packagist on ilmoittanut hylkäävänsä uudelleen tagatut vakaat versiot, julkaisevansa läpinäkyvyyslokia omistajuus- ja tagimuutoksista ja merkinneensä haittaohjelmia Packagist.orgissa maaliskuusta 2026 alkaen.

PHP-buildin suojaaminen CI:ssä

Buildissa koodisi, riippuvuutesi ja salaisuutesi kohtaavat, joten siellä haitallinen paketti tekee eniten vahinkoa. GitHub Actions -työnkulku tuotantobuildille:

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
  • Vähimpien oikeuksien token. permissions: contents: read estää vaarantunutta vaihetta pushaamasta koodia tai julkaisuja GITHUB_TOKEN-tokenilla. Katso GitHub-tokenin oikeudet.
  • Kiinnitetyt actionit. Täyttä commit-SHA:ta ei voi siirtää niin kuin tagin; kommentti pitää version luettavana Dependabotille tai Renovatelle.
  • Tallenna välimuistiin lataukset, älä vendor/-hakemistoa. Composerin latausvälimuistin tallentaminen pitää composer install -komennon ajossa lukitustiedostoa vasten jokaisessa jobissa. Palautettuun vendor/-hakemistoon luotetaan sellaisenaan, ja välimuisteja voidaan myrkyttää työnkulkujen yli; katso GitHub Actionsin välimuisti.
  • Tallenna, mitä asennettiin. Lukitustiedosto kertoo, mitä pitäisi asentaa. Buildista tallennettu SBOM kertoo, mitä asennettiin, mukaan lukien PHP-ajoympäristö, käyttöjärjestelmän paketit ja Composerin ulkopuoliset lataukset. Katso buildin aikana tallennetut SBOM:t.
  • Rajoita, mihin buildi voi yhdistää. Composer-buildi tarvitsee Packagistin, GitHubin ja yksityisen repositoriosi. Egress-käytäntö, joka sallii vain ne, estää tunnusvarastajaa lähettämästä löytämäänsä.

SBOM:n generointi PHP-projektille

Tavallinen avoimen lähdekoodin vaihtoehto on CycloneDX Composer -lisäosa. Lisäosana se tarvitsee allow-plugins-merkinnän:

composer require --dev cyclonedx/cyclonedx-php-composer
composer CycloneDX:make-sbom --output-format=JSON --output-file=sbom.json

Se lukee composer.json-tiedoston yhdessä asennettujen pakettien tai composer.lock-tiedoston kanssa, joten se luettelee Composer-riippuvuutesi tarkasti eikä mitään muuta. PHP-ajoympäristö, sen laajennukset, peruskuvan apt- tai apk-paketit ja jobin lataamat työkalut eivät ole siinä. Buildin aikana tallennettu SBOM kirjaa nekin, koska se muodostetaan siitä, mitä jobi haki, eikä Composerin tiedostoista. Formaateista ja siitä, mitä SBOM:n pitäisi sisältää: mikä on SBOM.

PHP-tietoturvatyökalut kerroksittain

Kerros Työkalut Mitä ne kattavat Mitä ne ohittavat
Koodi PHPStan, Psalm, Semgrep Bugit ja tyyppivirheet, saastuneet datavirrat, tunnetut turvattomat mallit Haavoittuvat riippuvuudet, ajoympäristö, buildi
Riippuvuudet composer audit, Composerin käytännöt, roave/security-advisories, Dependabot tai Renovate Julkaistut tietoturvatiedotteet, hylätyt ja merkityt paketit, päivitys-PR:t Oma koodisi ja kaikki composer.lock-tiedoston ulkopuolinen
Kuva Konttiskannerit Käyttöjärjestelmän ja kielten paketit buildatussa kuvassa Se, mitä buildi haki eikä säilyttänyt, ja kuvat, joita kukaan ei skannannut
SBOM CycloneDX Composer -lisäosa Composer-paketit CycloneDX-SBOM:na Kaikki Composerin ulkopuolinen
Buildi CRACI Mitä kukin GitHub Actions -jobi todella asensi Composerista ja kaikista muista lähteistä, seurattuna ajan mittaan PHP-koodisi (ei SAST:ia); muut CI-järjestelmät kuin GitHub Actions

Lisää kuvista: konttiskannaus. Vähemmän ja parempia löydöksiä kaikissa kerroksissa: miten vähentää haavoittuvuuskohinaa.

PHP-tietoturva ja CRACI

CRACI on GitHub Actions -runneri, joka tallentaa, mitä kukin jobi todella asentaa. Vaihda runs-on-arvoksi craci, niin jokainen jobin lataama Composer-paketti tallennetaan paketti-URL-osoitteineen (pkg:composer/vendor/package) yhdessä käyttöjärjestelmän paketinhallinnan pakettien, konttikuvien, Git-lähteiden ja muiden saman jobin latausten kanssa, välimuistista palautetut paketit mukaan lukien. Jokainen SBOM:n komponentti oli buildissa.

CRACI täsmää nämä komponentit tunnettuihin haavoittuvuuksiin ja tarkistaa toimittamiesi ohjelmistojen SBOM:t uudelleen aina, kun uusia tietoturvatiedotteita julkaistaan, joten ensi vuonna julkistettu CVE johtaa sinut yhä buildeihin, jotka sisältävät sen. Käytäntöportit voivat pysäyttää buildin löydösten perusteella, myös buildin, joka sisältää tietyn CVE:n, ja egress-käytäntö rajaa, mihin jobi voi yhdistää.

CRACI ei skannaa PHP-koodiasi: SAST:ia ei ole, ja se on tietoinen valinta. Pidä PHPStan, Psalm tai Semgrep koodikerroksessa. Näe jokainen paketti, jonka PHP-buildisi asentaa.

Milloin CRACI ei ole oikea ratkaisu

  • Viet tuotantoon FTP:llä tai jaettuun webhotelliin ilman CI:tä. Tallennettavaa buildia ei ole. Aloita composer audit -tarkistuksella ja versionhallintaan tallennetulla composer.lock-tiedostolla.
  • Riskisi on pääasiassa omassa koodissasi, kuten vanhassa sovelluksessa, joka on täynnä raakaa SQL:ää. SAST, katselmointi ja testit ovat etusijalla.
  • Buildaat GitHub Actionsin ulkopuolella, GitLab CI:ssä tai Bitbucket Pipelinesissa. CRACI tukee tällä hetkellä GitHub Actionsia; GitLab CI ja Jenkins ovat tiekartalla.

PHP-tietoturva: usein kysytyt kysymykset

Miten suojaan PHP-koodin?

Toimi kolmessa kerroksessa. Koodissasi validoi syötteet, käytä valmisteltuja lauseita, luota kehyksen tulosteen escapointiin äläkä koskaan deserialisoi epäluotettavaa dataa. Riippuvuuksissasi tallenna composer.lock versionhallintaan, aja composer audit CI:ssä ja pidä Composerin turvattomien versioiden esto päällä. Buildissasi kiinnitä actionit, rajaa tokenin oikeudet, rajoita verkkoyhteyksiä ja tallenna, mitä jobi asensi.

Mitkä ovat yleisimmät PHP-haavoittuvuudet?

SQL-injektio, cross-site scripting, cross-site request forgery, koodi-injektio eval()-kutsun kautta, objekti-injektio unserialize()-kutsun kautta, tiedostojen sisällyttäminen ja turvattomat lataukset, tyyppimuunnokset ==-vertailussa, heikot istuntoasetukset, SSRF URL-wrappereiden kautta, komentoinjektio ja massasijoitus. Haavoittuvat Composer-riippuvuudet ja paikkaamattomat PHP-versiot, kuten PHP2Shell-bugit, ovat yhtä yleinen tietoturvapoikkeamien syy.

Mikä on paras PHP-tietoturvaskanneri?

Mikään yksittäinen työkalu ei kata PHP-tietoturvaa. Käytä koodillesi staattista analysaattoria, kuten PHPStan, Psalm tai Semgrep, Composer-riippuvuuksillesi composer auditia, kuvalle konttiskanneria ja sille, mitä CI todella asensi, buildin aikaista kirjausta.

Miten composer audit toimii, ja mitä se ei näe?

Se tarkistaa asennetut paketit, tai --locked-valinnalla composer.lock-tiedoston, Packagist.orgin API:n tietoturvatiedotteita vasten ja raportoi hylätyt ja haittaohjelmiksi merkityt paketit. Se palauttaa paluukoodin 1, kun jokin ei läpäise tarkistusta. Se ei näe PHP:tä itseään, PECL-laajennuksia, kuvasi käyttöjärjestelmäpaketteja eikä mitään, mitä CI lataa Composerin ulkopuolelta.

Miten estän turvattomat Composer-paketit CI:ssä?

Käytä Composer 2.9:ää tai uudempaa, joka estää oletuksena versiot, joista on tietoturvatiedote, update- ja require-komennoissa (policy.advisories.block). Koska lukittu haavoittuva versio asentuu silti, aja myös composer audit composer installin jälkeen ja kaada jobi sen paluukoodin perusteella. Älä koskaan käytä --no-blocking-valintaa CI:ssä.

Onko Laravel oletuksena turvallinen?

Enimmäkseen. Blade escapoi tulosteen, Eloquent sitoo kyselyparametrit, mallit on suojattu massasijoitukselta ja istuntotunniste luodaan uudelleen kirjautumisessa. Nämä oletukset lakkaavat toimimasta, kun koodi käyttää {!! !!}-syntaksia, whereRaw()-kutsua syötteen kanssa, $guarded = [] -asetusta tai APP_DEBUG=true-asetusta tuotannossa, tai kun APP_KEY vuotaa.

Miten päivitän riippuvuudet vanhassa PHP-sovelluksessa, jossa on vähän testejä?

Päivitä ylävirran paketti, joka tuo haavoittuvan mukanaan, yksi paketti kerrallaan komennolla composer update vendor/package --with-dependencies. Savutestaa kriittiset polut, lisää karakterisointitestejä sen ympärille, mihin päivitys koskee, ja ota muutos käyttöön vaiheittain.

Miten generoin SBOM:n PHP-projektille?

CycloneDX Composer -lisäosa (cyclonedx/cyclonedx-php-composer) generoi sen Composer-paketeistasi komennolla composer CycloneDX:make-sbom. Se luettelee vain Composer-paketit. Buildin aikana tallennettu SBOM kirjaa myös PHP-ajoympäristön, käyttöjärjestelmän paketit ja muut buildin lataukset.

Mikä on PHP2Shell?

PHP2Shell on nimitys haavoittuvuuksille CVE-2026-109618 ja CVE-2026-109619. Ne ovat kaksi kriittistä haavoittuvuutta itse PHP:ssä, ja ne voivat mahdollistaa etäkoodin suorittamisen ilman tunnistautumista. Ne on korjattu versioissa PHP 8.5.12, 8.4.27, 8.3.36 ja 8.2.35, jotka julkaistiin 21.10.2026. Katso PHP2Shell.

Näe jokainen paketti, jonka PHP-buildisi asentaa

Aja yksi GitHub Actions -työnkulku CRACI:lla ja vertaa sen tallennettua SBOM:ia composer.lock-tiedostoosi.

Varaa demo