131% flere rapporterede CVE'er pr. dag i 2026 end sidste år.

PHP-sikkerhed

PHP-sikkerhed: undgå PHP-sårbarheder i din kode, dine Composer-afhængigheder og dit build

At sikre PHP handler om tre lag. I den kode, du skriver: valider input, brug parametriserede forespørgsler og frameworkets escaping, og deserialiser aldrig data, du ikke kan stole på. I de Composer-pakker, du er afhængig af: kør composer audit, commit composer.lock og bloker usikre versioner. I den CI, der installerer dem: registrer, hvad der faktisk blev installeret, og begræns, hvor buildet kan forbinde sig til. De fleste guides til PHP-sikkerhed stopper ved det første lag. Denne dækker alle tre.

Opdateret

  1. Din kode Injection, XSS, deserialisering, filhåndtering, sessioner Frameworkets standardindstillinger, code review, PHPStan, Psalm, Semgrep
  2. Dine Composer-afhængigheder Kendte sårbarheder, opgivne og ondsindede pakker, lockfile-drift, kode, der kører ved installation composer audit, Composer-politikker, Dependabot eller Renovate
  3. Dit build Hvad CI faktisk installerede og kørte, hvor det blev hentet fra, og hvad det kunne nå Fastlåste actions, tokens med mindste rettigheder, egress-politik, SBOM fra buildet
PHP-sikkerhedens tre lag og de værktøjer, der arbejder på hvert af dem

På denne side: PHP2Shell, de PHP-sårbarheder, der stadig betyder noget, konfiguration og understøttede versioner, statisk analyse, sikkerhed for Composer-afhængigheder, supply chain-angreb på PHP, CI-buildet, SBOM'er og værktøjer pr. lag.

PHP2Shell: PHP-sårbarheden, du skal patche nu

Den 21. oktober 2026 udgav PHP-projektet PHP 8.5.12, 8.4.27, 8.3.36 og 8.2.35 for at rette to kritiske sårbarheder i selve PHP, som kan give mulighed for fjernkørsel af kode uden godkendelse. Begge har scoren 9,2 under CVSS v4.0, og tilsammen kaldes de PHP2Shell:

  • CVE-2026-109618: håndteringen af manglende hukommelse kan afbryde PHP, mens request-data kun er delvist initialiseret, og oprydningen efter requestet kan så behandle de ugyldige data som gyldige.
  • CVE-2026-109619: tilstand, der tilhører et request, kan overleve, efter at dens hukommelse er frigivet, og det næste request, som den samme worker håndterer, kan bruge den.

Ifølge PHP's advisory er enhver installation, der betjener webrequests, i fare, herunder PHP-FPM, Apache med mod_php, php-cgi, FrankenPHP og LiteSpeed, og mod_php vurderes som den største risiko. PHP 8.1 og ældre får ingen ny release fra PHP-projektet. På grund af alvoren har PHP's sikkerhedsteam dog udgivet minimale kildekode-backports til PHP 7.4, 8.0 og 8.1 til dem, der bygger PHP fra kildekode eller ikke har en LTS-leverandør, og nogle distributioner og LTS-leverandører leverer deres egne backportede rettelser. Det skal du gøre:

  • Opgrader til en rettet release. På 8.1 eller ældre: skift til en understøttet gren, brug din leverandørs backportede pakke, eller anvend den officielle kildekode-backport, hvis du selv bygger PHP.
  • Genbyg hvert containerimage, der indeholder PHP, og deploy det igen. Et gammelt image-tag bliver ved med at køre den gamle PHP, indtil det udskiftes, så tjek, hvad der er deployet, og ikke kun, hvad der ligger i repositoriet: docker run --rm IMAGE php -v.
  • Hvis du ikke kan opgradere i dag, så anvend de midlertidige afhjælpninger fra PHP's sikkerhedsadvisory, for eksempel session.upload_progress.enabled=0. Sæt det i php.ini, FPM-poolen eller serverkonfiguration, der gælder, før requesten parses, og genstart hver worker. Stol ikke på ini_set() eller FPM's .user.ini til det: advisoryen advarer mod begge. PHP gør det klart, at afhjælpningerne mindsker eksponeringen, men ikke gør en installation sikker.

