131% fler rapporterade CVE:er per dag 2026 än förra året.

PHP-säkerhet

PHP-säkerhet: så förebygger du sårbarheter i PHP i din kod, dina Composer-beroenden och ditt bygge

Att säkra PHP innebär tre lager. I koden du skriver: validera indata, använd parametriserade frågor och ramverkets escaping, och avserialisera aldrig opålitliga data. I Composer-paketen du är beroende av: kör composer audit, checka in composer.lock och blockera osäkra versioner. I CI-miljön som installerar dem: registrera vad som faktiskt installerades och begränsa vart bygget kan ansluta. De flesta guider om PHP-säkerhet stannar vid det första lagret. Den här täcker alla tre.

Uppdaterad

  1. Din kod Injection, XSS, avserialisering, filhantering, sessioner Ramverkets standardinställningar, kodgranskning, PHPStan, Psalm, Semgrep
  2. Dina Composer-beroenden Kända sårbarheter, övergivna och skadliga paket, lockfile-drift, kod som körs vid installation composer audit, Composer-policyer, Dependabot eller Renovate
  3. Ditt bygge Vad CI faktiskt installerade och körde, varifrån det hämtades, vad bygget kunde nå Pinnade actions, tokens med minsta behörighet, egress-policy, SBOM från bygget
PHP-säkerhetens tre lager, och verktygen som arbetar i vart och ett

På den här sidan: PHP2Shell, PHP-sårbarheterna som fortfarande spelar roll, konfiguration och versioner som stöds, statisk analys, säkerhet för Composer-beroenden, attacker mot PHP:s leveranskedja, CI-bygget, SBOM:er och verktyg per lager.

PHP2Shell: PHP-sårbarheten du ska patcha nu

Den 21 oktober 2026 släppte PHP-projektet PHP 8.5.12, 8.4.27, 8.3.36 och 8.2.35 för att åtgärda två kritiska sårbarheter i själva PHP som kan möjliggöra fjärrkörning av kod utan autentisering. Båda har betyget 9,2 enligt CVSS v4.0, och tillsammans kallas de PHP2Shell:

  • CVE-2026-109618: hanteringen av minnesbrist kan avbryta PHP medan begärandedata bara är delvis initierade, och städningen efter begäran kan då behandla de ogiltiga data som giltiga.
  • CVE-2026-109619: tillstånd som tillhör en begäran kan finnas kvar efter att dess minne har frigjorts, och nästa begäran som samma worker hanterar kan använda det.

Enligt PHP:s säkerhetsmeddelande är varje installation som hanterar webbegäranden utsatt, bland annat PHP-FPM, Apache med mod_php, php-cgi, FrankenPHP och LiteSpeed, och mod_php bedöms ha högst risk. PHP 8.1 och äldre får ingen ny release från PHP-projektet. På grund av allvaret publicerade PHP:s säkerhetsteam ändå minimala källkodsbackportar för PHP 7.4, 8.0 och 8.1, för den som bygger PHP från källkod eller saknar LTS-leverantör, och vissa distributioner och LTS-leverantörer levererar egna backporterade rättningar. Det här ska du göra:

  • Uppgradera till en rättad release. På 8.1 eller äldre: byt till en gren som stöds, ta leverantörens backporterade paket eller tillämpa den officiella källkodsbackporten om du bygger PHP själv.
  • Bygg om varje containeravbild som innehåller PHP och driftsätt den igen. En gammal avbildstagg fortsätter att köra den gamla PHP-versionen tills den ersätts, så kontrollera vad som är driftsatt, inte bara vad som finns i repot: docker run --rm IMAGE php -v.
  • Om du inte kan uppgradera i dag, tillämpa de tillfälliga åtgärderna från PHP:s säkerhetsmeddelande, till exempel session.upload_progress.enabled=0. Sätt det i php.ini, FPM-poolen eller serverkonfiguration som gäller innan begäran tolkas, och starta om varje worker. Förlita dig inte på ini_set() eller FPM:s .user.ini för det: meddelandet varnar för båda. PHP är tydligt med att åtgärderna minskar exponeringen men inte gör en installation säker.

