De upload was een afbeelding. De controle slaagde. Later werd dezelfde inhoud uitgevoerd als PHP.
Dat is het detail dat ik wil onthouden van het Bricksforge-verhaal, dat vandaag weer in het nieuws staat. Bij een uploadlek denk je al snel aan ontbrekende validatie. Hier werd wel degelijk gecontroleerd. Bij de volgende stap werd die uitkomst niet meer meegenomen.
Dat verschil bepaalt waar ik bij een review naar zou kijken. Een MIME-controle in de uploadhandler vinden beantwoordt één vraag. Het bestand volgen tot de applicatie het gebruikt, beantwoordt de vraag waar het om gaat.
Waar de gecontroleerde afbeelding PHP werd
Het rapport van Patchstack van 8 oktober beschrijft CVE-2026-85097. Een aanvaller kon zonder in te loggen een nonce ophalen, een GIF met PHP-code uploaden en vervolgens een formulier versturen met gewijzigde metadata.
Het bestand kwam door de eerste MIME-controle. Bij het versturen verwees het veld file naar die afbeelding, terwijl de aanvaller in het veld url een PHP-bestemming opgaf. Bricksforge vertrouwde die metadata en schreef de inhoud daar weg. Zodra dat PHP-bestand werd opgevraagd, werd de ingebedde code uitgevoerd.
Dezelfde bytes kregen zo twee verschillende betekenissen. Bij de upload herkende de controle een afbeelding. Op de uiteindelijke bestemming kon de server de inhoud als programma uitvoeren.
Dit zijn de twee velden waar de fout om draait. Het is een schematisch voorbeeld, geen volledig verzoek:
file: /uploads/tmp/image.gif
url: https://example.com/uploads/image.phpDe gevaarlijke beslissing viel bij de overdracht: gegevens die de browser terugstuurde, bepaalden waar de server het bestand neerzette.
Wat heeft de controle eigenlijk vastgesteld?
Een afbeelding controleren heeft zin. Je kunt ermee weigeren wat je applicatie niet ondersteunt, vergissingen opvangen en een deel van je uploadbeleid afdwingen. Die controle zou ik houden.
Maar wat weet je daarna precies?
Je hebt iets vastgesteld over de aangeleverde inhoud, op dat moment in het proces. Daarmee heb je nog geen latere bestandsnaam goedgekeurd, toestemming gegeven voor een bestemming of een nieuw formulierverzoek gekoppeld aan de uploadgegevens die de server eerder vastlegde.
Dat zijn afzonderlijke beslissingen. Voor elke beslissing moet duidelijk zijn wie de gegevens mag bepalen.
De denkfout is dat een gecontroleerd bestand in alle volgende stappen veilig blijft. Er kan een andere extensie bij komen. De client kan andere metadata terugsturen. Een volgende handler kan een veld als opdracht gebruiken, terwijl de eerste handler het alleen als informatie behandelde.
Bij een review wil ik zien waar die betekenis verandert. Van wie kwam de waarde? Wie kan hem vervangen? Wat bepaalt hij op dit punt in de code?
Daarom zegt een keurig uploadveld op een pagina weinig over de server erachter. In de changelog van Bricksforge voor 4.0.1, van 8 oktober, staat dat alle sites met actieve Pro Forms geraakt werden, ook als hun formulieren geen uploadvelden hadden. Aan het zichtbare formulier kon je dus onvoldoende aflezen of een site kwetsbaar was.
Lees door naar de volgende handler
Als je alleen de uploadfunctie bekijkt, kun je een geruststellend lijstje afvinken: limiet op bestandsgrootte, typecontrole, tijdelijke map. Het ziet er allemaal redelijk uit.
Daarna open je de handler die het formulier verwerkt. Die krijgt een bestandsverwijzing, een URL en wat metadata. Ook die namen zien er redelijk uit. Het probleem wordt zichtbaar zodra je volgt welke waarden van de ene stap naar de andere gaan en welke de client tussendoor kan vervangen.
Dat is het soort lezen dat ik developers wil laten oefenen. Bij mijn oefeningen voor onbekende code draait het om voorspellen wat de code daadwerkelijk gaat doen. Trek die voorspelling bij een upload door naar het volgende verzoek.
Schrijf het uiteindelijke pad op voordat je iets draait. Leg uit waar elk onderdeel vandaan komt. Zit daar een veld van de client bij, vraag dan waarom dat veld over de opslag mag beslissen.
Een agent kan helpen de aanroepers te vinden en de gegevens te volgen. Het oordeel blijft jouw werk. Laat hem elke waarde volgen tot aan het wegschrijven en controleer dat vervolgens in de code.
Als je na een vlotte uitleg van de uploadvalidator stopt met lezen, mis je de rest van het proces. Dan neem je code over zonder de beslissingen erin te begrijpen.
Ik heb geen bewijs dat AI de kwetsbare Bricksforge-code heeft geschreven. Deze fout zit in de overdracht tussen twee stappen. Daar moet de review op letten.
Laat de server over de opslag beslissen
De uploadrichtlijnen van OWASP adviseren bestandsnamen die de applicatie zelf genereert, een beperkte lijst toegestane typen en opslag buiten de webroot of op een aparte host. OWASP waarschuwt ook dat een controle van de bestandssignatuur op zichzelf onvoldoende bescherming biedt.
Bij een upload in twee stappen zou ik de gegevens op de server in beide stappen leidend houden. Het eerste verzoek legt vast welk bestand is opgeslagen. Het volgende verzoek levert alleen een upload-ID aan. De server zoekt zelf het pad, het toegestane type en de koppeling met de eigenaar of het formulierverzoek op.
Ook voor zo'n ID moet je de rechten controleren. Een willekeurig ogende tekenreeks geeft een formulierverzoek geen recht op de upload van een ander verzoek.
De oorspronkelijke bestandsnaam mag je aan de gebruiker blijven tonen. Hij hoort geen bestandspad te worden omdat de browser hem terugstuurt. Een openbare download-URL leid je eveneens af van het opgeslagen bestand. Je neemt hem niet aan als instructie voor waar de inhoud moet worden weggeschreven.
Ik zou uploads ook buiten locaties houden waar code kan worden uitgevoerd. Als de applicatie bestanden openbaar moet aanbieden, moet de server ze daar als gegevens behandelen, zonder PHP-handler voor die opslaglocatie. Zo heb je een extra beveiliging als een controle in de applicatie faalt.
Dit zijn ontwerpaanbevelingen, geen beschrijving van de patch van Bricksforge. De changelog bevestigt de oplossing, maar beschrijft de implementatie niet.
Test de route van de aanvaller
Een unittest die laat zien dat de MIME-validator een GIF accepteert, kan kloppen terwijl het hele systeem kwetsbaar blijft. De nuttige regressietest gaat verder dan dat punt.
Begin met een toegestane upload. Verstuur daarna het formulier met metadata die afwijkt van het oorspronkelijke bestand. Bekijk vervolgens wat er echt is opgeslagen en aangeboden.
Deze gevallen zou ik testen:
- Andere bestemming. Geef een extensie voor uitvoerbare code op of een pad buiten de bedoelde opslaglocatie. De client mag nooit bepalen waar het bestand uiteindelijk wordt weggeschreven.
- Andere verwijzing. Probeer een upload-ID van een ander formulierverzoek, of gebruik een ID opnieuw nadat het is verbruikt. Controleer de regels voor eigenaarschap en geldigheid die voor jouw applicatie gelden.
- Onverwachte uploadgegevens. Stuur uploadmetadata naar een formulier zonder uploadveld. De backend moet alleen de ingestelde velden verwerken en niet vertrouwen op de formulierindeling in de browser.
- Uitvoering bij het opvragen. Vraag het opgeslagen bestand op via de serverconfiguratie waarmee je het daadwerkelijk aanbiedt. Controleer dat de inhoud van een upload niet als servercode kan draaien.
Die tests volgen het bestand van upload via formulierverwerking tot en met het opvragen. Ze leggen de beveiligingseis vast: metadata van de client mag van een geaccepteerde upload geen uitvoerbaar bestand maken.
Doe dat in een afgeschermde testomgeving, met bestanden die je zelf beheert. Je hoeft geen aanval op andermans site na te doen om de beveiliging van je eigen applicatie te testen.
De patch blokkeert de uploadroute
Patchstack noemt 3.1.8.10 als de versie uit de gemelde 3.x-reeks waarin de kwetsbaarheid is verholpen. De leverancier vermeldt de oplossing voor de Pro Forms-upload in 4.0.1. Controleer de versiereeks die je gebruikt, in plaats van een hoger versienummer als bewijs te nemen dat de oplossing erin zit.
Patchstack meldt ook aanvalspogingen vanaf 7 oktober om 21:47 UTC. Als een kwetsbare site bereikbaar was, adviseert het bedrijf om oude logs en de server te controleren op onverwachte PHP-bestanden of onbevoegde wijzigingen.
Patchen en een eerdere inbraak onderzoeken beantwoorden verschillende vragen. De uploadroute dichtzetten zegt niets over een bestand dat een aanvaller al heeft achtergelaten.
Wat ik hiervan meeneem naar mijn eigen code, is de overdracht. Zoek de handler ná de controle. Volg de waarden tot aan het wegschrijven en het opvragen van het bestand.
De validator deed zijn werk. Het hele proces had dezelfde zorg nodig.