Skip to content

[Uid] Fix the weak-secret guard in Uuid47Transformer for secrets longer than 16 bytes - #65105

Merged
nicolas-grekas merged 1 commit into
symfony:8.1from
semx:fix/uuid47-weak-secret-length
Aug 2, 2026
Merged

[Uid] Fix the weak-secret guard in Uuid47Transformer for secrets longer than 16 bytes#65105
nicolas-grekas merged 1 commit into
symfony:8.1from
semx:fix/uuid47-weak-secret-length

Conversation

@semx

@semx semx commented Aug 2, 2026

Copy link
Copy Markdown
Contributor
Q A
Branch? 8.1
Bug fix? yes
New feature? no
Deprecations? no
Issues -
License MIT

Uuid47Transformer rejects a secret made of identical bytes, but the check compares against str_repeat($secret[0], 16), so it can only ever match a secret of exactly 16 bytes. One byte longer and the same secret passes:

new Uuid47Transformer(str_repeat("\x00", 16)); // InvalidArgumentException, as intended
new Uuid47Transformer(str_repeat("\x00", 17)); // accepted
new Uuid47Transformer(str_repeat("\x00", 32)); // accepted
new Uuid47Transformer(str_repeat('A', 64));    // accepted

A secret longer than 16 bytes is then folded through sha256, so the derived key looks perfectly random while carrying no entropy at all. The caller gets no hint that the guard did not apply to them.

The existing provideWeakKeys data set states what the guard is meant to catch -- all NUL, all 0xFF, all 'a' -- but every case in it is exactly 16 bytes, which is why this went unnoticed. This PR adds the same three shapes at 17, 32 and 64 bytes: they fail on 8.1 and pass with the fix.

What this is not: the secret is still secret, and Uuid47Transformer documents itself as timestamp obfuscation rather than authenticated encryption, so nothing here is remotely exploitable by an attacker. It is the guard that is inconsistent -- the very same weak secret is refused at 16 bytes and accepted at 17 -- so I am sending it as an ordinary bug fix rather than through the security channel.

…er than 16 bytes

The guard compares the secret against str_repeat($secret[0], 16), so it only
ever matches a 16-byte secret. A secret made of identical bytes but longer
than that, e.g. str_repeat("\x00", 32), passes as if it were sound.

Compare against the actual length instead.
@semx

semx commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

A note on the red Fabbot check, since it is not caused by this PR.

Fabbot asks to rewrite six pre-existing lines in Uuid47Transformer::transform() from $bytes[0] = $bytes[0] ^ $hash[5] to $bytes[0] ^= $hash[5]. Applying that patch makes the component fatal:

PHP Fatal error: Cannot use assign-op operators with string offsets

PHP does not allow assign-op on a string offset, and long_to_shorthand_operator is marked RISKY precisely for this case. The rule only reaches this file now because Fabbot inspects changed files and nobody had touched it since 8.1.

I opened #65106 to remove the pattern the rule trips on. Once that lands I will rebase this one and the check should go green. Happy to reorder or combine them if you prefer.

@nicolas-grekas

Copy link
Copy Markdown
Member

Thank you @semx.

@nicolas-grekas
nicolas-grekas merged commit 64b4f01 into symfony:8.1 Aug 2, 2026
12 of 13 checks passed
@fabpot fabpot mentioned this pull request Aug 7, 2026
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.

3 participants