Lägg märke till vad composer audit inte kan berätta här: PHP2Shell finns i körmiljön, inte i ett Composer-paket, så den syns aldrig i composer.lock. Den PHP du kör kommer från din basavbild eller dina OS-paket, vilket är det tredje lagret i den här guiden. Versionstabell, tidslinje och ett skript som kontrollerar varje avbildstagg hittar du i PHP2Shell: vad du ska patcha och hur du hittar varje sårbar PHP.

PHP-sårbarheterna som fortfarande spelar roll

Sårbarhetsklasserna i PHP-applikationer har knappt förändrats på ett decennium. Det som har förändrats är att Laravel, Symfony och modern PHP stänger de flesta som standard, så verkliga buggar samlas nu där koden går runt de standardinställningarna. Injection ligger på A05 i OWASP Top 10:2025, och Software Supply Chain Failures är ny på A03, vilket är ämnet för den andra halvan av den här guiden.

Sårbarhet Hur den visar sig i PHP Åtgärden Laravel och Symfony
SQL-injection Begärandedata som sätts ihop med SQL, även via nödutgångar för råa frågor PDO prepared statements med bundna parametrar Eloquent och Doctrine binder parametrar; whereRaw(), DB::raw() och hopsatt DQL gör det inte
Cross-site scripting (XSS) Användarindata som skrivs ut i HTML utan escaping Escapa vid utskrift med htmlspecialchars() eller mallmotorn Blade {{ }} och Twig escapar som standard; {!! !!} och |raw gör det inte
Cross-site request forgery (CSRF) Ett formulär eller en AJAX-endpoint som ändrar tillstånd accepterar varje begäran som har sessionscookien En token per session som kontrolleras vid varje POST, PUT, PATCH och DELETE, plus SameSite-cookies Laravel kontrollerar token på web-rutter (@csrf i formulär); Symfony Forms innehåller och kontrollerar en som standard; rutter som undantas från kontrollen saknar skydd
Kodinjektion via eval() Begärandedata, eller en mall eller formel som en användare kan redigera, som når eval() Använd inte eval(); mappa indata till en fast uppsättning operationer, eller använd en parser för de uttryck du behöver Inget ramverk skyddar dina egna eval()-anrop
Objektinjektion unserialize() på cookies, begärandedata eller cacheposter som en angripare kan skriva Använd JSON för data utifrån; om du måste avserialisera, vägra objekt Inget ramverk skyddar dina egna unserialize()-anrop
Filinkludering och uppladdningar En sökväg byggd av indata som skickas till include; uppladdningar som lagras under webbroten med klientens filnamn Tillåtelselista för inkluderade filer; lagra uppladdningar utanför webbroten under genererade namn Laravels store() genererar filnamn; validera typ och storlek på servern
Type juggling == som jämför tokens eller hashar, där "0e123" == "0e456" är sant ===, hash_equals() för hemligheter, password_verify() för lösenord Ramverkets hjälpfunktioner jämför säkert; dina egna jämförelser är ditt ansvar
Svaga sessioner och cookies Sessions-id:n som JavaScript kan läsa, som skickas över HTTP eller behålls efter inloggning Secure-, HttpOnly- och SameSite-cookies, strict mode, ett nytt sessions-id efter inloggning Båda genererar ett nytt sessions-id vid inloggning
Server-side request forgery (SSRF) file_get_contents() eller cURL mot en URL som användaren angett, även via wrappers som file:// och phar:// Tillåtelselista för scheman och värdar, blockera interna adresser, stäng av allow_url_fopen där det inte används Inget skydd som standard
Command injection Indata som skickas till exec(), shell_exec(), system() eller proc_open() Undvik skalet; skicka argument som en array, eller escapa vart och ett med escapeshellarg() Symfony Process och Laravels Process-fasad tar emot en argumentarray
Mass assignment User::create($request->all()) låter en begäran sätta is_admin Lista fält som får fyllas i, eller skicka bara validerade indata Eloquent-modeller är skyddade som standard; $guarded = [] stänger av det