Bemærk, hvad composer audit ikke kan fortælle dig her: PHP2Shell sidder i runtime, ikke i en Composer-pakke, så den dukker aldrig op i composer.lock. Den PHP, du kører, kommer fra dit base image eller dine OS-pakker, som er det tredje lag i denne guide. Versionstabellen, tidslinjen og et script, der tjekker hvert image-tag, finder du i PHP2Shell: hvad du skal patche, og hvordan du finder hver sårbar PHP.

De PHP-sårbarheder, der stadig betyder noget

Sårbarhedsklasserne i PHP-applikationer har knap ændret sig på et årti. Det, der har ændret sig, er, at Laravel, Symfony og moderne PHP lukker de fleste af dem som standard, så rigtige fejl samler sig nu de steder, hvor koden går uden om disse standarder. Injection er A05 i OWASP Top 10:2025, og Software Supply Chain Failures er ny som A03, hvilket er emnet for anden halvdel af denne guide.

Sårbarhed Sådan viser den sig i PHP Løsningen Laravel og Symfony
SQL-injection Request-data, der sættes sammen med SQL, også via nødudgange til rå forespørgsler PDO prepared statements med bundne parametre Eloquent og Doctrine binder parametre; whereRaw(), DB::raw() og sammensat DQL gør ikke
Cross-site scripting (XSS) Brugerinput, der skrives ud i HTML uden escaping Escape ved output med htmlspecialchars() eller templatemotoren Blade {{ }} og Twig escaper som standard; {!! !!} og |raw gør ikke
Cross-site request forgery (CSRF) En formular eller et AJAX-endpoint, der ændrer tilstand, accepterer enhver request med sessionscookien Et token pr. session, der kontrolleres ved hver POST, PUT, PATCH og DELETE, plus SameSite-cookies Laravel kontrollerer tokenet på web-ruter (@csrf i formularer); Symfony Forms indeholder og kontrollerer et som standard; ruter, der er undtaget fra kontrollen, har ingen beskyttelse
Kodeinjektion via eval() Request-data, eller en skabelon eller formel som en bruger kan redigere, der når eval() Brug ikke eval(); map input til et fast sæt operationer, eller brug en parser til de udtryk, du har brug for Intet framework beskytter dine egne eval()-kald
Objektinjektion unserialize() på cookies, request-data eller cacheposter, som en angriber kan skrive Brug JSON til data udefra; hvis du skal deserialisere, så afvis objekter Intet framework beskytter dine egne unserialize()-kald
Filinkludering og uploads En sti bygget af input, der sendes til include; uploads gemt under webroden med klientens filnavn Tilladelsesliste for inkluderede filer; gem uploads uden for webroden under genererede navne Laravels store() genererer filnavne; valider type og størrelse på serveren
Type juggling ==, der sammenligner tokens eller hashes, hvor "0e123" == "0e456" er sandt ===, hash_equals() til hemmeligheder, password_verify() til adgangskoder Frameworkets hjælpefunktioner sammenligner sikkert; dine egne sammenligninger er dit ansvar
Svage sessioner og cookies Session-id'er, som JavaScript kan læse, som sendes over HTTP eller beholdes på tværs af login Secure-, HttpOnly- og SameSite-cookies, strict mode, et nyt session-id efter login Begge genererer et nyt session-id ved login
Server-side request forgery (SSRF) file_get_contents() eller cURL på en URL, som brugeren har angivet, også via wrappers som file:// og phar:// Tilladelsesliste for skemaer og værter, bloker interne adresser, slå allow_url_fopen fra, hvor det ikke bruges Ingen beskyttelse som standard
Command injection Input, der sendes til exec(), shell_exec(), system() eller proc_open() Undgå shellen; send argumenter som et array, eller escape hvert af dem med escapeshellarg() Symfony Process og Laravels Process-facade accepterer et array af argumenter
Mass assignment User::create($request->all()) lader et request sætte is_admin Angiv de felter, der må udfyldes, eller send kun valideret input videre Eloquent-modeller er beskyttet som standard; $guarded = [] slår det fra

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

Objektinjektion: 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 hvor en filoperation på en phar://-sti deserialiserede arkivets metadata, blev indsnævret i PHP 8.0: phar-metadata bliver ikke længere deserialiseret, når et phar-arkiv åbnes, kun når kode kalder getMetadata(). Det er endnu en grund til at lægge PHP 7 bag sig.

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;

Hærdning af konfiguration og runtime

En sikker applikation kan stadig lække gennem sin runtime. Disse indstillinger i php.ini følger OWASP PHP Configuration Cheat Sheet:

Indstilling Værdi i produktion Hvorfor
display_errors Off (med log_errors = On) Stack traces viser stier, forespørgsler og nogle gange hemmeligheder
expose_php Off Holder op med at vise den præcise PHP-version i svarheaders
allow_url_include Off Forhindrer include af eksterne URL'er; forældet siden PHP 7.4
allow_url_fopen Off, hvis applikationen ikke henter URL'er med filfunktioner Indsnævrer SSRF via file_get_contents()
disable_functions For eksempel exec, shell_exec, system, passthru, proc_open, popen Fjerner shellen for en angriber, der får kode til at køre; angiv kun det, din applikation aldrig kalder
open_basedir Applikationens mappe Begrænser de stier, PHP's filfunktioner kan åbne; et autoværn, ikke en isolationsgrænse
Session-cookies session.cookie_secure = 1, session.cookie_httponly = 1, session.cookie_samesite = Lax eller Strict, session.use_strict_mode = 1 Holder session-id'er væk fra ukrypteret HTTP og fra JavaScript og afviser id'er, som serveren ikke har udstedt
file_uploads Off, hvis applikationen ikke modtager uploads Fjerner en hel inputvej

Bliv på en understøttet PHP-version

PHP giver hver gren to års aktiv support og derefter to år med kun sikkerhedsrettelser, ifølge php.net. PHP 8.1 nåede end of life den 31. december 2025, og PHP 8.2 følger den 31. december 2026. En gren, der har nået end of life, får ingen nye releases fra PHP-projektet, og det er den situation, PHP2Shell satte brugerne af 8.1 i: deres eneste officielle rettelse var en kildekode-backport.

  • PHP 8.1 End of life, ophørte 31. dec. 2025
  • PHP 8.2 Kun sikkerhedsrettelser, indtil 31. dec. 2026
  • PHP 8.3 Kun sikkerhedsrettelser, indtil 31. dec. 2027
  • PHP 8.4 Aktiv support, indtil 31. dec. 2026
  • PHP 8.5 Aktiv support, indtil 31. dec. 2027
Understøttede PHP-versioner (datoer fra php.net)

Produktionsindstillinger for Laravel og Symfony

  • Debug slået fra. APP_DEBUG=false i Laravel, APP_ENV=prod og APP_DEBUG=0 i Symfony. En debugside i produktion viser konfigurations- og miljøværdier.
  • Nøgler og hemmeligheder. Hold APP_KEY (Laravel) og APP_SECRET (Symfony) ude af repositoriet, og roter dem, hvis de lækker. Med Laravels APP_PREVIOUS_KEYS kan du rotere nøglen uden at logge alle brugere ud.
  • Betroede proxyer. Bag en load balancer angiver du de proxyer, du stoler på (Laravels middleware til trusted proxies, Symfonys framework.trusted_proxies), så en klient ikke kan forfalske sin IP-adresse eller HTTPS med X-Forwarded-*-headers.

Statisk analyse til PHP (SAST)

Statisk analyse læser din kode uden at køre den. Tre open source-værktøjer dækker de fleste PHP-teams:

  • PHPStan finder typefejl og bugs på stadig strengere niveauer. Det er ikke en sikkerhedsscanner, men mange sikkerhedsfejl begynder som typeforvirring eller en ukontrolleret null.
  • Psalm tilføjer taint-analyse: det følger data fra kilder som $_GET og $_COOKIE til modtagere som echo, include og SQL-forespørgsler, og rapporterer problemer som TaintedSql og TaintedHtml. Den nuværende Psalm kører analysen sammen med typetjekket; --taint-analysis slår den til for en enkelt kørsel, og --report=results.sarif skriver SARIF til GitHub code scanning.
  • Semgrep understøtter PHP som et generelt tilgængeligt sprog, med community-regler for almindelige PHP-fejl og dataflow på tværs af funktioner i det kommercielle niveau.

