Skip to content

Latest commit

 

History

History
163 lines (124 loc) · 6.91 KB

File metadata and controls

163 lines (124 loc) · 6.91 KB

Puny Obsidian Beaver

Medium

The protocol can sign fake attestations as a result of a hash collision in _keccakForCreateAttestation resulting in fraudulent identity verification

Summary

The attestation system allows attackers to create fraudulent attestations by exploiting a hash collision in the _keccakForCreateAttestation function. The vulnerability stems from using abi.encodePacked() with multiple variable-length strings, combined with predictable fixed-length parameters. An attacker can create colliding attestations that reuse valid signatures, effectively bypassing the protocol's identity verification system.

The way the function _keccakForCreateAttestation is implemented results in a hash collision, i.e return keccak256(abi.encodePacked(profileId, randValue, account, service, evidence));

where:-

  1. evidence is a string
  2. service is a string
  3. account is a string
  4. randValue is an integer which is just the value of Date.now
  5. profileId is a well known integer

Root Cause

In EthosAttestations:528 there is a hash collision when abi.encodePacked is used in conjunction with keccak256

Internal pre-conditions

The protocol must have signed at least one valid attestation

External pre-conditions

N/A

Attack Path

When creating an attestation, the function createAttestation is called in EthosAttestation.sol.

There is a subsequent signature check, i.e. validateAndSaveSignature implemented as:

    validateAndSaveSignature(
      _keccakForCreateAttestation(
        profileId,
        randValue,
        attestationDetails.account,
        attestationDetails.service,
        evidence
      ),
      signature
    );

which is supposed to verify the attestation's signature. The parameters this function takes is another function _keccakForCreateAttestation and a signature. The _keccakForCreateAttestation looks like below:

function _keccakForCreateAttestation(
    uint256 profileId,
    uint256 randValue,
    string calldata account,
    string calldata service,
    string calldata evidence
  ) private pure returns (bytes32) {
    return keccak256(abi.encodePacked(profileId, randValue, account, service, evidence));
  }

Notice how it returns, i.e return keccak256(abi.encodePacked(profileId, randValue, account, service, evidence));

From the solidity docs, when abi.encodePacked() is used with multiple variable-length arguments (such as strings), the packed encoding does not include information about the boundaries between different arguments. This can lead to situations where different combinations of arguments result in the same encoded output, causing hash collisions.

As all the parameters in the _keccakForCreateAttestation are user specified(conveniently, the randValue isn't actually random but rather the current timestamp as seen in BlockchainManager.ts#L354 a hash collision arises.

There is no check at all to check for the hash collision in the associated EthosAttestation.ts

/**
   * Creates a new attestation and links it to the current sender's profile.
   * @param profileId Profile id. Use max uint for non-existing profile.
   * @param randValue Random value.
   * @param attestationDetails Attestation details with service name and account.
   * @param evidence Evidence of attestation.
   * @param signature Signature of the attestation.
   * @returns Transaction response.
   */
  async createAttestation(
    profileId: number,
    randValue: number,
    attestationDetails: { account: string; service: AttestationService },
    evidence: string,
    signature: string,
  ): Promise<ContractTransactionResponse> {
    return await this.contract.createAttestation(
      profileId,
      randValue,
      attestationDetails,
      evidence,
      signature,
    );
  }

Attestations in the Ethos protocol are very important as seen in their whitepaper and this breaks a core invariant of the protocol! Fake user attestations can be signed by the protocol for that matter.

Impact

The protocol signs fake user attestations breaking attestation's vital purpose of preventing fraud and impersonation.

From the whitepaper

Lying about your identity, however, is the most expensive action you can take on Ethos. Fraudulent attestation warrants Social Validation and Slashing (see: Vouch), which costs both reputation and staked Ethereum.

PoC

  1. Obtain a valid attestation onchain. Lets take this transaction for example where
    profileId: 978,
    randValue: 1729872420831,
    account: "257724631",
    service: "x.com",
    evidence: "https://x.com/inmarelibero/status/1849845065037795662",
    signature: "0xe323e3bb04be25ef4b40680624b2f07a9ff1f302d20566a3d4c7f5cfd669747866e6a40321c94782c41e98c3988e1fe63ea19e0184420580bb790ea9117f029c1b"
  1. Create colliding parameters that produce the same hash:
    profileId: 978,
    randValue: 1729872420831,
    account: "257724631x.com",
    service: "",
    evidence: "https://x.com/inmarelibero/status/1849845065037795662"
  1. Submit the colliding attestation using the original signature:
await ethosAttestations.createAttestation(
    978,
    1729872420831,
    {
        account: "257724631x.com",
        service: ""
    },
    "https://x.com/inmarelibero/status/1849845065037795662",
    "0xe323e3bb04be25ef4b40680624b2f07a9ff1f302d20566a3d4c7f5cfd669747866e6a40321c94782c41e98c3988e1fe63ea19e0184420580bb790ea9117f029c1b"
);

PoC

The transaction succeeds because:

  1. The hash matches the original due to collision
  2. The signature is valid for this hash
  3. The signature hasn't been used before

Mitigation

  1. Avoid using abi.encodePacked(), instead use abi.encode(). Unlike abi.encodePacked(), abi.encode() includes additional type information and length prefixes in the encoding, making it much less prone to hash collisions.
  2. Use a truly random number and not just Date.now. Consider a Verifiable Random Function(VRF) from Chainlink.