0x01:前言
风投 DAO 组织 Build Finance 在社交媒体发文表示,该项目遭遇恶意治理攻击,攻击者恶意铸造了110万枚BUILD并抛售套利。知道创宇区块链安全实验室第一时间对本次事件深入跟踪并进行分析。
0x02:事件详情
攻击者Suho.eth(0xD6dBed6297B539A11f1aAb7907e7DF7d9FFeda7e)在区块高度为14169198时尝试恶意接管,投票失败,随后在区块高度为
14175830发起的提案成功通过:
该合约线上提案投票地址 https://snapshot.org/#/build ,最近的提案在2021年11月24号,而参与线上提案的钱包地址未发现与治理合约有交互,推测线上的提案不直接上链和合约交互:
治理合约 0x3157439c84260541003001129c42fb6aba57e758 提案相关的合约代码如下:
function propose(address _target, bytes memory _data) public lockVotes returns (uint) {
require(balanceOf[msg.sender] >= proposalThreshold, "Governance::propose: proposer votes below proposal threshold");
bytes32 txHash = keccak256(abi.encode(_target, _data));
proposalCount++;
Proposal memory newProposal = Proposal({
id: proposalCount,
proposer: msg.sender,
startTime: block.timestamp,
forVotes: 0,
againstVotes: 0,
txHash: txHash,
executed: false
});
proposals[newProposal.id] = newProposal;
return proposalCount;
}
function vote(uint _proposalId, bool _support) public lockVotes {
require(state(_proposalId) == ProposalState.Active, "Governance::vote: voting is closed");
Proposal storage proposal = proposals[_proposalId];
Receipt storage receipt = receipts[_proposalId][msg.sender];
require(receipt.hasVoted == false, "Governance::vote: voter already voted");
uint votes = balanceOf[msg.sender];
if (_support) {
proposal.forVotes += votes;
} else {
proposal.againstVotes += votes;
}
receipt.hasVoted = true;
receipt.support = _support;
receipt.votes = votes;
}
该函数方法允许任何拥有一定数量资产的用户发起提案,持有该资产的其他用户进行投票,函数代码未发现安全问题,因此我们推测攻击者可能是通过合约发起的提案。在提案通过后,攻击者铸造了100万个BUILD代币,耗尽大部分Balancer和Uniswap流动性池的资金:
随后又通过治理合约控制平衡池,耗尽包括13万METRIC代币在内的其他数字资产:
最后丧心病狂的铸造了一亿个Build,出售给任何还存在流动性的池子:
目前还未确定攻击者发起通过的提案内容,但根据通过提案后的铸币行为,跟进到代币合约0x6e36556b3ee5aa28def2a8ec3dae30ec2b208739:
address public governance;
constructor () public ERC20Detailed("BUILD Finance", "BUILD", 18) {
governance = msg.sender;
}
function mint(address account, uint amount) public {
require(msg.sender == governance, "!governance");
_mint(account, amount);
}
function setGovernance(address _governance) public {
require(msg.sender == governance, "!governance");
governance = _governance;
}
合约在初始化的时候会设置合约拥有者为治理者,并且只有治理者可以发起铸币请求,而只有治理者才能调用setGovernance函数更换治理者,因此可以确定,攻击者发起的具体提案为更换治理者。
在创建合约的时候,治理者为0x2Cb037BD6B7Fbd78f04756C99B7996F430c58172,也就是合约部署者,他在部署合约后将治理者更换为Time Lock合约0x38bce4b45f3d0d138927ab221560dac926999ba6:
而在2021年1月,Time Lock合约将治理权交给了0x5a5a6ebeb61a80b2a2a5e0b4d893d731358d888583:
最后在2022年2月,由Suho.eth(0xD6dBed6297B539A11f1aAb7907e7DF7d9FFeda7e)发起提案,利用低投票阈值将治理者更换为0xdcc8a38a3a1f4ef4d0b4984dcbb31627d0952c28,恶意接管后铸币套现。
0x03:总结
经过完整分析,知道创宇区块链安全实验室明确了该次事件的源头由攻击者创造低阈值提案,让自己恶意接管了治理权限,去中心化的治理实现是很有必要的,但不应该让攻击者可以利用少量投票就通过提案。