Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Mime][RateLimiter][Routing][Security] Harden __unserialize against __toString trampolines#64343

Merged
nicolas-grekas merged 1 commit into
symfony:6.4symfony/symfony:6.4from
nicolas-grekas:harden-unserialize-trampolinesnicolas-grekas/symfony:harden-unserialize-trampolinesCopy head branch name to clipboard
May 23, 2026
Merged

[Mime][RateLimiter][Routing][Security] Harden __unserialize against __toString trampolines#64343
nicolas-grekas merged 1 commit into
symfony:6.4symfony/symfony:6.4from
nicolas-grekas:harden-unserialize-trampolinesnicolas-grekas/symfony:harden-unserialize-trampolinesCopy head branch name to clipboard

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Member

Q A
Branch? 6.4
Bug fix? yes
New feature? no
Deprecations? no
Issues -
License MIT

Reject objects in typed-string properties during __unserialize

When a class declares an __unserialize(array $data) that assigns $data[...] to a typed string (or union-with-string) property, PHP's type coercion invokes __toString() on any object placed in that slot before the value is stored. A post-assignment is_string() guard runs too late: the gadget has already fired.

The following 6.4 classes have that shape:

Class Typed string property assigned from raw $data
Routing\Route string $path, string $host, string $condition
Routing\CompiledRoute string $staticPrefix, string $regex, ?string $hostRegex
RateLimiter\Policy\Window string $id (BC path)
RateLimiter\Policy\SlidingWindow string $id (BC path)
RateLimiter\Policy\TokenBucket string $id (BC path)
Mime\Email ?string $textCharset, ?string $htmlCharset

Typed int/float/bool properties are not exploitable in the same way: PHP rejects object-to-scalar coercion for those with a TypeError without calling __toString().

Each __unserialize() now validates the shape of the raw $data before any assignment, throwing BadMethodCallException on mismatch.

Drop the legacy Serializable interface from Route, CompiledRoute, AbstractToken

33969158d0 ("Remove Serializable implementations", 2021) kept \Serializable on these three classes so existing payloads using the legacy C:...:"ClassName":... wire format could still be read; serialize() was made to throw so no new such payloads were written.

The retained unserialize($serialized) calls plain unserialize() on the inner payload with no allowed_classes restriction. A forged C:N:"Route":... (or AbstractToken subclass) payload bypasses the trampoline guard above by instantiating arbitrary classes via the nested call, firing any __wakeup / __destruct / typed-property assignment side-effect their code may have.

The supported upgrade path is 3.4 -> 4.4 -> 5.4 -> 6.4 with cache and session refreshes along the way, so legacy C: payloads are no longer expected in any storage. Following the same rationale as #64195, this PR drops implements \Serializable and the two legacy methods from each class. The corresponding v4 fixture test in Security/Core/Tests/Role/LegacyRoleTest.php is removed.

@nicolas-grekas nicolas-grekas requested a review from chalasr as a code owner May 23, 2026 11:38
@carsonbot carsonbot added this to the 6.4 milestone May 23, 2026
@carsonbot carsonbot changed the title [Routing][RateLimiter][Mime][Security] Harden __unserialize against __toString trampolines [Mime][RateLimiter][Routing][Security] Harden __unserialize against __toString trampolines May 23, 2026
@nicolas-grekas nicolas-grekas force-pushed the harden-unserialize-trampolines branch from 1ffdba4 to dbf3c45 Compare May 23, 2026 14:40
@nicolas-grekas nicolas-grekas merged commit a52daa9 into symfony:6.4 May 23, 2026
9 of 13 checks passed
@nicolas-grekas nicolas-grekas deleted the harden-unserialize-trampolines branch May 23, 2026 14:41
nicolas-grekas added a commit that referenced this pull request May 23, 2026
…uring __unserialize (nicolas-grekas)

This PR was merged into the 7.4 branch.

Discussion
----------

[Mime][String] Reject objects in typed-string properties during __unserialize

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

Same class of issue as #64343 on 6.4: a `__unserialize(array $data)` that assigns `$data[...]` to a typed `string` (or union-with-`string`) property lets PHP's type coercion invoke `__toString()` on any attacker-supplied object before the value is stored. A post-assignment `is_string()` guard runs too late.

This PR covers the surface that exists on 7.4 and is not addressed in #64343:

| Class                  | Typed-string slots assigned from raw `$data`                                                       |
| ---------------------- | -------------------------------------------------------------------------------------------------- |
| `String\UnicodeString` | `string $string`                                                                                   |
| `Mime\Part\TextPart`   | `?string $charset`, `string $subtype`, `?string $disposition`, `?string $name`, `string $encoding` |
| `Mime\Part\SMimePart`  | `iterable\|string $body`, `string $type`, `string $subtype`                                        |

Union types that include `string` (e.g. `iterable|string`) also trigger `__toString()` coercion; for `SMimePart::$body` the guard is `instanceof \Stringable && !is_iterable($body)` since PHP prefers the iterable branch when an object satisfies both.

Each `__unserialize()` now validates the shape of the raw `$data` before any assignment, throwing `BadMethodCallException` on mismatch. `UnicodeString`'s existing post-check is moved to run before the assignment; `TextPart` and `SMimePart` gain explicit pre-checks for every typed-string slot (in every key-prefix variant: bare, `\0Class\0...`, and `\0*\0...`).

The 6.4 hardening for `Route`, `CompiledRoute`, `Window`, `SlidingWindow`, `TokenBucket`, and `Email` lands on 7.4 via the upmerge.

Commits
-------

d64ccd7 [String][Mime] Reject objects in typed-string properties during __unserialize
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.