"verifyAndProcessWithdrawal" can be abused to steal from every validator at least once. #20
Labels
3 (High Risk)
Assets can be stolen/lost/compromised directly
bug
Something isn't working
duplicate-388
edited-by-warden
satisfactory
satisfies C4 submission criteria; eligible for awards
Lines of code
https://github.com/code-423n4/2023-04-eigenlayer/blob/398cc428541b91948f717482ec973583c9e76232/src/contracts/libraries/BeaconChainProofs.sol#L273
Vulnerability details
Impact
Detailed description of the impact of this finding.
"verifyAndProcessWithdrawal" can be abused to steal from every validator at least once.
Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Whevener user call
verifyAndProcessWithdrawal
there is a verification that proofs are validInside function
verifyWithdrawalProofs
there are not validation thatslotProof
is at least 32 length andslotProof % 32 ==0
like all the other proofs in this function.contracts/libraries/BeaconChainProofs.sol#L245
Therefore its possible to set
slotProof
to any string below 32 length and there will be no Sha256 validation insideMerkle.sol
file due toproof.length
<32 and loop start with 32src/contracts/libraries/Merkle.sol#L99
What we need to do in exploit is set
slotRoot
toblockHeaderRoot
to get a bonus eth. There maybe other ways how to get a reward from more than 1 slot per each validator but I`ve not figured it out.Tools Used
POC:
Recommended Mitigation Steps
I think its important to add these require inside merkle for security
function processInclusionProofSha256(bytes memory proof, bytes32 leaf, uint256 index) internal view returns (bytes32) { bytes32[1] memory computedHash = [leaf]; + require(proof.length % 32 == 0 && proof.length > 0, "Invalid proof length"); for (uint256 i = 32; i <= proof.length; i+=32) { if(index % 2 == 0) { // if ith bit of index is 0, then computedHash is a left sibling assembly { mstore(0x00, mload(computedHash)) mstore(0x20, mload(add(proof, i))) if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)} index := div(index, 2) } } else { // if ith bit of index is 1, then computedHash is a right sibling assembly { mstore(0x00, mload(add(proof, i))) mstore(0x20, mload(computedHash)) if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)} index := div(index, 2) } } } return computedHash[0]; }
The text was updated successfully, but these errors were encountered: