131% flere rapporterte CVE-er per dag i 2026 enn i fjor.

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

  1. Koden din Injection, XSS, deserialisering, filhåndtering, sesjoner Rammeverkets standardinnstillinger, kodegjennomgang, PHPStan, Psalm, Semgrep
  2. 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
  3. 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
De tre lagene i PHP-sikkerhet, og verktøyene som virker på hvert av dem

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 i php.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.ini for 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
Støttede PHP-versjoner (datoer fra php.net)

Produksjonsinnstillinger for Laravel og Symfony

  • Feilsøking av. APP_DEBUG=false i Laravel, APP_ENV=prod og APP_DEBUG=0 i Symfony. En feilsøkingsside i produksjon viser konfigurasjons- og miljøverdier.
  • Nøkler og hemmeligheter. Hold APP_KEY (Laravel) og APP_SECRET (Symfony) utenfor repositoriet, og roter dem hvis de lekker. Med Laravels APP_PREVIOUS_KEYS kan 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 med X-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 $_GET og $_COOKIE til mottakere som echo, include og SQL-spørringer, og rapporterer problemer som TaintedSql og TaintedHtml. Gjeldende Psalm kjører analysen sammen med typesjekken; --taint-analysis slår den på for én enkelt kjøring, og --report=results.sarif skriver 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.lock for applikasjoner, slik at hvert miljø installerer de samme versjonene.
  • Bruk composer install i CI og ved utrulling, aldri composer 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år composer.lock ikke er i takt med composer.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-plugins liste 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 install med php artisan package:discover, som laster autoloaderen, så en avhengighets autoloadede files kjø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:

  1. Oppdater pakken som drar inn den sårbare. De fleste funn er transitive; kjør composer why vendor/package for å se kjeden, og oppdater først pakken lenger opp innenfor versjonskravet.
  2. Oppdater én ting om gangen. composer update vendor/package --with-dependencies holder endringen liten nok til å gå gjennom i diffen for composer.lock.
  3. 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.
  4. 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.
  5. 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: read hindrer et kompromittert trinn i å pushe kode eller releaser med GITHUB_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ører composer install mot lockfilen i hver jobb. En gjenopprettet vendor/ 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 audit og en committet composer.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