Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Anyone can call ETHRegistrarController.register for already existing commitments and set a reverse record to the caller instead of the owner of a record #81

Open
code423n4 opened this issue Jul 18, 2022 · 2 comments
Labels
bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/ethregistrar/ETHRegistrarController.sol#L170
https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/ethregistrar/ETHRegistrarController.sol#L270-L281

Vulnerability details

Impact

To prevent front running, the ETHRegistrarController contract uses a two-step process to register names. First one has to call ETHRegistrarController.commit with the desired configuration parameters and wait for minCommitmentAge to pass by. Then a call to ETHRegistrarController.register with the same parameters as in the previous step to finally register the name. This ETHRegistrarController.register can be front-run without any consequences by anyone else. At least that's the case for registering the name.

If reverseRecord is set to true and the ETHRegistrarController.register function is called by anyone else than the owner, a reverse record with name is set for the caller address msg.sender instead of the owner.

Proof of Concept

ETHRegistrarController.sol#L170

if (reverseRecord) {
    _setReverseRecord(name, resolver, msg.sender); // @audit-info the caller address `msg.sender` of `ETHRegistrarController.register` will be used as the `owner` for `_setReverseRecord`
}

ETHRegistrarController._setReverseRecord

function _setReverseRecord(
    string memory name,
    address resolver,
    address owner
) internal {
    reverseRegistrar.setNameForAddr(
        msg.sender,
        owner,
        resolver,
        string.concat(name, ".eth")
    );
}

Example

  1. Bob wants to register foo.eth and calls ETHRegistrarController.commit with the appropriate parameters and reverseRecord = true
  2. After the waiting period of minCommitmentAge, Bob calls ETHRegistrarController.register with the same parameters as in the step before
  3. Alice monitors the mempool and spots the transaction from Bob. She decides to front run with more gas and wins the race. Her transaction get's processed first
  4. The name foo.eth is successfully registered (with Bob as the owner), however, Alice has now her address associated with a reverse record set to foo.eth and Bob is missing the reverse record for his address.

Copy-paste the following test into the TestEthRegistrarController.js file and run the tests:

it.only("should set the reverse record of the account from someone else", async () => {
  const commitment = await controller.makeCommitment(
    "reverse",
    registrantAccount,
    REGISTRATION_TIME,
    secret,
    resolver.address,
    [],
    true,
    0,
    0
  );
  await controller.commit(commitment);

  await evm.advanceTime((await controller.minCommitmentAge()).toNumber());
  await controller
    .connect(signers[2])
    .register(
      "reverse",
      registrantAccount,
      REGISTRATION_TIME,
      secret,
      resolver.address,
      [],
      true,
      0,
      0,
      { value: BUFFERED_REGISTRATION_COST }
    );

  const signer2Address = await signers[2].getAddress();

  expect(await resolver.name(getReverseNode(signer2Address))).to.equal(
    "reverse.eth"
  ); // @audit-info caller "steals" reverse record
  expect(await resolver.name(getReverseNode(registrantAccount))).to.equal(""); // @audit-info owner of registered name lacks reverse record
});

Tools Used

Manual review

Recommended mitigation steps

Consider using the owner instead of msg.sender:

ETHRegistrarController.sol#L170

if (reverseRecord) {
    _setReverseRecord(name, resolver, owner); // @audit-info use `owner` instead of `msg.sender`
}

ETHRegistrarController._setReverseRecord

function _setReverseRecord(
    string memory name,
    address resolver,
    address owner
) internal {
    reverseRegistrar.setNameForAddr(
        owner, // @audit-info use `owner` instead of `msg.sender`
        owner,
        resolver,
        string.concat(name, ".eth")
    );
}
@code423n4 code423n4 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Jul 18, 2022
code423n4 added a commit that referenced this issue Jul 18, 2022
@jefflau jefflau added the disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) label Jul 22, 2022
@jefflau
Copy link
Collaborator

jefflau commented Jul 22, 2022

The reverse for a name can be set to any name a user wants to, so despite this being a bug that it is possible, the name is still registered to the correct account and alice has just registered the name for bob and paid the registration fee, and has set her reverse to bob's name (but she could do that already, because setName can be set to anything that you would like).

@jefflau jefflau added the sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons label Jul 27, 2022
@dmvt
Copy link
Collaborator

dmvt commented Aug 3, 2022

I'm downgrading this to QA. There is a bug, but no negative impact on Bob.

@dmvt dmvt closed this as completed Aug 3, 2022
@dmvt dmvt added QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Aug 3, 2022
@dmvt dmvt reopened this Sep 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons
Projects
None yet
Development

No branches or pull requests

3 participants