IamZKdevETH
high
During a thorough smart contract audit of the D3MM contract, a serious vulnerability was found. This vulnerability can allow a malicious actor to initialize the contract by calling the init function, potentially enabling them to become the contract's creator without the original owner's knowledge.
The init function in the smart contract D3MM.sol is designed to set various parameters such as the contract creator, maker, vault, oracle, feeRateModel, and maintainer. However, this function does not check if the caller is the original contract owner or has the necessary permissions. This lack of access control mechanism makes it possible for anyone, including potential attackers, to call this function.
An attacker can exploit this by monitoring the blockchain for the contract deployment and then calling the init function with a higher gas fee. By doing so, they can pre-empt the owner's transaction and thus take control of the contract. The attack can be done stealthily without the knowledge of the contract owner, giving the attacker complete control over the contract.
If this vulnerability is exploited, a malicious actor could gain control over the contract during the initialization process. The attacker, by being able to set the addresses for crucial components like the creator, oracle, vault, maker, and fee rate model, would have substantial influence over the smart contract's functionalities. This includes potential access to assets stored in the vault, ability to manipulate data provided by the oracle, and power to arbitrarily set the fee rate.
function init(
address creator,
address maker,
address vault,
address oracle,
address feeRateModel,
address maintainer
) external {
initOwner(creator);
state._CREATOR_ = creator;
state._D3_VAULT_ = vault;
state._ORACLE_ = oracle;
state._MAKER_ = maker;
state._FEE_RATE_MODEL_ = feeRateModel;
state._MAINTAINER_ = maintainer;
}
The vulnerability was discovered using a combination of Foundry and manual contract review. A simulation was also created to reproduce the attack scenario using Foundry's virtual machine (VM) functionality.
Test result anyone can call the init function.
Traces:
[2303816] D3MMTest::testSetNewMakerCallByHacker(0x000000000000000000000000000000000000049B)
├─ [0] VM::assume(true)
│ └─ ← ()
├─ [0] VM::assume(true)
│ └─ ← ()
├─ [2148287] → new D3Maker@0x92a6649Fdcc044DA968d94202465578a9371C7b1
│ └─ ← 10730 bytes of code
├─ [0] VM::assume(true)
│ └─ ← ()
├─ [0] VM::prank(0x000000000000000000000000000000000000049B)
│ └─ ← ()
├─ [89224] D3Maker::init(0x0000000000000000000000000000000000000237, d3MM: [0x65B6A5f2965e6f125A8B1189ed57739Ca49Bc70e], 100000)
│ └─ ← ()
├─ [0] VM::stopPrank()
│ └─ ← ()
└─ ← ()
To mitigate this vulnerability, we recommend implementing a mechanism to restrict who can call the init function. Typically, this would be limited to the contract owner or specific addresses that have been granted permission.
Here's an example of how to restrict access:
function init(
address creator,
address maker,
address vault,
address oracle,
address feeRateModel,
address maintainer
) external onlyOwner {
initOwner(creator);
state._CREATOR_ = creator;
state._D3_VAULT_ = vault;
state._ORACLE_ = oracle;
state._MAKER_ = maker;
state._FEE_RATE_MODEL_ = feeRateModel;
state._MAINTAINER_ = maintainer;
}
In the above example, the onlyOwner modifier checks that the sender of the message (msg.sender) is the owner of the contract. If they are not, the contract throws an error and reverts the transaction.
By implementing this change, the init function will be callable only by the contract owner, thereby securing it from potential attackers.