Một lỗ hổng ERC-20 mới hiện ra.
Trong tuần qua, tôi nhận được yêu cầu kiểm tra hợp đồng thông minh của một dự án DeFi mới nổi. Chỉ sau 30 phút fork và chạy phân tích tĩnh, tôi phát hiện ra một lỗi metadata nghiêm trọng: hàm name() và symbol() có thể bị ghi đè bởi bất kỳ ai gọi _setMetadata(). Không có modifier kiểm soát quyền truy cập. Một token ERC-20 tiêu chuẩn, nhưng ai cũng có thể đổi tên nó thành 'USDC' và đánh lừa người dùng.
Đây không phải là một attack vector mới về mặt lý thuyết. Nhưng nó là một vết nứt điển hình mà tôi thấy trong ít nhất 12 dự án trong năm nay — tất cả đều từ các đội ngũ non trẻ, vội vàng deploy mà không qua audit đàng hoàng.
Context: Chu kỳ thổi phồng metadata token
Kể từ mùa hè DeFi 2020, metadata token (tên, ký hiệu, decimals) thường bị xem nhẹ. Ai cũng nghĩ: 'Chỉ là tên hiển thị thôi mà, làm gì có chuyện bị hack từ cái tên?' Nhưng thực tế, các sàn DEX như Uniswap, PancakeSwap đều hiển thị metadata từ hợp đồng gốc. Nếu kẻ tấn công đổi tên token fake thành tên của token uy tín, người dùng sẽ swap nhầm mà không hề hay biết.
Bản thân tiêu chuẩn ERC-20 không bắt buộc hàm name() hay symbol(). Nhiều dev trẻ sao chép code mẫu từ OpenZeppelin nhưng lại chỉnh sửa lung tung, thêm hàm setter mà quên mất access control. Đó chính là lỗ hổng mà tôi vừa tìm thấy.
Core: Tháo gỡ có hệ thống lỗi `_setMetadata`
Hợp đồng được deploy trên testnet Goerli với 15 triệu token khởi tạo. Tôi fork mainnet tại block 19.200.000, triển khai lại hợp đồng trong môi trường Foundry, và viết kịch bản khai thác bằng Solidity.
Mã lỗi nằm ở dòng 87 của file CryptoPunkzToken.sol:
function _setMetadata(string memory newName, string memory newSymbol) internal {
name = newName;
symbol = newSymbol;
}
Hàm này được gọi từ constructor và cũng từ một hàm public updateMetadata không có modifier:
function updateMetadata(string memory _name, string memory _symbol) public {
_setMetadata(_name, _symbol);
}
Bất kỳ ai cũng có thể gọi updateMetadata và thay đổi tên token thành bất cứ thứ gì. Tôi viết contract tấn công:
contract Exploit {
CryptoPunkzToken target;
constructor(address _target) {
target = CryptoPunkzToken(_target);
target.updateMetadata("USDC", "USDC");
}
}
Sau khi deploy Exploit, token gốc hiển thị là USDC. Tạo một pool trên Uniswap V2 với token gốc và WETH, người dùng thấy biểu tượng USDC/WETH 0.3% — và swap. Họ mua phải token rác.
Hậu quả: ví dụ 500 ETH đã bị lừa trong vòng 48 giờ trước khi tôi báo cáo cho đội ngũ dự án. Họ đã deploy lại contract và khóa hàm updateMetadata. Nhưng thiệt hại đã xảy ra.
Contrarian: Phe bò lại đúng — metadata không phải là không cần thiết
Nghịch lý của lỗ hổng này: metadata token là tính năng hiển thị tưởng chừng vô hại, nhưng chính vì bị xem nhẹ mà nó trở thành công cụ tấn công mạnh mẽ. Nếu không có metadata, các DEX sẽ phải dùng địa chỉ contract làm nhãn — điều này khiến người dùng khó phân biệt token hơn, nhưng cũng triệt tiêu vector này.
Tuy nhiên, side bò có lý: metadata giúp UX dễ tiếp cận, thu hút người dùng mới. Chìa khóa là không nên cho phép thay đổi metadata sau khi token được mint. Hoặc nếu có, phải thông qua governance với multisig và thời gian trễ 48 giờ.
Trong 12 dự án tôi từng audit, chỉ có 2 áp dụng cơ chế freeze metadata. Còn lại đều chơi với lửa.
Takeaway: Trách nhiệm thuộc về ai?
Một lỗ hổng ERC-20 mới hiện ra. Lỗi không nằm ở trình biên dịch, không nằm ở EVM. Nó nằm ở con người — dev không hiểu vì sao public là nguy hiểm, team không audit trước khi deploy. Ai sẽ trả giá? Người dùng cuối, như mọi khi.
Metadata NFT không bao giờ đáng tin cậy. Và metadata token cũng thế. Hãy kiểm tra nguồn gốc, check bytecode, và đừng bao giờ tin vào cái tên hiện lên trên màn hình.