PHP-sikkerhet
PHP-sikkerhet: slik forebygger du PHP-sårbarheter i koden, Composer-avhengighetene og bygget ditt
Å sikre PHP handler om tre lag. I koden du skriver: valider input, bruk parametriserte spørringer og rammeverkets escaping, og deserialiser aldri data du ikke kan stole på. I Composer-pakkene du er avhengig av: kjør composer audit, commit composer.lock og blokker usikre versjoner. I CI-en som installerer dem: registrer hva som faktisk ble installert, og begrens hvor bygget kan koble seg til. De fleste veiledninger om PHP-sikkerhet stopper ved det første laget. Denne dekker alle tre.
Oppdatert
- Koden din Injection, XSS, deserialisering, filhåndtering, sesjoner Rammeverkets standardinnstillinger, kodegjennomgang, PHPStan, Psalm, Semgrep
- Composer-avhengighetene dine Kjente sårbarheter, forlatte og ondsinnede pakker, lockfile-drift, kode som kjører ved installasjon composer audit, Composer-policyer, Dependabot eller Renovate
- Bygget ditt Hva CI faktisk installerte og kjørte, hvor det ble hentet fra, hva det kunne nå Låste actions, tokens med minste rettigheter, egress-policy, SBOM fra bygget
På denne siden: PHP2Shell, PHP-sårbarhetene som fortsatt betyr noe, konfigurasjon og støttede versjoner, statisk analyse, sikkerhet for Composer-avhengigheter, forsyningskjedeangrep mot PHP, CI-bygget, SBOM-er og verktøy per lag.
PHP2Shell: PHP-sårbarheten du bør patche nå
21. oktober 2026 ga PHP-prosjektet ut PHP 8.5.12, 8.4.27, 8.3.36 og 8.2.35 for å rette to kritiske sårbarheter i selve PHP som kan åpne for fjernkjøring av kode uten autentisering. Begge har score 9,2 etter CVSS v4.0, og sammen er de kjent som PHP2Shell:
- CVE-2026-109618: håndteringen av minnemangel kan avbryte PHP mens forespørselsdata bare er delvis initialisert, og oppryddingen etter forespørselen kan da behandle de ugyldige dataene som gyldige.
- CVE-2026-109619: tilstand som tilhører en forespørsel, kan overleve etter at minnet er frigjort, og den neste forespørselen som samme worker håndterer, kan bruke den.
Ifølge PHPs advisory er alle installasjoner som betjener webforespørsler utsatt, blant annet PHP-FPM, Apache med mod_php, php-cgi, FrankenPHP og LiteSpeed, og mod_php vurderes som den største risikoen. PHP 8.1 og eldre får ingen ny release fra PHP-prosjektet. På grunn av alvorlighetsgraden publiserte PHPs sikkerhetsteam likevel minimale kildekode-backporter for PHP 7.4, 8.0 og 8.1, for dem som bygger PHP fra kildekode eller ikke har en LTS-leverandør, og noen distribusjoner og LTS-leverandører leverer egne backportede rettinger. Dette bør du gjøre:
- Oppgrader til en rettet release. På 8.1 eller eldre: bytt til en støttet gren, bruk leverandørens backportede pakke, eller bruk den offisielle kildekode-backporten hvis du bygger PHP selv.
-
Bygg hvert containerimage som inneholder PHP på nytt, og deploy det igjen. En gammel image-tagg fortsetter å kjøre den
gamle PHP-versjonen til den blir erstattet, så sjekk hva som er deployet, ikke bare hva som ligger i repositoriet:
docker run --rm IMAGE php -v. -
Hvis du ikke kan oppgradere i dag, bruk de midlertidige tiltakene fra PHPs sikkerhetsadvisory, for eksempel
session.upload_progress.enabled=0. Sett det iphp.ini, FPM-poolen eller serverkonfigurasjon som gjelder før forespørselen tolkes, og start hver worker på nytt. Ikke stol påini_set()eller FPMs.user.inifor dette: advisoryen advarer mot begge. PHP er tydelig på at tiltakene reduserer eksponeringen, men ikke gjør en installasjon trygg.
Legg merke til hva composer audit ikke kan fortelle deg her: PHP2Shell ligger i runtime, ikke i en
Composer-pakke, så den dukker aldri opp i composer.lock. PHP-en du kjører, kommer fra base image eller
OS-pakkene dine, som er det tredje laget i denne veiledningen. Versjonstabellen, tidslinjen og et skript som sjekker
hver image-tagg, finner du i PHP2Shell: hva du må patche, og hvordan du finner hver sårbar PHP.
PHP-sårbarhetene som fortsatt betyr noe
Sårbarhetsklassene i PHP-applikasjoner har knapt endret seg på et tiår. Det som har endret seg, er at Laravel, Symfony og moderne PHP lukker de fleste av dem som standard, så ekte feil samler seg nå der koden går rundt disse standardene. Injection er A05 i OWASP Top 10:2025, og Software Supply Chain Failures er ny som A03, og det er temaet for andre halvdel av denne veiledningen.
| Sårbarhet | Slik viser den seg i PHP | Løsningen | Laravel og Symfony |
|---|---|---|---|
| SQL-injection | Forespørselsdata som settes sammen med SQL, også via nødutganger for rå spørringer | PDO prepared statements med bundne parametere | Eloquent og Doctrine binder parametere; whereRaw(), DB::raw() og sammensatt DQL gjør det ikke |
| Cross-site scripting (XSS) | Brukerinput som skrives ut i HTML uten escaping | Escape ved output med htmlspecialchars() eller malmotoren |
Blade {{ }} og Twig escaper som standard; {!! !!} og |raw gjør det ikke |
| Cross-site request forgery (CSRF) | Et skjema eller et AJAX-endepunkt som endrer tilstand, godtar enhver forespørsel med sesjonsinformasjonskapselen | Et token per sesjon som sjekkes ved hver POST, PUT, PATCH og DELETE, pluss SameSite-informasjonskapsler |
Laravel sjekker tokenet på web-ruter (@csrf i skjemaer); Symfony Forms inneholder og sjekker et som standard; ruter som er unntatt fra sjekken, har ingen beskyttelse |
Kodeinjeksjon via eval() |
Forespørselsdata, eller en mal eller formel som en bruker kan redigere, som når eval() |
Ikke bruk eval(); koble input til et fast sett operasjoner, eller bruk en parser for uttrykkene du trenger |
Ingen rammeverk beskytter dine egne eval()-kall |
| Objektinjeksjon | unserialize() på informasjonskapsler, forespørselsdata eller cacheoppføringer som en angriper kan skrive |
Bruk JSON for data utenfra; hvis du må deserialisere, avvis objekter | Ingen rammeverk beskytter dine egne unserialize()-kall |
| Filinkludering og opplastinger | En sti bygd av input som sendes til include; opplastinger lagret under webroten med klientens filnavn |
Tillatelsesliste for inkluderte filer; lagre opplastinger utenfor webroten under genererte navn | Laravels store() genererer filnavn; valider type og størrelse på serveren |
| Type juggling | == som sammenligner tokens eller hasher, der "0e123" == "0e456" er sant |
===, hash_equals() for hemmeligheter, password_verify() for passord |
Rammeverkets hjelpefunksjoner sammenligner trygt; dine egne sammenligninger er ditt ansvar |
| Svake sesjoner og informasjonskapsler | Sesjons-ID-er som JavaScript kan lese, som sendes over HTTP eller beholdes på tvers av innlogging | Secure-, HttpOnly- og SameSite-informasjonskapsler, strict mode, ny sesjons-ID etter innlogging | Begge genererer en ny sesjons-ID ved innlogging |
| Server-side request forgery (SSRF) | file_get_contents() eller cURL mot en URL brukeren har oppgitt, også via wrappere som file:// og phar:// |
Tillatelsesliste for skjemaer og verter, blokker interne adresser, slå av allow_url_fopen der det ikke brukes |
Ingen beskyttelse som standard |
| Command injection | Input som sendes til exec(), shell_exec(), system() eller proc_open() |
Unngå skallet; send argumenter som en matrise, eller escape hvert av dem med escapeshellarg() |
Symfony Process og Laravels Process-fasade godtar en matrise med argumenter |
| Mass assignment | User::create($request->all()) lar en forespørsel sette is_admin |
List opp feltene som kan fylles ut, eller send bare validert input | Eloquent-modeller er beskyttet som standard; $guarded = [] slår det av |
SQL-injection: sårbar og rettet
// 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: sårbar og rettet
<?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 #}
Objektinjeksjon: sårbar og rettet
// 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-deserialisering, varianten der en filoperasjon på en phar://-sti deserialiserte arkivets metadata,
ble innsnevret i PHP 8.0: phar-metadata blir
ikke lenger deserialisert når et
phar-arkiv åpnes, bare når kode kaller getMetadata(). Det er enda en grunn til å legge PHP 7 bak seg.
Filinkludering: sårbar og rettet
// 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;
Herding av konfigurasjon og runtime
En sikker applikasjon kan likevel lekke gjennom runtime. Disse innstillingene i php.ini følger
OWASP PHP
Configuration Cheat Sheet:
| Innstilling | Verdi i produksjon | Hvorfor |
|---|---|---|
display_errors |
Off (med log_errors = On) |
Stakkspor viser stier, spørringer og noen ganger hemmeligheter |
expose_php |
Off |
Slutter å vise den nøyaktige PHP-versjonen i svarhoder |
allow_url_include |
Off |
Hindrer include av eksterne URL-er; foreldet siden PHP 7.4 |
allow_url_fopen |
Off hvis applikasjonen ikke henter URL-er med filfunksjoner |
Begrenser SSRF via file_get_contents() |
disable_functions |
For eksempel exec, shell_exec, system, passthru, proc_open, popen |
Fjerner skallet for en angriper som får kode til å kjøre; list bare opp det applikasjonen din aldri kaller |
open_basedir |
Applikasjonens katalog | Begrenser stiene PHPs filfunksjoner kan åpne; et rekkverk, ikke en isolasjonsgrense |
| Sesjonsinformasjonskapsler | session.cookie_secure = 1, session.cookie_httponly = 1, session.cookie_samesite = Lax eller Strict, session.use_strict_mode = 1 |
Holder sesjons-ID-er unna ukryptert HTTP og JavaScript, og avviser ID-er som serveren ikke har utstedt |
file_uploads |
Off hvis applikasjonen ikke tar imot opplastinger |
Fjerner en hel inndatavei |
Hold deg på en støttet PHP-versjon
PHP gir hver gren to års aktiv støtte og deretter to år med bare sikkerhetsrettinger, ifølge php.net. PHP 8.1 nådde end of life 31. desember 2025, og PHP 8.2 følger 31. desember 2026. En gren som har nådd end of life, får ingen nye releaser fra PHP-prosjektet, og det er den situasjonen PHP2Shell satte brukerne av 8.1 i: deres eneste offisielle retting var en kildekode-backport.
- PHP 8.1 End of life, endte 31. des. 2025
- PHP 8.2 Bare sikkerhetsrettinger, til 31. des. 2026
- PHP 8.3 Bare sikkerhetsrettinger, til 31. des. 2027
- PHP 8.4 Aktiv støtte, til 31. des. 2026
- PHP 8.5 Aktiv støtte, til 31. des. 2027
Produksjonsinnstillinger for Laravel og Symfony
-
Feilsøking av.
APP_DEBUG=falsei Laravel,APP_ENV=prodogAPP_DEBUG=0i Symfony. En feilsøkingsside i produksjon viser konfigurasjons- og miljøverdier. -
Nøkler og hemmeligheter. Hold
APP_KEY(Laravel) ogAPP_SECRET(Symfony) utenfor repositoriet, og roter dem hvis de lekker. Med LaravelsAPP_PREVIOUS_KEYSkan du rotere nøkkelen uten å logge ut alle brukere. -
Klarerte proxyer. Bak en lastbalanserer lister du opp proxyene du stoler på (Laravels middleware for
trusted proxies, Symfonys
framework.trusted_proxies), slik at en klient ikke kan forfalske IP-adressen sin eller HTTPS medX-Forwarded-*-hoder.
Statisk analyse for PHP (SAST)
Statisk analyse leser koden din uten å kjøre den. Tre verktøy med åpen kildekode dekker de fleste PHP-team:
-
PHPStan finner typefeil og bugs på stadig strengere nivåer. Det er ingen sikkerhetsskanner, men mange
sikkerhetsfeil begynner som typeforveksling eller en ukontrollert
null. -
Psalm legger til taint-analyse:
det følger data fra kilder som
$_GETog$_COOKIEtil mottakere somecho,includeog SQL-spørringer, og rapporterer problemer somTaintedSqlogTaintedHtml. Gjeldende Psalm kjører analysen sammen med typesjekken;--taint-analysisslår den på for én enkelt kjøring, og--report=results.sarifskriver SARIF for GitHub code scanning. - Semgrep støtter PHP som et allment tilgjengelig språk, med fellesskapsregler for vanlige PHP-feil og dataflyt på tvers av funksjoner i det kommersielle nivået.
SAST gir mye støy på eldre PHP av strukturelle grunner: dynamiske includes, variable variabler, magiske metoder som
__get() og __call(), og rammeverkskonvensjoner som sender data gjennom containere og fasader
analysatoren ikke kan følge. Verktøyet enten gjetter, og rapporterer falske positiver, eller gir opp, og går glipp av
ekte dataflyt.
På en gammel kodebase fungerer det best å innføre det som en skralle. Lagre dagens funn som en baseline
(vendor/bin/phpstan analyse --generate-baseline skriver phpstan-baseline.neon; Psalm har
--set-baseline), la bygget feile bare på nye funn, og hev nivået etter hvert som du betaler ned baselinen.
Team med langlivede PHP-kodebaser forteller oss at AI-autofiks fungerer godt for oppdateringer av avhengigheter og er
ujevnt på kodefunn, så gå gjennom genererte rettinger for SAST-funn like nøye som enhver annen endring. SAST og SCA
besvarer ulike spørsmål; se SAST vs SCA.
Sikkerhet for Composer-avhengigheter
En Laravel- eller Symfony-applikasjon installerer dusinvis av Composer-pakker, de fleste av dem transitive. Composer har fått reelle sikkerhetsfunksjoner i de siste versjonene; denne delen tar utgangspunkt i Composer 2.11, den gjeldende versjonen.
composer audit
composer audit sjekker pakkene du har installert, mot sikkerhetsadvisories fra
Packagist.org-API-et, som samler
advisories fra kilder som GitHub Advisory Database og FriendsOfPHP/security-advisories. Det rapporterer også forlatte
pakker og, siden Composer 2.10, pakkeversjoner som er flagget som skadevare. Se
Composer-dokumentasjonen for alle valg.
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
Siden Composer 2.10 er avslutningskoden 0 når det ikke finnes problemer, og 1 når noe ikke
besto revisjonen, så det fungerer som en CI-port uten parsing. Forlatte pakker får revisjonen til å feile som standard.
Aksepterte funn hører hjemme i composer.json, med en begrunnelse, ikke i et CI-flagg som ingen husker.
Composer 2.10 erstattet det meste av den gamle audit-konfigurasjonen med en policy-blokk:
{
"config": {
"policy": {
"advisories": {
"ignore-id": {
"CVE-1234": "Not affected: we never call the vulnerable parser."
}
},
"abandoned": {
"audit": "report"
}
}
}
}
De gamle nøklene (audit.ignore, audit.abandoned, audit.block-insecure) virker
fortsatt som reserve så lenge den tilsvarende policy-delen mangler, men de er foreldet. Migrer alle
advisory-innstillingene på én gang: Så snart policy.advisories finnes, ignoreres alle advisory-relaterte
audit.*-nøkler.
Blokker usikre versjoner
Siden Composer 2.9 (november 2025) avviser composer update og composer require pakkeversjoner
som har en sikkerhetsadvisory. Det var innstillingen audit.block-insecure og er nå
policy.advisories.block, slått på som standard. Composer 2.10 la til blokkering av skadevare fra
Packagists skadevarefeed, og den gjelder også for composer install. Composer 2.11 la til en valgfri
cooldown-policy som holder tilbake nylig utgitte versjoner i en periode du velger, for eksempel
"7 days", fordi mange ondsinnede versjoner blir oppdaget og fjernet i løpet av timer eller dager.
To detaljer betyr noe i CI. For det første gjelder blokkeringen basert på advisories når versjoner løses opp: En versjon
som allerede står i composer.lock, installeres fortsatt med composer install etter at en
advisory er publisert, så du trenger fortsatt composer audit som port. For det andre slår
--no-blocking av alle policyer for én kommando, så det er verdt å søke etter i pipelinene dine.
Før Composer 2.9 var den vanlige måten å blokkere usikre versjoner på
roave/security-advisories, en pakke som
deklarerer en conflict med hver kjente sårbare versjon, bygd fra GitHub Advisory Database og
FriendsOfPHP. Den virker fortsatt og hjelper fortsatt på eldre Composer-versjoner; på Composer 2.9 eller nyere dekker
den innebygde blokkeringen de samme advisoriene uten den ekstra pakken.
Disiplin med lockfilen
- Commit
composer.lockfor applikasjoner, slik at hvert miljø installerer de samme versjonene. -
Bruk
composer installi CI og ved utrulling, aldricomposer update, som løser opp nye versjoner underveis. - Installer produksjonsbygg med
--no-dev, slik at testverktøy aldri blir levert. -
Kjør
composer validate --strict: Det feiler nårcomposer.lockikke er i takt medcomposer.json.
En lockfile registrerer hva Composer løste opp. Se hva en lockfile ikke registrerer.
Kode ved installasjon: plugins, skript og autoloadede filer
Composer kjører kode fra avhengighetene dine flere steder enn de fleste team regner med, noe som gjør det til PHPs motstykke til installasjonsskript i npm:
-
Plugins kjører inne i selve Composer. Siden Composer 2.2 må innstillingen
allow-pluginsliste opp hver plugin som får kjøre, og som standard tillater den ingen. Behandle en ny oppføring der som en ny avhengighet med skalltilgang. -
Skript kjører bare fra rot-
composer.json, ikke fra avhengigheter. Men de starter ofte applikasjonen din. -
Autoloadede filer kjører så snart autoloaderen lastes. I en standard Laravel-applikasjon avsluttes
composer installmedphp artisan package:discover, som laster autoloaderen, så en avhengighets autoloadedefileskjører i CI-jobben din under installasjonen.
Hvis en jobb ikke trenger plugins eller skript, hindrer --no-plugins --no-scripts kode fra avhengigheter i
å kjøre under installasjonen; kjør trinnene du trenger, eksplisitt etterpå.
Pakkekilder og dependency confusion
Composer kan installere fra Packagist.org, private Composer-repositorier som Private Packagist, VCS-repositorier
(Git-URL-er) og lokale stier. Der private og offentlige kilder blandes, oppstår
dependency confusion: Hvis navnet på den private pakken din også finnes på
Packagist.org med en høyere versjon, hvilken får du da? I Composer 2 er
hvert repositorium
kanonisk som standard: Når Composer har funnet en pakke i et repositorium, leter det ikke etter den lenger ned i
listen, og Packagist.org kommer alltid sist. Å sette "canonical": false på et repositorium fjerner den
beskyttelsen, så gjør det bare med vilje. VCS-repositorier henter det Git-verten leverer for en tagg, så lås dem til en
commit i lockfilen, og gå gjennom endringer i dem.
Forlatte og uvedlikeholdte pakker
Eldre PHP-miljøer inneholder pakker der vedlikeholderne har gått videre. Packagist merker en pakke som forlatt når
vedlikeholderen sier det, ofte med en erstatning, og composer audit feiler på den som standard. Mange
uvedlikeholdte pakker blir likevel aldri formelt forlatt. Se på datoen for siste versjon for alt som håndterer input,
autentisering eller kryptografi, og planlegg erstatninger før en sårbarhet tvinger deg til det.
Oppdater trygt når det finnes få tester
Det vanskeligste tilfellet er en stor, eldre applikasjon med en sårbar avhengighet og få tester. Dette fungerer:
-
Oppdater pakken som drar inn den sårbare. De fleste funn er transitive; kjør
composer why vendor/packagefor å se kjeden, og oppdater først pakken lenger opp innenfor versjonskravet. -
Oppdater én ting om gangen.
composer update vendor/package --with-dependenciesholder endringen liten nok til å gå gjennom i diffen forcomposer.lock. - Røyktest de kritiske flytene først. Innlogging, kasse, de viktigste API-endepunktene: En håndfull ende-til-ende-sjekker fanger de fleste feil som oppgraderinger fører til.
- Legg til karakteriseringstester rundt det oppgraderingen berører. Registrer hva koden gjør i dag, slik at du merker det når oppgraderingen endrer det.
- Rull ut trinnvis og følg med på feilratene før endringen når alle kunder.
Hva composer audit går glipp av
Lockfilen er en plan, ikke en registrering av bygget. composer audit leser vendor/ eller
composer.lock, så det ser ikke:
- Selve PHP og utvidelsene, som kommer fra base image eller OS-pakkene dine (PHP2Shell er ett eksempel).
- PECL-utvidelser og systembiblioteker installert i Docker-imaget.
- Verktøy som CI-en din henter med
curl, actions den kjører, og binærfiler som en plugin eller et skript laster ned. - Ondsinnet kode som ennå ikke har en advisory eller et skadevareflagg.
De samme begrensningene gjelder for npm audit og pip-audit i sine økosystemer.
Forsyningskjedeangrep mot PHP
Fire hendelser viser hvor forsyningskjedeangrep mot PHP faktisk treffer, og hva som ville ha fanget hver av dem.
Mars 2021: ondsinnede commits i php-src
28. mars 2021 ble to ondsinnede commits pushet til php-src-repositoriet i navnet til to kjerneutviklere. PHP-teamet kunngjorde at det ville slutte å drive sin egen Git-server og gjøre GitHub-repositoriene kanoniske. Dets oppfølging fant at commitene var pushet over HTTPS med passordautentisering, muligens med påloggingsopplysninger fra en lekket brukerdatabase, og ikke gjennom en kompromittert server. Hva som ville ha fanget det: ingenting i din egen pipeline. Angrepet rettet seg mot selve språket, en påminnelse om at PHP-runtime også er en avhengighet.
April 2021: command injection i Composer (CVE-2021-29472)
En feil i hvordan Composer håndterte URL-er til Mercurial-repositorier, åpnet for command injection. Packagist rapporterte at Packagist.org og Private Packagist teoretisk var eksponert gjennom manipulerte pakke-URL-er, mente ikke at feilen var utnyttet, og rettet den i Composer 2.0.13 og 1.10.22. Hva som ville ha fanget det: å holde selve Composer oppdatert på utviklermaskiner og i CI-images. Pakkebehandleren er en del av forsyningskjeden din.
Mai 2023: overtakelse av Packagist-kontoer
1. mai 2023 logget en angriper inn på fire inaktive Packagist.org-kontoer med passord som hadde lekket andre steder.
Kontoene kontrollerte til sammen 14 pakker, og angriperen pekte pakkene til forkede repositorier. Forkene inneholdt en
endret beskrivelse og ingen ondsinnet kode.
Packagist
gjenopprettet dem neste morgen. Hva som ville ha fanget det: en committet composer.lock,
siden composer install bruker kildene som er registrert i lockfilen, og gjennomgang av differ i lockfilen
for uventede URL-er, noe Packagist selv anbefaler. For vedlikeholdere: unike passord og tofaktorautentisering.
2026: kaprede tagger på laravel-lang og intercom-php
I 2026 publiserte angripere med stjålet tilgang ondsinnede versjoner av eksisterende pakker. Packagist nevner
intercom/intercom-php (30. april) og laravel-lang-pakkene (22. mai) i sin
oppdatering
om forsyningskjedesikkerhet. I laravel-lang-saken pekte versjonstagger til commits i en ondsinnet fork, og
nyttelasten, en src/helpers.php-fil som
ble
lastet av Composers autoloader, hentet en credential stealer for skynøkler, SSH-nøkler og .env-filer.
Aikido telte 233 kompromitterte versjoner. Hva som ville ha fanget det: Når versjonene først er
flagget, blokkerer skadevarepolicyen i Composer 2.10 dem selv ved composer install; før det holder en
cooldown-periode helt nye versjoner ute, en egress-policy hindrer nyttelasten i å nå serveren sin, og en registrering av
hvert bygg viser hvilke bygg som installerte en dårlig versjon.
Siden da har Packagist kunngjort at det avviser stabile versjoner som er tagget på nytt, publiserer en åpenhetslogg over endringer i eierskap og tagger, og har flagget skadevare på Packagist.org siden mars 2026.
Sikre PHP-bygget i CI
Bygget er der koden din, avhengighetene dine og hemmelighetene dine møtes, så det er der en ondsinnet pakke gjør mest skade. En GitHub Actions-arbeidsflyt for et produksjonsbygg:
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 med minste rettigheter.
permissions: contents: readhindrer et kompromittert trinn i å pushe kode eller releaser medGITHUB_TOKEN. Se rettigheter for GitHub-token. - Låste actions. En fullstendig commit-SHA kan ikke flyttes slik en tagg kan; kommentaren holder versjonen lesbar for Dependabot eller Renovate.
-
Cache nedlastinger, ikke
vendor/. Når du cacher Composers nedlastingscache, kjørercomposer installmot lockfilen i hver jobb. En gjenopprettetvendor/blir stolt på som den er, og cacher kan forgiftes på tvers av arbeidsflyter; se GitHub Actions-cache. - Registrer hva som ble installert. Lockfilen sier hva som burde installeres. En SBOM registrert fra bygget sier hva som ble installert, inkludert PHP-runtime, OS-pakker og nedlastinger utenfor Composer. Se SBOM-er fra bygget.
- Begrens hvor bygget kan koble seg til. Et Composer-bygg trenger Packagist, GitHub og det private repositoriet ditt. En egress-policy som bare tillater disse, hindrer en credential stealer i å sende det den fant.
Generer en SBOM for et PHP-prosjekt
Det vanlige alternativet med åpen kildekode er
CycloneDX-pluginen for Composer.
Som plugin trenger den en oppføring i allow-plugins:
composer require --dev cyclonedx/cyclonedx-php-composer
composer CycloneDX:make-sbom --output-format=JSON --output-file=sbom.json
Den leser composer.json sammen med de installerte pakkene eller composer.lock, så den lister
Composer-avhengighetene dine nøyaktig og ingenting annet. PHP-runtime, utvidelsene, base imagets apt- eller apk-pakker
og verktøyene jobben lastet ned, er ikke med. En SBOM fra bygget registrerer også disse, fordi den lages ut fra det
jobben hentet, og ikke ut fra Composers filer. For formatene og hva en SBOM bør inneholde, se
hva er en SBOM.
PHP-sikkerhetsverktøy per lag
| Lag | Verktøy | Hva de dekker | Hva de går glipp av |
|---|---|---|---|
| Kode | PHPStan, Psalm, Semgrep | Bugs og typefeil, dataflyt med upålitelige data, kjente usikre mønstre | Sårbare avhengigheter, runtime, bygget |
| Avhengigheter | composer audit, Composer-policyer, roave/security-advisories, Dependabot eller Renovate |
Publiserte advisories, forlatte og flaggede pakker, pull requests med oppdateringer | Din egen kode, og alt utenfor composer.lock |
| Image | Containerskannere | OS- og språkpakker i et bygd image | Det bygget hentet uten å beholde, og images ingen har skannet |
| SBOM | CycloneDX-pluginen for Composer | Composer-pakker som en CycloneDX-SBOM | Alt utenfor Composer |
| Bygg | CRACI | Hva hver GitHub Actions-jobb faktisk installerte, fra Composer og alle andre kilder, overvåket over tid | PHP-koden din (ingen SAST); andre CI-systemer enn GitHub Actions |
Mer om images: containerskanning. Færre og bedre funn på tvers av alle lag: slik reduserer du støyen fra sårbarheter.
PHP-sikkerhet og CRACI
CRACI er en GitHub Actions-runner som registrerer hva hver jobb faktisk installerer. Endre runs-on til
craci, så blir hver Composer-pakke jobben laster ned, registrert med sin package URL
(pkg:composer/vendor/package), sammen med pakker fra OS-ets pakkebehandler, containerimages, Git-kilder
og andre nedlastinger i samme jobb, inkludert pakker gjenopprettet fra CI-cacher. Hver komponent i SBOM-en var i
bygget.
CRACI matcher disse komponentene mot kjente sårbarheter og fortsetter å sjekke SBOM-ene for det du har levert på nytt etter hvert som nye advisories publiseres, så en CVE som offentliggjøres neste år, fortsatt fører deg til byggene som inneholder den. Policy gates kan stoppe et bygg ved funn, også et bygg som inneholder en bestemt CVE, og egress-policyen begrenser hvor jobben kan koble seg til.
CRACI skanner ikke PHP-koden din: Det finnes ingen SAST, med vilje. Behold PHPStan, Psalm eller Semgrep for kodelaget. Se hver pakke PHP-bygget ditt installerer.
Når CRACI ikke er svaret
-
Du deployer via FTP eller til delt webhotell uten CI. Det finnes ikke noe bygg å registrere. Begynn
med
composer auditog en committetcomposer.lock. - Risikoen din ligger hovedsakelig i din egen kode, for eksempel en eldre applikasjon full av rå SQL. SAST, gjennomgang og tester kommer først.
- Du bygger utenfor GitHub Actions, på GitLab CI eller Bitbucket Pipelines. CRACI støtter GitHub Actions i dag; GitLab CI og Jenkins står på veikartet.
PHP-sikkerhet: ofte stilte spørsmål
Hvordan sikrer jeg PHP-kode?
Jobb i tre lag. I koden din: valider input, bruk prepared statements, len deg på rammeverkets escaping av output, og deserialiser aldri data du ikke kan stole på. I avhengighetene dine: commit composer.lock, kjør composer audit i CI, og la Composers blokkering av usikre versjoner være slått på. I bygget ditt: lås actions, begrens tokenet, begrens nettverkstilgangen, og registrer hva jobben installerte.
Hva er de vanligste PHP-sårbarhetene?
SQL-injection, cross-site scripting, cross-site request forgery, kodeinjeksjon via eval(), objektinjeksjon via unserialize(), filinkludering og usikre opplastinger, type juggling med ==, svake sesjonsinnstillinger, SSRF via URL-wrappere, command injection og mass assignment. Sårbare Composer-avhengigheter og upatchede PHP-versjoner, som med PHP2Shell-feilene, er en like vanlig årsak til hendelser.
Hva er den beste sikkerhetsskanneren for PHP?
Ingen enkeltverktøy dekker PHP-sikkerhet. Bruk en statisk analysator som PHPStan, Psalm eller Semgrep for koden din, composer audit for Composer-avhengighetene dine, en containerskanner for imaget, og en registrering fra bygget av hva CI faktisk installerte.
Hvordan virker composer audit, og hva går det glipp av?
Det sjekker installerte pakker, eller composer.lock med --locked, mot advisories fra Packagist.org-API-et, og rapporterer forlatte pakker og pakker flagget som skadevare. Det avslutter med 1 når noe ikke består revisjonen. Det ser ikke selve PHP, PECL-utvidelser, OS-pakker i imaget ditt eller noe CI laster ned utenfor Composer.
Hvordan blokkerer jeg usikre Composer-pakker i CI?
Bruk Composer 2.9 eller nyere, som som standard blokkerer versjoner med advisories ved update og require (policy.advisories.block). Siden en sårbar versjon som allerede står i composer.lock, likevel installeres, bør du også kjøre composer audit etter composer install og la jobben feile på avslutningskoden. Bruk aldri --no-blocking i CI.
Er Laravel sikkert som standard?
For det meste. Blade escaper output, Eloquent binder spørringsparametere, modeller er beskyttet mot mass assignment, og sesjons-ID-en fornyes ved innlogging. Disse standardene slutter å virke når kode bruker {!! !!}, whereRaw() med input, $guarded = [] eller APP_DEBUG=true i produksjon, eller når APP_KEY lekker.
Hvordan oppdaterer jeg avhengigheter i en eldre PHP-app med få tester?
Oppdater pakken lenger opp som drar inn den sårbare, én pakke om gangen med composer update vendor/package --with-dependencies. Røyktest de kritiske flytene, legg til karakteriseringstester rundt det oppgraderingen berører, og rull ut trinnvis.
Hvordan genererer jeg en SBOM for et PHP-prosjekt?
CycloneDX-pluginen for Composer (cyclonedx/cyclonedx-php-composer) genererer en fra Composer-pakkene dine med composer CycloneDX:make-sbom. Den lister bare Composer-pakker. En SBOM fra bygget registrerer også PHP-runtime, OS-pakker og andre nedlastinger i bygget.
Hva er PHP2Shell?
PHP2Shell er navnet på CVE-2026-109618 og CVE-2026-109619, to kritiske sårbarheter i selve PHP som kan åpne for fjernkjøring av kode uten autentisering. De er rettet i PHP 8.5.12, 8.4.27, 8.3.36 og 8.2.35, utgitt 21. oktober 2026. Se PHP2Shell.
Se hver pakke PHP-bygget ditt installerer
Kjør én GitHub Actions-arbeidsflyt på CRACI, og sammenlign den registrerte SBOM-en med composer.lock.
Book en demo