实战例子:Solidity代码小失误导致池子里60万USDT被盗空 被盗原因:利用token的漏洞
查看合约地址 一、先看这笔交易:黑客用0.04个eth换了112个USDT 再用112个USDT换了101个TCR 关键这步:101个TCR换了63.9万USDT(价值400万RMB),如下图
第1步,点击这里 第2步,点address
第3步,点contract,就可以看合约源码了
/**
*Submitted for verification at Etherscan.io on 2021-04-13
*/
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.2;
// interface need to claim rouge tokens from contract and handle upgraded functions
abstract contract IERC20 {
function balanceOf(address owner) public view virtual returns (uint256);
function transfer(address to, uint256 amount) public virtual;
function allowance(address owner, address spender)
public
view
virtual
returns (uint256);
function totalSupply() public view virtual returns (uint256);
}
// interface to potential future upgraded contract,
// only essential write functions that need check that this contract is caller
abstract contract IUpgradedToken {
function transferByLegacy(
address sender,
address to,
uint256 amount
) public virtual returns (bool);
function transferFromByLegacy(
address sender,
address from,
address to,
uint256 amount
) public virtual returns (bool);
function approveByLegacy(
address sender,
address spender,
uint256 amount
) public virtual;
}
//
// The ultimate ERC20 token contract for TecraCoin project
//
contract TcrToken {
//
// ERC20 basic information
//
uint8 public constant decimals = 8;
string public constant name = "TecraCoin";
string public constant symbol = "TCR";
uint256 private _totalSupply;
uint256 public constant maxSupply = 21000000000000000;
string public constant version = "1";
uint256 public immutable getChainId;
//
// other flags, data and constants
//
address public owner;
address public newOwner;
bool public paused;
bool public deprecated;
address public upgradedAddress;
bytes32 public immutable DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
string private constant ERROR_DAS = "Different array sizes";
string private constant ERROR_BTL = "Balance too low";
string private constant ERROR_ATL = "Allowance too low";
string private constant ERROR_OO = "Only Owner";
//
// events
//
event Transfer(address indexed from, address indexed to, uint256 value);
event Paused();
event Unpaused();
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event AddedToBlacklist(address indexed account);
event RemovedFromBlacklist(address indexed account);
//
// data stores
//
mapping(address => mapping(address => uint256)) private _allowances;
mapping(address => uint256) private _balances;
mapping(address => bool) public isBlacklisted;
mapping(address => bool) public isBlacklistAdmin;
mapping(address => bool) public isMinter;
mapping(address => bool) public isPauser;
mapping(address => uint256) public nonces;
//
// contract constructor
//
constructor() {
owner = msg.sender;
getChainId = block.chainid;
// EIP712 Domain
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name)),
keccak256(bytes(version)),
block.chainid,
address(this)
)
);
}
//
// "approve"
//
function approve(address spender, uint256 amount) external {
if (deprecated) {
return
IUpgradedToken(upgradedAddress).approveByLegacy(
msg.sender,
spender,
amount
);
}
_approve(msg.sender, spender, amount);
}
//
// "burnable"
//
function burn(uint256 amount) external {
require(_balances[msg.sender] >= amount, ERROR_BTL);
_burn(msg.sender, amount);
}
function burnFrom(address from, uint256 amount) external {
require(_allowances[msg.sender][from] >= amount, ERROR_ATL);
require(_balances[from] >= amount, ERROR_BTL);
_approve(msg.sender, from, _allowances[msg.sender][from] - amount);
_burn(from, amount);
}
//
// "transfer"
//
function transfer(address to, uint256 amount) external returns (bool) {
if (deprecated) {
return
IUpgradedToken(upgradedAddress).transferByLegacy(
msg.sender,
to,
amount
);
}
require(_balances[msg.sender] >= amount, ERROR_BTL);
_transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool) {
if (deprecated) {
return
IUpgradedToken(upgradedAddress).transferFromByLegacy(
msg.sender,
from,
to,
amount
);
}
_allowanceTransfer(msg.sender, from, to, amount);
return true;
}
//
// non-ERC20 functionality
//
// Rouge tokens and ETH withdrawal
function acquire(address token) external onlyOwner {
if (token == address(0)) {
payable(owner).transfer(address(this).balance);
} else {
uint256 amount = IERC20(token).balanceOf(address(this));
require(amount > 0, ERROR_BTL);
IERC20(token).transfer(owner, amount);
}
}
//
// "blacklist"
//
function addBlacklister(address user) external onlyOwner {
isBlacklistAdmin[user] = true;
}
function removeBlacklister(address user) external onlyOwner {
isBlacklistAdmin[user] = false;
}
modifier onlyBlacklister {
require(isBlacklistAdmin[msg.sender], "Not a Blacklister");
_;
}
modifier notOnBlacklist(address user) {
require(!isBlacklisted[user], "Address on blacklist");
_;
}
function addBlacklist(address user) external onlyBlacklister {
isBlacklisted[user] = true;
emit AddedToBlacklist(user);
}
function removeBlacklist(address user) external onlyBlacklister {
isBlacklisted[user] = false;
emit RemovedFromBlacklist(user);
}
function burnBlackFunds(address user) external onlyOwner {
require(isBlacklisted[user], "Address not on blacklist");
_burn(user, _balances[user]);
}
//
// "bulk transfer"
//
// transfer to list of address-amount
function bulkTransfer(address[] calldata to, uint256[] calldata amount)
external
returns (bool)
{
require(to.length == amount.length, ERROR_DAS);
for (uint256 i = 0; i < to.length; i++) {
require(_balances[msg.sender] >= amount[i], ERROR_BTL);
_transfer(msg.sender, to[i], amount[i]);
}
return true;
}
// transferFrom to list of address-amount
function bulkTransferFrom(
address from,
address[] calldata to,
uint256[] calldata amount
) external returns (bool) {
require(to.length == amount.length, ERROR_DAS);
for (uint256 i = 0; i < to.length; i++) {
_allowanceTransfer(msg.sender, from, to[i], amount[i]);
}
return true;
}
// send same amount to multiple addresses
function bulkTransfer(address[] calldata to, uint256 amount)
external
returns (bool)
{
require(_balances[msg.sender] >= amount * to.length, ERROR_BTL);
for (uint256 i = 0; i < to.length; i++) {
_transfer(msg.sender, to[i], amount);
}
return true;
}
// send same amount to multiple addresses by allowance
function bulkTransferFrom(
address from,
address[] calldata to,
uint256 amount
) external returns (bool) {
require(_balances[from] >= amount * to.length, ERROR_BTL);
for (uint256 i = 0; i < to.length; i++) {
_allowanceTransfer(msg.sender, from, to[i], amount);
}
return true;
}
//
// "mint"
//
modifier onlyMinter {
require(isMinter[msg.sender], "Not a Minter");
_;
}
function addMinter(address user) external onlyOwner {
isMinter[user] = true;
}
function removeMinter(address user) external onlyOwner {
isMinter[user] = false;
}
function mint(address to, uint256 amount) external onlyMinter {
_balances[to] += amount;
_totalSupply += amount;
require(_totalSupply < maxSupply, "You can not mine that much");
emit Transfer(address(0), to, amount);
}
//
// "ownable"
//
modifier onlyOwner {
require(msg.sender == owner, ERROR_OO);
_;
}
function giveOwnership(address _newOwner) external onlyOwner {
newOwner = _newOwner;
}
function acceptOwnership() external {
require(msg.sender == newOwner, ERROR_OO);
newOwner = address(0);
owner = msg.sender;
}
//
// "pausable"
//
function addPauser(address user) external onlyOwner {
isPauser[user] = true;
}
function removePauser(address user) external onlyOwner {
isPauser[user] = false;
}
modifier onlyPauser {
require(isPauser[msg.sender], "Not a Pauser");
_;
}
modifier notPaused {
require(!paused, "Contract is paused");
_;
}
function pause() external onlyPauser notPaused {
paused = true;
emit Paused();
}
function unpause() external onlyPauser {
require(paused, "Contract not paused");
paused = false;
emit Unpaused();
}
//
// "permit"
// Uniswap integration EIP-2612
//
function permit(
address user,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(deadline >= block.timestamp, "permit: EXPIRED");
bytes32 digest =
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(
abi.encode(
PERMIT_TYPEHASH,
user,
spender,
value,
nonces[user]++,
deadline
)
)
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(
recoveredAddress != address(0) && recoveredAddress == user,
"permit: INVALID_SIGNATURE"
);
_approve(user, spender, value);
}
//
// upgrade contract
//
function upgrade(address token) external onlyOwner {
deprecated = true;
upgradedAddress = token;
}
//
// ERC20 view functions
//
function balanceOf(address account) external view returns (uint256) {
if (deprecated) {
return IERC20(upgradedAddress).balanceOf(account);
}
return _balances[account];
}
function allowance(address account, address spender)
external
view
returns (uint256)
{
if (deprecated) {
return IERC20(upgradedAddress).allowance(account, spender);
}
return _allowances[account][spender];
}
function totalSupply() external view returns (uint256) {
if (deprecated) {
return IERC20(upgradedAddress).totalSupply();
}
return _totalSupply;
}
//
// internal functions
//
function _approve(
address account,
address spender,
uint256 amount
) private notOnBlacklist(account) notOnBlacklist(spender) notPaused {
_allowances[account][spender] = amount;
emit Approval(account, spender, amount);
}
function _allowanceTransfer(
address spender,
address from,
address to,
uint256 amount
) private {
require(_allowances[from][spender] >= amount, ERROR_ATL);
require(_balances[from] >= amount, ERROR_BTL);
// exception for Uniswap "approve forever"
if (_allowances[from][spender] != type(uint256).max) {
_approve(from, spender, _allowances[from][spender] - amount);
}
_transfer(from, to, amount);
}
function _burn(address from, uint256 amount) private notPaused {
_balances[from] -= amount;
_totalSupply -= amount;
emit Transfer(from, address(0), amount);
}
function _transfer(
address from,
address to,
uint256 amount
) private notOnBlacklist(from) notOnBlacklist(to) notPaused {
require(to != address(0), "Use burn instead");
require(from != address(0), "What a Terrible Failure");
_balances[from] -= amount;
_balances[to] += amount;
emit Transfer(from, to, amount);
}
}
// rav3n_pl was here
部署一个合约,然后在合约里面进行很多交易,然后把USDT转走了
第4步,查看合约创建时间
合约创建300多天了, 但是黑客是Feb-04-2022 10:54:37 AM交易的,说明上线很久,才发行漏洞。
而且合约没有经过安全校验。 二、分析合约代码 在tenderly.co上分析合约操作步骤
让池子里的TCR币突然就燃烧掉很多,池子里的TCR变少了,价格自然变高。所以101个TCR换了 63.9万个USDT 错误代码: require(_allowances[msg.sender][from] >= amount, ERROR_ATL); 应该是:require(_allowances[from] [msg.sender]>= amount, ERROR_ATL); 写反了,导致任何人都可以燃烧任何人的token
|