티스토리 뷰
1. 문제 설명
The goal of this level is for you to claim ownership of the instance you are given.
Things that might help
- Look into Solidity's documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope.
- Fallback methods
- Method ids
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Delegate {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
해당 컨트랙트의 소유권을 빼앗으면 문제가 해결됩니다.
2. 취약점 분석
우리에게 주어진 컨트랙트의 주소는 Delegation 컨트랙트이며 해당 컨트랙트에서 owner를 변경하는 함수는 따로 없습니다. 남은 함수는 fallback 함수인데, fallback 함수에서 delegate의 주소에 delegatecall을 수행합니다. 따라서 delegatecall에 대해서 제대로 알아보고, 문제점을 확인해봐야 할 것 같습니다.
Call vs Delegate Call
call
솔리디티에서 call 함수는 다른 컨트랙트의 함수를 호출하거나 이더를 전송할 때, 그리고 그 둘을 동시에 하려고 할 때 사용하며 return 값으로 호출 성공 여부와 반환 값을 받게 됩니다.
pragma solidity ^0.8.0;
contract Caller {
function callAnotherContract(address _target, uint256 _value) public returns (bool success, bytes memory data) {
// _target 주소로 value와 함께 call 실행
(success, data) = _target.call{value: _value}(
abi.encodeWithSignature("set(uint256)", 123)
);
}
}
contract Callee {
uint256 public num;
function set(uint256 _num) public payable {
num = _num;
}
}
위의 예제처럼 Caller 컨트랙트에서 Callee 컨트랙트의 set 함수를 호출하게 되면 Callee 컨트랙트의 num 변수가 변경됩니다.
delegatecall
솔리디티에서 delegatecall 함수도 call 함수와 마찬가지로 다른 컨트랙트의 함수를 호출하는데 사용하지만 다른 점은 대상 컨트랙트의 코드를 호출자의 컨텍스트에서 실행합니다. 이 말은 즉, 호출자의 스토리지와 상태, 주소를 사용하여서 호출 대상의 코드를 실행한다는 것입니다. 아래의 예제를 통해 알아보겠습니다.
pragma solidity ^0.8.0;
contract Proxy {
uint256 public num; // Proxy 컨트랙트의 스토리지 사용
function delegateCallTo(address _target, uint256 _value) public {
_target.delegatecall(abi.encodeWithSignature("set(uint256)", _value));
}
}
contract Logic {
uint256 public num; // 이 스토리지는 실제 사용되지 않음
function set(uint256 _num) public {
num = _num; // Proxy의 상태를 변경
}
}
주석을 보면 알 수 있듯이 Proxy 컨트랙트에서 Logic 컨트랙트의 set 함수를 호출하였을 때 Logic 컨트랙트의 num 변수가 변경되는 것이 아닌 호출한 컨트랙트 Proxy의 num 변수가 변경됩니다. (이건 같은 이름의 변수에 매칭되어 변경되는 것이 아니라 같은 슬롯에 위치한 변수가 변경되는 것입니다. 해당 부분은 더 깔끔히 정리해서 포스팅해보도록 하겠습니다 🫡)
다시 문제로 돌아와보면 Delegation 컨트랙트에서 Delegate 컨트랙트의 pwn 함수를 호출한다면 owner가 Delegation의 msg.sender 즉, Delegation 컨트랙트를 호출한 주소가 될 것입니다. 따라서 Delegation 컨트랙트를 내 EOA 주소로 호출해 Delegate 컨트랙트의 pwn 함수를 호출하도록 만들어야 합니다.
3. 공격 수행
fallback 함수 호출
솔리디티 0.6.0 이후부터 fallback 함수는 데이터가 있는 호출과 존재하지 않는 함수 호출에 대한 처리를 하기 위해 사용되었습니다. 그렇기에 해당 컨트랙트의 fallback 함수가 동작하도록 만들기 위해서는 컨트랙트에 데이터를 담아서 트랜잭션을 보내면 될 것입니다.
pragma solidity ^0.8.0;
contract Target {
event FallbackCalled(address sender, uint256 value, bytes data);
// fallback 함수: 존재하지 않는 함수 호출 또는 데이터 포함 호출 처리
fallback() external payable {
emit FallbackCalled(msg.sender, msg.value, msg.data);
}
}
contract Caller {
function triggerFallback(address payable _target) public payable {
// call을 통해 Target 컨트랙트의 fallback 함수 호출
(bool success, ) = _target.call{value: msg.value}(abi.encodeWithSignature("nonExistentFunction()"));
require(success, "Fallback call failed");
}
}
function signature
call과 delegatecall 함수로 다른 컨트랙트의 함수를 호출하기 위해서는 function signature라는 것을 사용해야 합니다.
function signature란 함수의 이름과 파라미터의 타입들을 소괄호로 묶은 문자열을 말합니다. 이러한 문자열을 keccak256 함수로 해싱한 값의 상위 4바이트를 function hash라 부르며 function hash를 가지고 call/delegatecall 함수에 넣어 실행하면 해당 함수를 호출할 수 있게 됩니다. (설명이 너무 조잡한 것 같아서 이 부분도 다음에 자세히 정리해 올려보도록 하겠습니다 😅)
이 function hash같은 경우 Remix에서 간단히 확인해볼 수 있습니다.
Delegate 컨트랙트 컴파일 후 왼쪽 패널에 있는 Solidity compiler > Compilation Details > Function hashes
pwn 함수의 function hash는 "dd365b8b"라는 것을 확인할 수 있습니다.
컨트랙트에 data를 담아서 트랜잭션을 전송하였습니다.
owner의 주소가 성공적으로 변경되었습니다. 이제 Submit instance를 눌러 문제를 제출해봅시다.
✌️
4. 문제 보충 설명
Usage of delegatecall is particularly risky and has been used as an attack vector on multiple historic hacks. With it, your contract is practically saying "here, -other contract- or -other library-, do whatever you want with my state". Delegates have complete access to your contract's state. The delegatecall function is a powerful feature, but a dangerous one, and must be used with extreme care.
Please refer to the The Parity Wallet Hack Explained article for an accurate explanation of how this idea was used to steal 30M USD.
delegate는 '~에게 위임하다'를 뜻하는 단어입니다. delegatecall은 배포 후 코드 변경이 어려운 스마트 컨트랙트에게 있어서 원본 컨트랙트는 변경하지 않고도 새로운 로직을 반영할 수 있는 솔리디티의 강력한 기능 중 하나이지만 이를 잘못 사용하게 된다면 공격자에게 자신의 컨트랙트 상태를 완전히 위임해버릴 수 있게 됩니다. Parity Wallet Hack Explained 기사를 참고한다면 3,000만 달러가 어떻게 도난당하였는지를 정확히 알 수 있습니다.
감사합니다!
5. 참고자료
[BlockChain] DelegateCall
하이루~~ 하이루~~ 방가방가!! 나랑 같이 블록체인 공부하니까 재미찌? 이번에는 간단하지만 핵심적인 내용 delegatecall에 대해서 알아볼게 ㅎㅎ Delegatecall 블록체인하면서 많이 들어봤지?? Delegatecal
rhenus9911.tistory.com
[Solidity] Payable, Fallback and Receive || Solidity 0.8 ||
안녕하세요. 스마트 컨트렉트 개발자 개발이 체질의 최원혁입니다. 요즘 앱개발 프로젝트 계약을 하게 돼서, 블로그 작성할 시간이 많이 없네요. 하루에 한두 시간씩 공부하면서 정리노트에 정
borntodevelop.tistory.com
'Web3 > Wargame' 카테고리의 다른 글
[Ethernaut] Level 8 Vault 풀이 (0) | 2024.12.10 |
---|---|
[Ethernaut] Level 7 Force 풀이 (0) | 2024.12.10 |
[Ethernaut] Level 5 Token 풀이 (1) | 2024.12.03 |
[Ethernaut] Level 4 Telephone 풀이 (0) | 2024.11.22 |
[Ethernaut] Level 3 Coin Flip 풀이 (0) | 2024.11.19 |
- Total
- Today
- Yesterday
- blockchain
- YISF
- Los
- misc
- 디지털 포렌식
- 디스코드 봇
- Crypto
- forensic
- 파이썬
- Python
- CTF
- Ethereum
- smart contract
- writeup
- 오블완
- 이세돌
- 정렬
- 알고리즘
- ctftime
- 코딩테스트
- 웹해킹
- solidity
- web3
- 워게임
- Lord of SQL injection
- Write up
- 티스토리챌린지
- Ethernaut
- 프로그래머스
- 이더넛
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |