Security MEDIUM: NameRegistry reverse-lookup poisoning#8
Open
philpof102-svg wants to merge 2 commits into
Open
Security MEDIUM: NameRegistry reverse-lookup poisoning#8philpof102-svg wants to merge 2 commits into
philpof102-svg wants to merge 2 commits into
Conversation
) Previously register() and update() blindly wrote didToName[didHash] without checking whether the DID was already mapped to a different name. This let anyone register a fresh name pointing at a victim's already-claimed DID, overwriting the reverse mapping and turning consumers' reverseLookup(victimDid) → 'attacker-name' for phishing. Attack walkthrough : 1. Legit user registers 'alice' → did:gitlawb:alice didToName[hash(did:gitlawb:alice)] = 'alice' ✓ 2. Attacker calls register('alice-pay', 'did:gitlawb:alice') - Name 'alice-pay' is free, so NameTaken does not fire - didToName overwritten to 'alice-pay' ✗ 3. Any consumer trusting reverseLookup(did:gitlawb:alice) sees 'alice-pay' instead of 'alice' — pays the attacker, signs the attacker's contracts, etc. Fix : - register(): require didToName[didHash] empty before write. New error DIDAlreadyClaimed(bytes32 didHash). - update(): require didToName[newDidHash] empty UNLESS the caller is re-pointing to the SAME did (no-op refresh of updatedAt allowed). - Both functions reject empty-string DIDs (EmptyDID error) — a separate edge case that lets attackers spam the '' reverse slot. Semantics now match the documented intent : a name owns a DID, and a DID can be owned by at most one name at a time. Backward compatible : existing collision-free deployments see no change. Names that legitimately re-point to a fresh DID continue to work because the old DID's mapping is deleted before the new one is written. Suggested regression test (Foundry) : function test_DIDPoisoning_Reverts() public { registry.register('alice', 'did:gitlawb:alice'); vm.prank(attacker); vm.expectRevert(abi.encodeWithSelector( GitlawbNameRegistry.DIDAlreadyClaimed.selector, keccak256(bytes('did:gitlawb:alice')) )); registry.register('alice-pay', 'did:gitlawb:alice'); } function test_SameOwner_SameDID_UpdateIsNoOp() public { registry.register('alice', 'did:gitlawb:alice'); registry.update('alice', 'did:gitlawb:alice'); // refresh updatedAt assertEq(registry.reverseLookup('did:gitlawb:alice'), 'alice'); } Related to PR Gitlawb#8 bug report.
Author
|
Upgraded with merge-ready fix ( Attack walkthrough
Fix // register()
bytes32 didHash = keccak256(bytes(did));
if (bytes(didToName[didHash]).length != 0) revert DIDAlreadyClaimed(didHash);
// update() — allow same-DID self-refresh
if (newDidHash != oldDidHash && bytes(didToName[newDidHash]).length != 0) {
revert DIDAlreadyClaimed(newDidHash);
}Also: both functions now reject empty-string DIDs ( Regression tests (Foundry) function test_DIDPoisoning_Reverts() public {
registry.register('alice', 'did:gitlawb:alice');
vm.prank(attacker);
vm.expectRevert(abi.encodeWithSelector(
GitlawbNameRegistry.DIDAlreadyClaimed.selector,
keccak256(bytes('did:gitlawb:alice'))
));
registry.register('alice-pay', 'did:gitlawb:alice');
}
function test_SameOwner_SameDID_UpdateIsNoOp() public {
registry.register('alice', 'did:gitlawb:alice');
registry.update('alice', 'did:gitlawb:alice');
assertEq(registry.reverseLookup('did:gitlawb:alice'), 'alice');
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Severity: MEDIUM — identity confusion / phishing surface
NameRegistry.register() and update() write the didToName reverse mapping without checking whether the DID is already mapped. Attacker registers a different name with victim's DID → poisons reverseLookup(victimDID) to return attacker-chosen name → social engineering on any UI displaying agent names.
The forward mapping (name → did) is fine. The reverse direction is the only impacted surface, but it IS the canonical display direction for agent UIs.
Full PoC + fix in BUG_REPORT_name_reverse_lookup_poison.md.
Reporter @philpof102-svg — 4th PR in this audit run. Related : #5, #6, #7.