SQL-injection: sårbart och åtgärdat

// 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årbart och åtgärdat

<?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årbart och åtgärdat

// 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-avserialisering, varianten där en filoperation på en phar://-sökväg avserialiserade arkivets metadata, begränsades i PHP 8.0: phar-metadata avserialiseras inte längre när ett phar-arkiv öppnas, bara när kod anropar getMetadata(). Ännu ett skäl att lämna PHP 7 bakom sig.

Filinkludering: sårbart och åtgärdat

// 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 av konfiguration och körmiljö

En säker applikation kan ändå läcka via sin körmiljö. De här inställningarna i php.ini följer OWASP PHP Configuration Cheat Sheet:

Inställning Värde i produktion Varför
display_errors Off (med log_errors = On) Stackspår visar sökvägar, frågor och ibland hemligheter
expose_php Off Slutar visa den exakta PHP-versionen i svarshuvuden
allow_url_include Off Förhindrar include av fjärr-URL:er; föråldrat sedan PHP 7.4
allow_url_fopen Off om applikationen inte hämtar URL:er med filfunktioner Begränsar SSRF via file_get_contents()
disable_functions Till exempel exec, shell_exec, system, passthru, proc_open, popen Tar bort skalet för en angripare som får kod att köra; lista bara det som din applikation aldrig anropar
open_basedir Applikationens katalog Begränsar vilka sökvägar PHP:s filfunktioner kan öppna; ett skyddsräcke, inte en isoleringsgräns
Sessionscookies session.cookie_secure = 1, session.cookie_httponly = 1, session.cookie_samesite = Lax eller Strict, session.use_strict_mode = 1 Håller sessions-id:n borta från okrypterad HTTP och från JavaScript, och avvisar id:n som servern inte har utfärdat
file_uploads Off om applikationen inte tar emot uppladdningar Tar bort en hel väg för indata

Håll dig på en PHP-version som stöds

PHP ger varje gren två års aktivt stöd och därefter två år med enbart säkerhetsrättningar, enligt php.net. PHP 8.1 nådde slutet av sin livscykel den 31 december 2025, och PHP 8.2 följer den 31 december 2026. En gren som nått end of life får inga nya releaser från PHP-projektet, och det är den situationen PHP2Shell försatte 8.1-användarna i: deras enda officiella rättning var en källkodsbackport.

  • PHP 8.1 End of life, upphörde 31 dec. 2025
  • PHP 8.2 Endast säkerhetsrättningar, till 31 dec. 2026
  • PHP 8.3 Endast säkerhetsrättningar, till 31 dec. 2027
  • PHP 8.4 Aktivt stöd, till 31 dec. 2026
  • PHP 8.5 Aktivt stöd, till 31 dec. 2027
PHP-versioner som stöds (datum från php.net)

Produktionsinställningar för Laravel och Symfony

  • Felsökning av. APP_DEBUG=false i Laravel, APP_ENV=prod och APP_DEBUG=0 i Symfony. En felsökningssida i produktion visar konfigurations- och miljövärden.
  • Nycklar och hemligheter. Håll APP_KEY (Laravel) och APP_SECRET (Symfony) utanför repot och rotera dem om de läcker. Med Laravels APP_PREVIOUS_KEYS kan du rotera nyckeln utan att logga ut alla användare.
  • Betrodda proxyservrar. Bakom en lastbalanserare listar du de proxyservrar du litar på (Laravels middleware för trusted proxies, Symfonys framework.trusted_proxies), så att en klient inte kan förfalska sin IP-adress eller HTTPS med X-Forwarded-*-huvuden.

Statisk analys för PHP (SAST)