SAST giver meget støj på ældre PHP af strukturelle grunde: dynamiske includes, variable variabler, magiske metoder som __get() og __call(), og frameworkkonventioner, der sender data gennem containere og facades, som analysatoren ikke kan følge. Værktøjet gætter enten, og rapporterer falske positiver, eller giver op og overser rigtige dataflow.

På en gammel kodebase fungerer det bedst at indføre det som en skralde. Gem dagens fund som en baseline (vendor/bin/phpstan analyse --generate-baseline skriver phpstan-baseline.neon; Psalm har --set-baseline), lad kun buildet fejle på nye fund, og hæv niveauet, efterhånden som du afvikler baselinen. Teams med PHP-kodebaser, der har levet længe, fortæller os, at AI-autofix fungerer godt til opdateringer af afhængigheder og er ujævnt på kodefund, så gennemgå genererede rettelser til SAST-fund lige så omhyggeligt som enhver anden ændring. SAST og SCA besvarer forskellige spørgsmål; se SAST vs SCA.

Sikkerhed for Composer-afhængigheder

En Laravel- eller Symfony-applikation installerer snesevis af Composer-pakker, de fleste af dem transitive. Composer har fået rigtige sikkerhedsfunktioner i de seneste udgivelser; dette afsnit tager udgangspunkt i Composer 2.11, den aktuelle udgivelse.

composer audit

composer audit tjekker de pakker, du har installeret, mod sikkerhedsadvisories fra Packagist.org-API'et, som samler advisories fra kilder som GitHub Advisory Database og FriendsOfPHP/security-advisories. Det rapporterer også opgivne pakker og, siden Composer 2.10, pakkeversioner, der er markeret som malware. Se Composers dokumentation for alle muligheder.

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 exitkoden 0, når der ingen problemer er, og 1, når noget ikke bestod auditten, så det fungerer som en CI-gate uden parsing. Opgivne pakker får som standard auditten til at fejle.

Accepterede fund hører hjemme i composer.json med en begrundelse, ikke i et CI-flag, som ingen husker. Composer 2.10 erstattede det meste af den gamle audit-konfiguration med en policy-blok:

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

De gamle nøgler (audit.ignore, audit.abandoned, audit.block-insecure) virker stadig som fallback, så længe den tilsvarende policy-sektion mangler, men de er forældede. Migrer alle advisory-indstillinger på én gang: Så snart policy.advisories findes, ignoreres alle advisory-relaterede audit.*-nøgler.

Bloker usikre versioner

Siden Composer 2.9 (november 2025) afviser composer update og composer require pakkeversioner, der har en sikkerhedsadvisory. Det var indstillingen audit.block-insecure og er nu policy.advisories.block, slået til som standard. Composer 2.10 tilføjede blokering af malware fra Packagists malware-feed, og den gælder også for composer install. Composer 2.11 tilføjede en valgfri cooldown-politik, der holder nyudgivne versioner tilbage i en periode, du selv vælger, for eksempel "7 days", fordi mange ondsindede versioner bliver fundet og fjernet inden for timer eller dage.

To detaljer betyder noget i CI. For det første gælder blokeringen ud fra advisories, når versioner opløses: En version, der allerede står i composer.lock, installeres stadig med composer install, efter at en advisory er udgivet, så du har stadig brug for composer audit som gate. For det andet slår --no-blocking alle politikker fra for én kommando, så det er værd at søge efter i dine pipelines.

Før Composer 2.9 blokerede man som regel usikre versioner med roave/security-advisories, en pakke, der erklærer en conflict med hver kendt sårbar version, bygget ud fra GitHub Advisory Database og FriendsOfPHP. Den virker stadig og hjælper stadig på ældre Composer-versioner; på Composer 2.9 eller nyere dækker den indbyggede blokering de samme advisories uden den ekstra pakke.

