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
- Din kod Injection, XSS, avserialisering, filhantering, sessioner Ramverkets standardinställningar, kodgranskning, PHPStan, Psalm, Semgrep
- 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
- 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
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 iphp.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.inifö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
Produktionsinställningar för Laravel och Symfony
-
Felsökning av.
APP_DEBUG=falsei Laravel,APP_ENV=prodochAPP_DEBUG=0i Symfony. En felsökningssida i produktion visar konfigurations- och miljövärden. -
Nycklar och hemligheter. Håll
APP_KEY(Laravel) ochAPP_SECRET(Symfony) utanför repot och rotera dem om de läcker. Med LaravelsAPP_PREVIOUS_KEYSkan 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 medX-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
$_GEToch$_COOKIEtill mottagare somecho,includeoch SQL-frågor, och rapporterar problem somTaintedSqlochTaintedHtml. Aktuell Psalm kör analysen tillsammans med typkontrollen;--taint-analysisslår på den för en enskild körning, och--report=results.sarifskriver 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.lockför applikationer, så att varje miljö installerar samma versioner. -
Använd
composer installi CI och vid driftsättning, aldrigcomposer 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ärcomposer.lockinte är i takt medcomposer.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-pluginslista 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 installmedphp artisan package:discover, som laddar autoloadern, så ett beroendes autoladdadefileskö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:
-
Uppdatera paketet som drar in det sårbara. De flesta fynd är transitiva; kör
composer why vendor/packageför att se kedjan, och uppdatera först paketet uppströms inom dess versionsvillkor. -
Uppdatera en sak i taget.
composer update vendor/package --with-dependencieshåller ändringen liten nog att granska i diffen förcomposer.lock. - 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.
- 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.
- 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: readhindrar ett komprometterat steg från att pusha kod eller releaser medGITHUB_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 attcomposer installkörs mot lockfilen i varje jobb. En återställdvendor/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 auditoch en incheckadcomposer.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