Statisk analys läser din kod utan att köra den. Tre verktyg med öppen källkod täcker de flesta PHP-team:

  • PHPStan hittar typfel och buggar på allt striktare nivåer. Det är ingen säkerhetsskanner, men många säkerhetsbuggar börjar som typförväxling eller ett okontrollerat null.
  • Psalm lägger till taint-analys: det följer data från källor som $_GET och $_COOKIE till mottagare som echo, include och SQL-frågor, och rapporterar problem som TaintedSql och TaintedHtml. Aktuell Psalm kör analysen tillsammans med typkontrollen; --taint-analysis slår på den för en enskild körning, och --report=results.sarif skriver SARIF för GitHub code scanning.
  • Semgrep stöder PHP som allmänt tillgängligt språk, med communityregler för vanliga PHP-misstag och dataflöde över funktionsgränser i den kommersiella nivån.

SAST ger mycket brus på äldre PHP av strukturella skäl: dynamiska includes, variabla variabler, magiska metoder som __get() och __call(), och ramverkskonventioner som leder data genom containrar och fasader som analysatorn inte kan följa. Verktyget gissar antingen, och rapporterar falska positiva, eller ger upp, och missar verkliga flöden.

På en gammal kodbas fungerar det bäst att införa det som en spärrhake. Spara dagens fynd som en baslinje (vendor/bin/phpstan analyse --generate-baseline skriver phpstan-baseline.neon; Psalm har --set-baseline), låt bygget fallera bara på nya fynd och höj nivån i takt med att du betar av baslinjen. Team med långlivade PHP-kodbaser berättar för oss att AI-autofix fungerar bra för beroendeuppdateringar och är ojämn på kodfynd, så granska genererade rättningar för SAST-fynd lika noga som alla andra ändringar. SAST och SCA besvarar olika frågor; se SAST vs SCA.

Säkerhet för Composer-beroenden

En Laravel- eller Symfony-applikation installerar dussintals Composer-paket, de flesta transitiva. Composer har fått riktiga säkerhetsfunktioner i de senaste versionerna; det här avsnittet utgår från Composer 2.11, den aktuella versionen.

composer audit

composer audit kontrollerar paketen du har installerat mot säkerhetsmeddelanden från Packagist.org-API:et, som samlar in säkerhetsmeddelanden från källor som GitHub Advisory Database och FriendsOfPHP/security-advisories. Det rapporterar också övergivna paket och, sedan Composer 2.10, paketversioner som flaggats som skadlig kod. Alla alternativ finns i Composers dokumentation.

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

Sedan Composer 2.10 är slutkoden 0 när det inte finns några problem och 1 när något inte klarade granskningen, så det fungerar som en CI-spärr utan tolkning av utdata. Övergivna paket får granskningen att fallera som standard.

Accepterade fynd hör hemma i composer.json, med en motivering, inte i en CI-flagga som ingen minns. Composer 2.10 ersatte det mesta av den gamla audit-konfigurationen med ett policy-block:

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

De äldre nycklarna (audit.ignore, audit.abandoned, audit.block-insecure) fungerar fortfarande som reserv så länge motsvarande policy-avsnitt saknas, men de är föråldrade. Migrera alla inställningar för säkerhetsmeddelanden på en gång: när policy.advisories finns ignoreras varje audit.*-nyckel som rör säkerhetsmeddelanden.

Blockera osäkra versioner

Sedan Composer 2.9 (november 2025) vägrar composer update och composer require att använda paketversioner som har ett säkerhetsmeddelande. Det var inställningen audit.block-insecure och är nu policy.advisories.block, påslagen som standard. Composer 2.10 lade till blockering av skadlig kod från Packagists flöde för skadlig kod, och den gäller även composer install. Composer 2.11 lade till en valfri cooldown-policy som håller tillbaka nysläppta versioner under en period du väljer, till exempel "7 days", eftersom många skadliga versioner upptäcks och tas bort inom timmar eller dagar.

Två detaljer spelar roll i CI. För det första gäller blockeringen när versioner löses upp: en version som redan finns i composer.lock installeras fortfarande med composer install efter att ett säkerhetsmeddelande publicerats, så du behöver fortfarande composer audit som spärr. För det andra stänger --no-blocking av alla policyer för ett kommando, så det är värt att söka efter i dina pipelines.