Disciplin med lockfilen

  • Commit composer.lock for applikationer, så hvert miljø installerer de samme versioner.
  • Brug composer install i CI og ved deployments, aldrig composer update, der opløser nye versioner i farten.
  • Installer produktionsbuilds med --no-dev, så testværktøjer aldrig bliver leveret.
  • Kør composer validate --strict: Det fejler, når composer.lock ikke er i takt med composer.json.

En lockfile registrerer, hvad Composer opløste. Se hvad en lockfile ikke registrerer.

Kode ved installation: plugins, scripts og autoloadede filer

Composer kører kode fra dine afhængigheder flere steder, end de fleste teams regner med, hvilket gør det til PHP's modstykke til installationsscripts i npm:

  • Plugins kører inde i selve Composer. Siden Composer 2.2 skal indstillingen allow-plugins angive hvert plugin, der må køre, og som standard tillader den ingen. Behandl en ny post der som en ny afhængighed med shelladgang.
  • Scripts kører kun fra din rod-composer.json, ikke fra afhængigheder. Men de starter ofte din applikation.
  • Autoloadede filer kører, så snart autoloaderen indlæses. I en standard Laravel-applikation slutter composer install med php artisan package:discover, der indlæser autoloaderen, så en afhængigheds autoloadede files kører i dit CI-job under installationen.

Hvis et job ikke har brug for plugins eller scripts, forhindrer --no-plugins --no-scripts kode fra afhængigheder i at køre under installationen; kør de trin, du har brug for, eksplicit bagefter.

Pakkekilder og dependency confusion

