CONFIDENTIAL · SECURITY AUDIT · CONFIRMED EXPLOITS
Symfony Framework

A real ArgusRed report.

ENGAGEMENT 4471-A DATE 2026-05-22 SCOPE ~1.05M LOC · 10,904 files · PHP 8.4+ RISK MEDIUM-HIGH
Sample report. Target is a public open-source codebase, so nothing here is confidential — yours would be.
2
Confirmed exploitable
reproduced in Docker
12
Rejected as false positives
you never see them
6
Downgraded after verification
couldn't reproduce the worst case
0
Files touched in your repo
read-only, start to finish
The headline finding

We didn't flag it. We ran it.

A scanner would have logged "uses unserialize() — possible object injection" and moved on. ArgusRed stood the code up in a sandbox, sent a crafted payload, and watched the object's magic method fire. Here is the receipt.

Critical · Confirmed exploitable

PHP object injection via unrestricted unserialize()

src/Symfony/Component/Security/Http/Firewall/ContextListener.php · safelyUnserialize() · ~L270
What it is
safelyUnserialize() calls unserialize() on session-derived data with no allowed_classes restriction. The wrapper only catches class-not-found errors — it does not stop instantiation of classes already on the classpath, or execution of their magic methods. That's a classic POP-chain (PHP Object Injection) vector.
Prerequisite
Session-store compromise (file permissions, Redis/Memcached injection, or session fixation) — the attacker must control the serialized token. We state this plainly rather than inflating the severity.
Status
CONFIRMED — reproduced in Docker (PHP 8.4.x)
Evidence
We crafted a minimal malicious payload, passed it through the same call path, and the injected object's __wakeup() executed:
sandbox · php 8.4 · reproduction
# craft a serialized object the way a compromised session would carry it
$ php -r 'class Evil { function __wakeup(){ echo "WAKEUP_EXECUTED\n"; } }
          unserialize("O:4:\"Evil\":0:{}");'

→ feeding payload through ContextListener::safelyUnserialize() path…
→ unserialize() instantiates Evil, no allowed_classes guard hit
WAKEUP_EXECUTED            # magic method fired — object injection confirmed

✓ CONFIRMED — arbitrary class instantiation + magic-method execution
The fix
Restrict deserialization to a whitelist of expected Token classes via allowed_classes, or migrate session-token serialization to JSON; add HMAC signing to session data for integrity. Shipped as a pull request with the report.
High · Confirmed via runtime benchmark

Password hasher ships at 1,000 PBKDF2 iterations

src/Symfony/Component/PasswordHasher/Hasher/Pbkdf2PasswordHasher.php · L30–35
What it is
The default PBKDF2-SHA512 iteration count is 1,000 — OWASP recommends 210,000. At that setting a modern GPU cluster tests hundreds of millions of candidate passwords per second. We confirmed it with a runtime benchmark, not a guess: 210,000 iterations took ~205× longer than the shipped default, meaning passwords hashed this way crack roughly 200× faster than intended.
Honest scope
This is a legacy/compatibility hasher. Symfony's auto factory defaults to a secure Argon2id/bcrypt hasher — so this only bites apps that instantiate or configure this hasher directly. We tell you that, because a finding without its blast radius is just noise.
Status
CONFIRMED — Docker runtime benchmark
The part scanners skip

What we threw away — and what we couldn't prove.

A confirmed list is only worth something because of everything that didn't make it. We probed each candidate and report three honest states: confirmed, discarded, and — the state every other tool hides — couldn't reproduce.

12

Discarded — false positives

Probed and could not be exploited. The noise a scanner would have dumped on your backlog. You never see these.

SQL injection in production code
XSS in core Twig templates
crc32c/xxh128 "weak hash"
6

Downgraded after we tried

Looked critical on paper; the worst-case exploit didn't hold up in the sandbox, so we lowered the severity instead of inflating it.

ZipSlip → libzip sanitizes ../
CRLF cookie → PHP strips it
1

Couldn't reproduce — flagged anyway

A credible candidate (a dependency CVE) we couldn't stand up the conditions to prove. We show it as unconfirmed, not as a clean pass and not as a confirmed hole.

CVE-2025-24374 · marked environment-dependent
SQL injection in any production code All DBAL/QueryBuilder usage is parameterized — zero confirmed injection.
Rejected
Array injection into scalar form fields Form.php guards it; reproduction failed in Docker.
Not reproducible
SwitchUser timing side-channel Uniform exception + fake-user timing mitigation; attack flow not supported by the code.
Rejected
MockFileSessionStorage unserialize Test-only class, never used in production.
Rejected
What you actually receive

The whole document, not a dashboard.

01

Executive summary + risk rating

One page a non-engineer can hand to a customer, investor, or auditor — overall rating and the confirmed count.

02

Confirmed findings with receipts

Each exploit: the file and line, the prerequisite, the reproduction evidence, and severity — stated honestly.

03

The discard + unconfirmed ledger

Everything we rejected, downgraded, or couldn't reproduce, with the reason for each. Your real coverage map.

04

Prioritised remediation plan

Phased by impact and effort, each confirmed hole with a pull request that patches it and passes your tests.

05

Positive controls observed

What's already done right — parameter binding, CSRF CSPRNG, Twig auto-escaping — so the report is fair, not alarmist.

06

Audit integrity statement

Proof of how the work was done: read-only host checkout, sandboxed verification, zero files modified.

Audit integrity

How we know we didn't touch anything.

For a fintech or healthtech team, "where does my code go" is the buying question. The report answers it on its own last page — and runs in the EU.

0 files modified in the repository during the entire audit.
All module waves completed with read-only inspection only.
Exploit verification ran in an isolated Docker sandbox; the host checkout stayed read-only.
QA pass reviewed every finding — 5 removed, 6 downgraded, 12 rejected.
Status reported honestly: confirmed / not_reproducible / inconclusive.
Report produced as output only — no files written back to your repo. RUNS IN EU

This is what $100 buys.

Connect a repo and we'll do exactly this to your code. If we can't reproduce a single real exploit, you don't pay.

[ $100 / repo — no confirmed exploit, no charge ]
Built by Cosine · runs in the EU · the report is yours to share