Före Composer 2.9 var det vanliga sättet att blockera osäkra versioner roave/security-advisories, ett paket som deklarerar en conflict med varje känd sårbar version, byggt från GitHub Advisory Database och FriendsOfPHP. Det fungerar fortfarande och hjälper på äldre Composer-versioner; på Composer 2.9 eller senare täcker den inbyggda blockeringen samma säkerhetsmeddelanden utan det extra paketet.

Disciplin kring lockfilen

  • Checka in composer.lock för applikationer, så att varje miljö installerar samma versioner.
  • Använd composer install i CI och vid driftsättning, aldrig composer update, som löser upp nya versioner i farten.
  • Installera produktionsbyggen med --no-dev, så att testverktyg aldrig levereras.
  • Kör composer validate --strict: det fallerar när composer.lock inte är i takt med composer.json.

En lockfil registrerar vad Composer löste upp. Se vad en lockfil inte registrerar.

Kod vid installation: plugins, skript och autoladdade filer

Composer kör kod från dina beroenden på fler ställen än de flesta team räknar med, vilket gör det till PHP:s motsvarighet till installationsskript i npm:

  • Plugins körs inuti själva Composer. Sedan Composer 2.2 måste inställningen allow-plugins lista varje plugin som får köras, och som standard tillåter den inga. Behandla en ny post där som ett nytt beroende med skalåtkomst.
  • Skript körs bara från din rot-composer.json, inte från beroenden. Men de startar ofta din applikation.
  • Autoladdade filer körs så fort autoloadern laddas. I en vanlig Laravel-applikation avslutas composer install med php artisan package:discover, som laddar autoloadern, så ett beroendes autoladdade files körs i ditt CI-jobb under installationen.

Om ett jobb inte behöver plugins eller skript hindrar --no-plugins --no-scripts kod från beroenden att köras under installationen; kör de steg du behöver uttryckligen efteråt.

Paketkällor och dependency confusion

Composer kan installera från Packagist.org, privata Composer-repon som Private Packagist, VCS-repon (Git-URL:er) och lokala sökvägar. Där privata och publika källor blandas uppstår dependency confusion: om namnet på ditt privata paket också finns på Packagist.org med en högre version, vilket får du? I Composer 2 är varje repo kanoniskt som standard: när Composer har hittat ett paket i ett repo letar det inte efter det längre ner i listan, och Packagist.org kommer alltid sist. Att sätta "canonical": false på ett repo tar bort det skyddet, så gör det bara med avsikt. VCS-repon hämtar det som Git-värden levererar för en tagg, så pinna dem på commit i lockfilen och granska ändringar i dem.

Övergivna och ounderhållna paket

Äldre PHP-miljöer bär på paket vars underhållare har gått vidare. Packagist markerar ett paket som övergivet när dess underhållare säger det, ofta med en ersättare, och composer audit fallerar på det som standard. Många ounderhållna paket blir dock aldrig formellt övergivna. Titta på datumet för senaste versionen för allt som hanterar indata, autentisering eller kryptografi, och planera ersättare innan en sårbarhet tvingar fram frågan.

Uppdatera säkert när testerna är få

Det svåraste fallet är en stor äldre applikation med ett sårbart beroende och få tester. Det här fungerar:

  1. Uppdatera paketet som drar in det sårbara. De flesta fynd är transitiva; kör composer why vendor/package för att se kedjan, och uppdatera först paketet uppströms inom dess versionsvillkor.
  2. Uppdatera en sak i taget. composer update vendor/package --with-dependencies håller ändringen liten nog att granska i diffen för composer.lock.
  3. Röktesta de kritiska flödena först. Inloggning, kassa, de viktigaste API-slutpunkterna: en handfull end-to-end-kontroller fångar de flesta fel som uppgraderingar orsakar.
  4. Lägg till karakteriseringstester kring det uppgraderingen rör. Registrera vad koden gör i dag, så att du märker när uppgraderingen ändrar det.
  5. Rulla ut stegvis och följ felfrekvensen innan ändringen når alla kunder.

Vad composer audit missar