Composer kan installere fra Packagist.org, private Composer-repositorier som Private Packagist, VCS-repositorier (Git-URL'er) og lokale stier. Hvor private og offentlige kilder blandes, opstår dependency confusion: Hvis navnet på din private pakke også findes på Packagist.org med en højere version, hvilken får du så? I Composer 2 er hvert repositorium kanonisk som standard: Når Composer har fundet en pakke i et repositorium, leder det ikke efter den længere nede på listen, og Packagist.org kommer altid sidst. Hvis du sætter "canonical": false på et repositorium, fjerner du den beskyttelse, så gør det kun med vilje. VCS-repositorier henter det, Git-værten leverer for et tag, så fastlås dem til et commit i lockfilen, og gennemgå ændringer i dem.

Opgivne og uvedligeholdte pakker

Ældre PHP-landskaber rummer pakker, hvis maintainere er gået videre. Packagist markerer en pakke som opgivet, når dens maintainer siger det, ofte med en afløser, og composer audit fejler som standard på den. Mange uvedligeholdte pakker bliver dog aldrig formelt opgivet. Se på datoen for seneste udgivelse for alt, der håndterer input, godkendelse eller kryptografi, og planlæg afløsere, før en sårbarhed tvinger dig til det.

Opdater sikkert, når der er få tests

Det sværeste tilfælde er en stor, ældre applikation med en sårbar afhængighed og få tests. Det her virker:

  1. Opdater den pakke, der trækker den sårbare ind. De fleste fund er transitive; kør composer why vendor/package for at se kæden, og opdater først pakken længere oppe inden for dens versionskrav.
  2. Opdater én ting ad gangen. composer update vendor/package --with-dependencies holder ændringen lille nok til at gennemgå i diffen af composer.lock.
  3. Røgtest de kritiske flows først. Login, checkout, de vigtigste API-endpoints: En håndfuld end-to-end-tjek fanger de fleste fejl fra opgraderinger.
  4. Tilføj karakteriseringstests omkring det, opgraderingen berører. Registrer, hvad koden gør i dag, så du opdager, når opgraderingen ændrer det.
  5. Rul ud i etaper, og hold øje med fejlrater, før ændringen når alle kunder.

Hvad composer audit overser

Lockfilen er en plan, ikke en registrering af buildet. composer audit læser vendor/ eller composer.lock, så det ser ikke:

  • Selve PHP og dets udvidelser, som kommer fra dit base image eller dine OS-pakker (PHP2Shell er et eksempel).
  • PECL-udvidelser og systembiblioteker, der er installeret i Docker-imaget.
  • Værktøjer, som din CI henter med curl, actions, den kører, og binære filer, som et plugin eller script downloader.
  • Ondsindet kode, der endnu ikke har en advisory eller en malware-markering.

De samme begrænsninger gælder for npm audit og pip-audit i deres økosystemer.

Supply chain-angreb på PHP

Fire hændelser viser, hvor supply chain-angreb på PHP faktisk rammer, og hvad der ville have fanget hver af dem.

Marts 2021: ondsindede commits i php-src

Den 28. marts 2021 blev to ondsindede commits pushet til php-src-repositoriet i navnet på to kerneudviklere. PHP-teamet meddelte, at det ville holde op med at køre sin egen Git-server og gøre GitHub-repositorierne kanoniske. Dets opfølgning viste, at commits var blevet pushet over HTTPS med adgangskodegodkendelse, muligvis med loginoplysninger fra en lækket brugerdatabase, og ikke gennem en kompromitteret server. Hvad der ville have fanget det: intet i din egen pipeline. Angrebet var rettet mod selve sproget, en påmindelse om, at PHP-runtime også er en afhængighed.

April 2021: command injection i Composer (CVE-2021-29472)

En fejl i den måde, Composer håndterede URL'er til Mercurial-repositorier på, gjorde command injection mulig. Packagist rapporterede, at Packagist.org og Private Packagist teoretisk var eksponeret via manipulerede pakke-URL'er, mente ikke, at fejlen var blevet udnyttet, og rettede den i Composer 2.0.13 og 1.10.22. Hvad der ville have fanget det: at holde selve Composer opdateret på udviklermaskiner og i CI-images. Pakkehåndteringen er en del af din forsyningskæde.

Maj 2023: overtagelse af Packagist-konti

Den 1. maj 2023 loggede en angriber ind på fire inaktive Packagist.org-konti med adgangskoder, der var lækket andre steder. Kontiene styrede tilsammen 14 pakker, og angriberen pegede pakkerne over på forkede repositorier. Forkene indeholdt en ændret beskrivelse og ingen ondsindet kode. Packagist gendannede dem næste morgen. Hvad der ville have fanget det: en committet composer.lock, da composer install bruger de kilder, der er registreret i lockfilen, og gennemgang af diffs i lockfilen for uventede URL'er, som Packagist selv anbefaler. For maintainere: unikke adgangskoder og tofaktorgodkendelse.

2026: kaprede tags på laravel-lang og intercom-php

I 2026 udgav angribere med stjålet adgang ondsindede udgivelser af eksisterende pakker. Packagist nævner intercom/intercom-php (30. april) og laravel-lang-pakkerne (22. maj) i sin opdatering om supply chain-sikkerhed. I laravel-lang-sagen pegede versionstags på commits i en ondsindet fork, og payloaden, en src/helpers.php-fil, der blev indlæst af Composers autoloader, hentede en credential stealer til cloudnøgler, SSH-nøgler og .env-filer. Aikido talte 233 kompromitterede versioner. Hvad der ville have fanget det: Når versionerne først er markeret, blokerer malware-politikken i Composer 2.10 dem selv ved composer install; inden da holder en cooldown-periode helt nye versioner ude, en egress-politik forhindrer payloaden i at nå sin server, og en registrering af hvert build viser, hvilke builds der installerede en dårlig version.

Siden da har Packagist meddelt, at det afviser stabile versioner, der er blevet tagget igen, udgiver en gennemsigtighedslog over ændringer af ejerskab og tags, og har markeret malware på Packagist.org siden marts 2026.

Sikring af PHP-buildet i CI

Buildet er der, hvor din kode, dine afhængigheder og dine hemmeligheder mødes, så det er der, en ondsindet pakke gør mest skade. Et GitHub Actions-workflow til et produktionsbuild:

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 mindste rettigheder. permissions: contents: read forhindrer et kompromitteret trin i at pushe kode eller releases med GITHUB_TOKEN. Se rettigheder for GitHub-token.
  • Fastlåste actions. En fuld commit-SHA kan ikke flyttes, som et tag kan; kommentaren holder versionen læsbar for Dependabot eller Renovate.
  • Cache downloads, ikke vendor/. Når du cacher Composers downloadcache, kører composer install mod lockfilen i hvert job. En gendannet vendor/ stoles der på, som den er, og caches kan forgiftes på tværs af workflows; se GitHub Actions-cache.
  • Registrer, hvad der blev installeret. Lockfilen siger, hvad der burde installeres. En SBOM registreret fra buildet siger, hvad der blev installeret, inklusive PHP-runtime, OS-pakker og downloads uden for Composer. Se SBOM'er fra buildet.
  • Begræns, hvor buildet kan forbinde sig til. Et Composer-build har brug for Packagist, GitHub og dit private repositorium. En egress-politik, der kun tillader dem, forhindrer en credential stealer i at sende det, den har fundet.

Generer en SBOM til et PHP-projekt

Den almindelige open source-mulighed er CycloneDX-pluginnet til Composer. Som plugin kræver det en post i allow-plugins:

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

Det læser composer.json sammen med de installerede pakker eller composer.lock, så det angiver dine Composer-afhængigheder præcist og intet andet. PHP-runtime, dens udvidelser, base imagets apt- eller apk-pakker og de værktøjer, jobbet downloadede, er ikke med. En SBOM fra buildet registrerer også dem, fordi den tages ud fra det, jobbet hentede, og ikke ud fra Composers filer. Om formaterne og hvad en SBOM bør indeholde, se hvad er en SBOM.

PHP-sikkerhedsværktøjer pr. lag

Lag Værktøjer Hvad de dækker Hvad de overser
Kode PHPStan, Psalm, Semgrep Bugs og typefejl, dataflow med ubetroede data, kendte usikre mønstre Sårbare afhængigheder, runtime, buildet
Afhængigheder composer audit, Composer-politikker, roave/security-advisories, Dependabot eller Renovate Udgivne advisories, opgivne og markerede pakker, pull requests med opdateringer Din egen kode og alt uden for composer.lock
Image Containerscannere OS- og sprogpakker i et bygget image Det, buildet hentede uden at beholde, og images, som ingen har scannet
SBOM CycloneDX-pluginnet til Composer Composer-pakker som en CycloneDX-SBOM Alt uden for Composer
Build CRACI Hvad hvert GitHub Actions-job faktisk installerede, fra Composer og alle andre kilder, overvåget over tid Din PHP-kode (ingen SAST); andre CI-systemer end GitHub Actions

Mere om images: containerscanning. Færre og bedre fund på tværs af alle lag: sådan reducerer du støjen fra sårbarheder.

PHP-sikkerhed og CRACI

CRACI er en GitHub Actions-runner, der registrerer, hvad hvert job faktisk installerer. Skift runs-on til craci, så registreres hver Composer-pakke, jobbet downloader, med sin package URL (pkg:composer/vendor/package), sammen med pakker fra OS'ets pakkehåndtering, containerimages, Git-kilder og andre downloads i samme job, inklusive pakker gendannet fra CI-caches. Hver komponent i SBOM'en var i buildet.

CRACI matcher disse komponenter mod kendte sårbarheder og bliver ved med at tjekke SBOM'erne for det, du har leveret, igen, efterhånden som nye advisories udgives, så en CVE, der offentliggøres næste år, stadig fører dig til de builds, der indeholder den. Policy gates kan stoppe et build ved fund, også et build, der indeholder en bestemt CVE, og egress-politikken begrænser, hvor jobbet kan forbinde sig til.

CRACI scanner ikke din PHP-kode: Der er ingen SAST, og det er bevidst. Behold PHPStan, Psalm eller Semgrep til kodelaget. Se hver pakke, dit PHP-build installerer.

Når CRACI ikke er svaret

  • Du deployer via FTP eller til delt hosting uden CI. Der er intet build at registrere. Start med composer audit og en committet composer.lock.
  • Din risiko ligger primært i din egen kode, for eksempel en ældre applikation fuld af rå SQL. SAST, review og tests kommer først.
  • Du bygger uden for GitHub Actions, på GitLab CI eller Bitbucket Pipelines. CRACI understøtter GitHub Actions i dag; GitLab CI og Jenkins er på roadmappen.

PHP-sikkerhed: ofte stillede spørgsmål

Hvordan sikrer jeg PHP-kode?

Arbejd i tre lag. I din kode: valider input, brug prepared statements, læn dig op ad frameworkets escaping af output, og deserialiser aldrig data, du ikke kan stole på. I dine afhængigheder: commit composer.lock, kør composer audit i CI, og lad Composers blokering af usikre versioner være slået til. I dit build: fastlås actions, begræns tokenet, begræns netværksadgangen, og registrer, hvad jobbet installerede.

Hvad er de mest almindelige PHP-sårbarheder?

SQL-injection, cross-site scripting, cross-site request forgery, kodeinjektion via eval(), objektinjektion via unserialize(), filinkludering og usikre uploads, type juggling med ==, svage sessionsindstillinger, SSRF via URL-wrappers, command injection og mass assignment. Sårbare Composer-afhængigheder og upatchede PHP-versioner, som med PHP2Shell-fejlene, er en lige så almindelig årsag til hændelser.

Hvad er den bedste sikkerhedsscanner til PHP?

Intet enkelt værktøj dækker PHP-sikkerhed. Brug en statisk analysator som PHPStan, Psalm eller Semgrep til din kode, composer audit til dine Composer-afhængigheder, en containerscanner til imaget og en registrering fra buildet af, hvad CI faktisk installerede.

Hvordan virker composer audit, og hvad overser det?

Det tjekker installerede pakker, eller composer.lock med --locked, mod advisories fra Packagist.org-API'et og rapporterer opgivne pakker og pakker markeret som malware. Det afslutter med 1, når noget ikke består auditten. Det ser ikke selve PHP, PECL-udvidelser, OS-pakker i dit image eller noget, som CI downloader uden for Composer.

Hvordan blokerer jeg usikre Composer-pakker i CI?

Brug Composer 2.9 eller nyere, som som standard blokerer versioner med advisories ved update og require (policy.advisories.block). Da en sårbar version, der allerede står i composer.lock, stadig installeres, så kør også composer audit efter composer install, og lad jobbet fejle på exitkoden. Brug aldrig --no-blocking i CI.

Er Laravel sikkert som standard?

For det meste. Blade escaper output, Eloquent binder forespørgselsparametre, modeller er beskyttet mod mass assignment, og session-id'et genereres på ny ved login. Disse standarder holder op med at virke, når kode bruger {!! !!}, whereRaw() med input, $guarded = [] eller APP_DEBUG=true i produktion, eller når APP_KEY lækker.

Hvordan opdaterer jeg afhængigheder i en ældre PHP-app med få tests?

Opdater pakken længere oppe, der trækker den sårbare ind, én pakke ad gangen med composer update vendor/package --with-dependencies. Røgtest de kritiske flows, tilføj karakteriseringstests omkring det, opgraderingen berører, og rul ud i etaper.

Hvordan genererer jeg en SBOM til et PHP-projekt?

CycloneDX-pluginnet til Composer (cyclonedx/cyclonedx-php-composer) genererer en ud fra dine Composer-pakker med composer CycloneDX:make-sbom. Den angiver kun Composer-pakker. En SBOM fra buildet registrerer også PHP-runtime, OS-pakker og andre downloads i buildet.

Hvad er PHP2Shell?

PHP2Shell er navnet på CVE-2026-109618 og CVE-2026-109619, to kritiske sårbarheder i selve PHP, som kan give mulighed for fjernkørsel af kode uden godkendelse. De er rettet i PHP 8.5.12, 8.4.27, 8.3.36 og 8.2.35, udgivet den 21. oktober 2026. Se PHP2Shell.

Se hver pakke, dit PHP-build installerer

Kør ét GitHub Actions-workflow på CRACI, og sammenlign den registrerede SBOM med din composer.lock.

Book en demo