fix(federation): replace strcmp token oracle with hash-based comparison in requestSharedSecret#41579
fix(federation): replace strcmp token oracle with hash-based comparison in requestSharedSecret#41579DeepDiver1975 wants to merge 2 commits into
Conversation
The requestSharedSecret endpoint (@publicpage, unauthenticated) used strcmp() to compare the caller-supplied token against the stored local token, returning 403 when localToken > submitted_token and 200 otherwise. This binary oracle allows an attacker to binary-search the stored token in ~96 requests and then use it to obtain the federation shared secret. Replace strcmp($a, $b) with strcmp(hash("sha256",$a), hash("sha256",$b)). The deterministic tiebreaking property is preserved while the response reveals nothing about the plaintext token value. Signed-off-by: Thomas Müller <thomas.mueller@owncloud.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
|
Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a changelog item based on your changes. |
Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
DeepDiver1975
left a comment
There was a problem hiding this comment.
Code Review — fix(federation): replace strcmp token oracle with hash-based comparison in requestSharedSecret
Overview: requestSharedSecret (@PublicPage, unauthenticated) used strcmp($localToken, $submitted) to determine which server's token wins a simultaneous federation exchange. Because different responses (200 vs 403) are returned for >0 vs <=0, an attacker can binary-search the stored $localToken in ~96 requests. The fix hashes both values with SHA-256 before comparing, so the response reveals nothing about the plaintext token.
Correctness of the fix
if (\strcmp(\hash('sha256', $localToken), \hash('sha256', $token)) > 0) {The tiebreak semantics are preserved: both sides hash deterministically, the same side still "wins" for any given pair (assuming neither server has the other's real token), and no plaintext ordering is revealed. ✅
Is SHA-256 appropriate here? Yes — the goal is to eliminate the ordering oracle, not to provide cryptographic secrecy of the token itself. Both tokens are hashed identically; keyed HMAC would be overkill since the comparison is symmetric and the attacker doesn't get the hash value directly (only the 200/403 response). ✅
Does the tiebreak still resolve correctly? For the legitimate two-server exchange scenario, both servers hold the same $localToken and receive the other's token. After hashing, the same lexicographic comparison runs, so whichever side has the higher-hashing token still wins. ✅
Test analysis
dataTestRequestSharedSecret: Updated to use concrete token pairs where the hash-ordering matches the expected status code. The comment block explaining Case 1 vs Case 2 is unusually thorough for a test file but is genuinely useful here since the tokens are opaque — it makes the intent reviewable. ✅
testRequestSharedSecretNoOracleLeakage: This is the critical regression test. It uses a pair where strcmp(localToken, token) > 0 (old code: 403) but strcmp(hash(localToken), hash(token)) < 0 (new code: 200). This is exactly the distinguishing case that proves the fix changes behavior for the exploitable scenario. ✅
The test verifies:
jobList->add(GetSharedSecret)is called (remote wins, we schedule fetch)jobList->remove(RequestSharedSecret)is called- Status is 200 ✅
Changelog
changelog/unreleased/41579 accurately describes the oracle attack and the fix, including the "~96 requests" estimate. ✅
Summary
| Aspect | Assessment |
|---|---|
| Oracle eliminated | ✅ Hash comparison reveals no plaintext ordering |
| Tiebreak semantics | ✅ Preserved |
| Regression test | ✅ Directly demonstrates old code fails, new code passes |
| Data provider | ✅ Updated with hash-ordered token pairs + explanatory comments |
| Changelog | ✅ Accurate |
Verdict: Ready to merge. High-impact security fix with solid test coverage.
Summary
requestSharedSecret(@PublicPage, unauthenticated) usedstrcmp($localToken, $submitted)to decide 403 vs 200, leaking token ordering to any callergetSharedSecretto steal the federation shared secretSecurity Impact
High — unauthenticated callers can recover federation shared secrets when they know a trusted server URL
Test plan
testRequestSharedSecretNoOracleLeakageuses a token pair where oldstrcmpreturns 403 but hash-based returns 200; fails without fixdataTestRequestSharedSecretupdated with hash-ordered token pairsmake test TEST_PHP_SUITE=apps/federation🤖 Generated with Claude Code