Lockfilen är en plan, inte en registrering av bygget. composer audit läser vendor/ eller composer.lock, så det ser inte:

  • Själva PHP och dess tillägg, som kommer från din basavbild eller dina OS-paket (PHP2Shell är ett exempel).
  • PECL-tillägg och systembibliotek som installerats i Docker-avbilden.
  • Verktyg som din CI hämtar med curl, actions som den kör, och binärfiler som ett plugin eller skript laddar ner.
  • Skadlig kod som ännu saknar säkerhetsmeddelande eller flagga för skadlig kod.

Samma begränsningar gäller för npm audit och pip-audit i deras ekosystem.

Attacker mot PHP:s leveranskedja

Fyra incidenter visar var attacker mot leveranskedjan drabbar PHP i praktiken, och vad som hade fångat var och en.

Mars 2021: skadliga commits i php-src

Den 28 mars 2021 pushades två skadliga commits till php-src-repot i namnet på två kärnutvecklare. PHP-teamet meddelade att det skulle sluta driva sin egen Git-server och göra GitHub-repona kanoniska. Dess uppföljning visade att commitsen hade pushats över HTTPS med lösenordsautentisering, möjligen med inloggningsuppgifter från en läckt användardatabas, och inte via en komprometterad server. Vad som hade fångat det: inget i din egen pipeline. Attacken riktade sig mot själva språket, en påminnelse om att även PHP-körmiljön är ett beroende.

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

Ett fel i hur Composer hanterade URL:er till Mercurial-repon möjliggjorde command injection. Packagist rapporterade att Packagist.org och Private Packagist teoretiskt var exponerade via manipulerade paket-URL:er, trodde inte att felet hade utnyttjats och åtgärdade det i Composer 2.0.13 och 1.10.22. Vad som hade fångat det: att hålla själva Composer uppdaterat på utvecklarnas datorer och i CI-avbilder. Pakethanteraren är en del av din leveranskedja.

Maj 2023: kapade Packagist-konton

Den 1 maj 2023 loggade en angripare in på fyra inaktiva Packagist.org-konton med lösenord som läckt på andra ställen. Kontona kontrollerade tillsammans 14 paket, och angriparen pekade om paketen till forkade repon. Forkarna innehöll en ändrad beskrivning och ingen skadlig kod. Packagist återställde dem nästa morgon. Vad som hade fångat det: en incheckad composer.lock, eftersom composer install använder de källor som registrerats i lockfilen, och granskning av diffar i lockfilen efter oväntade URL:er, vilket Packagist själv rekommenderar. För underhållare: unika lösenord och tvåfaktorsautentisering.

2026: kapade taggar i laravel-lang och intercom-php

Under 2026 publicerade angripare med stulen åtkomst skadliga versioner av befintliga paket. Packagist nämner intercom/intercom-php (30 april) och laravel-lang-paketen (22 maj) i sin uppdatering om säkerheten i leveranskedjan. I laravel-lang-fallet pekade versionstaggar på commits i en skadlig fork, och nyttolasten, en src/helpers.php-fil som laddades av Composers autoloader, hämtade ett program som stjäl inloggningsuppgifter för molnnycklar, SSH-nycklar och .env-filer. Aikido räknade 233 komprometterade versioner. Vad som hade fångat det: när versionerna väl är flaggade blockerar Composer 2.10:s policy för skadlig kod dem även vid composer install; innan dess håller en cooldown-period helt nya versioner borta, en egress-policy hindrar nyttolasten från att nå sin server, och en registrering av varje bygge visar vilka byggen som installerade en dålig version.

Sedan dess har Packagist meddelat att det avvisar omtaggade stabila versioner, publicerar en transparenslogg över ändringar av ägarskap och taggar, och flaggar skadlig kod på Packagist.org sedan mars 2026.

Säkra PHP-bygget i CI

Bygget är där din kod, dina beroenden och dina hemligheter möts, så det är där ett skadligt paket gör mest skada. Ett GitHub Actions-arbetsflöde för ett produktionsbygge:

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 minsta behörighet. permissions: contents: read hindrar ett komprometterat steg från att pusha kod eller releaser med GITHUB_TOKEN. Se behörigheter för GitHub-token.
  • Pinnade actions. En fullständig commit-SHA kan inte flyttas som en tagg kan; kommentaren håller versionen läsbar för Dependabot eller Renovate.
  • Cacha nedladdningar, inte vendor/. Att cacha Composers nedladdningscache gör att composer install körs mot lockfilen i varje jobb. En återställd vendor/ litar man på som den är, och cachar kan förgiftas mellan arbetsflöden; se GitHub Actions-cache.
  • Registrera vad som installerades. Lockfilen säger vad som borde installeras. En SBOM som registreras från bygget säger vad som installerades, inklusive PHP-körmiljön, OS-paket och nedladdningar utanför Composer. Se SBOM:er från bygget.
  • Begränsa vart bygget kan ansluta. Ett Composer-bygge behöver Packagist, GitHub och ditt privata repo. En egress-policy som bara tillåter dem hindrar ett program som stjäl inloggningsuppgifter från att skicka iväg det det hittat.

Generera en SBOM för ett PHP-projekt

Det vanliga alternativet med öppen källkod är CycloneDX-pluginet för Composer. Eftersom det är ett plugin behö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 tillsammans med de installerade paketen eller composer.lock, så det listar dina Composer-beroenden korrekt och ingenting annat. PHP-körmiljön, dess tillägg, basavbildens apt- eller apk-paket och de verktyg som jobbet laddade ner finns inte med. En SBOM från bygget registrerar även dem, eftersom den tas fram ur det som jobbet hämtade och inte ur Composers filer. Om format och vad en SBOM bör innehålla, se vad är en SBOM.

PHP-säkerhetsverktyg per lager

Lager Verktyg Vad de täcker Vad de missar
Kod PHPStan, Psalm, Semgrep Buggar och typfel, flöden av otillförlitliga data, kända osäkra mönster Sårbara beroenden, körmiljön, bygget
Beroenden composer audit, Composer-policyer, roave/security-advisories, Dependabot eller Renovate Publicerade säkerhetsmeddelanden, övergivna och flaggade paket, pull requests med uppdateringar Din egen kod, och allt utanför composer.lock
Avbild Containerskannrar OS- och språkpaket i en byggd avbild Vad bygget hämtade utan att behålla, och avbilder som ingen har skannat
SBOM CycloneDX-pluginet för Composer Composer-paket som en CycloneDX-SBOM Allt utanför Composer
Bygge CRACI Vad varje GitHub Actions-jobb faktiskt installerade, från Composer och alla andra källor, bevakat över tid Din PHP-kod (ingen SAST); andra CI-system än GitHub Actions

Mer om avbilder: containerskanning. Färre och bättre fynd i alla lager: så minskar du bruset från sårbarheter.

PHP-säkerhet och CRACI

CRACI är en GitHub Actions-runner som registrerar vad varje jobb faktiskt installerar. Ändra runs-on till craci, så registreras varje Composer-paket som jobbet laddar ner med sin paket-URL (pkg:composer/vendor/package), tillsammans med paket från OS:ets pakethanterare, containeravbilder, Git-källor och andra nedladdningar i samma jobb, inklusive paket som återställts från CI-cachar. Varje komponent i SBOM:en fanns i bygget.

CRACI matchar de komponenterna mot kända sårbarheter och fortsätter att kontrollera SBOM:erna för det du har levererat i takt med att nya säkerhetsmeddelanden publiceras, så en CVE som offentliggörs nästa år leder dig ändå till de byggen som innehåller den. Policyspärrar kan stoppa ett bygge vid fynd, även ett bygge som innehåller en viss CVE, och egress-policyn begränsar vart jobbet kan ansluta.

CRACI skannar inte din PHP-kod: det finns ingen SAST, avsiktligt. Behåll PHPStan, Psalm eller Semgrep för kodlagret. Se varje paket som ditt PHP-bygge installerar.

När CRACI inte är svaret

  • Du driftsätter via FTP eller till delat webbhotell utan CI. Det finns inget bygge att registrera. Börja med composer audit och en incheckad composer.lock.
  • Din risk ligger främst i din egen kod, till exempel en äldre applikation full av rå SQL. SAST, granskning och tester kommer först.
  • Du bygger utanför GitHub Actions, i GitLab CI eller Bitbucket Pipelines. CRACI stöder GitHub Actions i dag; GitLab CI och Jenkins finns på färdplanen.

PHP-säkerhet: vanliga frågor

Hur säkrar jag PHP-kod?

Arbeta i tre lager. I din kod: validera indata, använd prepared statements, förlita dig på ramverkets escaping av utdata och avserialisera aldrig opålitliga data. I dina beroenden: checka in composer.lock, kör composer audit i CI och låt Composers blockering av osäkra versioner vara påslagen. I ditt bygge: pinna actions, begränsa token, begränsa nätverksåtkomsten och registrera vad jobbet installerade.

Vilka är de vanligaste sårbarheterna i PHP?

SQL-injection, cross-site scripting, cross-site request forgery, kodinjektion via eval(), objektinjektion via unserialize(), filinkludering och osäkra uppladdningar, type juggling med ==, svaga sessionsinställningar, SSRF via URL-wrappers, command injection och mass assignment. Sårbara Composer-beroenden och opatchade PHP-versioner, som med PHP2Shell-buggarna, är en lika vanlig orsak till incidenter.

Vilken är den bästa säkerhetsskannern för PHP?

Inget enskilt verktyg täcker PHP-säkerheten. Använd en statisk analysator som PHPStan, Psalm eller Semgrep för din kod, composer audit för dina Composer-beroenden, en containerskanner för avbilden, och en registrering från bygget av vad CI faktiskt installerade.

Hur fungerar composer audit, och vad missar det?

Det kontrollerar installerade paket, eller composer.lock med --locked, mot säkerhetsmeddelanden från Packagist.org-API:et, och rapporterar övergivna paket och paket som flaggats som skadlig kod. Det avslutas med 1 när något inte klarar granskningen. Det ser inte själva PHP, PECL-tillägg, OS-paket i din avbild eller något som CI laddar ner utanför Composer.

Hur blockerar jag osäkra Composer-paket i CI?

Använd Composer 2.9 eller senare, som som standard blockerar versioner med säkerhetsmeddelanden vid update och require (policy.advisories.block). Eftersom en sårbar version som redan står i composer.lock ändå installeras, kör också composer audit efter composer install och låt jobbet fallera på slutkoden. Skicka aldrig med --no-blocking i CI.

Är Laravel säkert som standard?

Till största delen. Blade escapar utdata, Eloquent binder frågeparametrar, modeller är skyddade mot mass assignment och sessions-id:t förnyas vid inloggning. De standardinställningarna slutar fungera när kod använder {!! !!}, whereRaw() med indata, $guarded = [] eller APP_DEBUG=true i produktion, eller när APP_KEY läcker.

Hur uppdaterar jag beroenden i en äldre PHP-app med få tester?

Uppdatera paketet uppströms som drar in det sårbara, ett paket i taget med composer update vendor/package --with-dependencies. Röktesta de kritiska flödena, lägg till karakteriseringstester kring det uppgraderingen rör och rulla ut stegvis.

Hur genererar jag en SBOM för ett PHP-projekt?

CycloneDX-pluginet för Composer (cyclonedx/cyclonedx-php-composer) genererar en från dina Composer-paket med composer CycloneDX:make-sbom. Den listar bara Composer-paket. En SBOM från bygget registrerar även PHP-körmiljön, OS-paket och andra nedladdningar i bygget.

Vad är PHP2Shell?

PHP2Shell är namnet på CVE-2026-109618 och CVE-2026-109619, två kritiska sårbarheter i själva PHP som kan möjliggöra fjärrkörning av kod utan autentisering. De är åtgärdade i PHP 8.5.12, 8.4.27, 8.3.36 och 8.2.35, som släpptes den 21 oktober 2026. Se PHP2Shell.

Se varje paket som ditt PHP-bygge installerar

Kör ett GitHub Actions-arbetsflöde på CRACI och jämför dess registrerade SBOM med din composer.lock.

Boka en demo