背景概述
在上一期的技术分享中,我们深入剖析了利用 tx.origin 进行钓鱼攻击的手法。今天,查找币安全团队将带大家探索一种更为隐蔽的攻击方式——在合约部署阶段隐藏恶意代码。这种手法利用开发者对合约地址的信任盲区,通过“移花接木”的方式将恶意逻辑隐藏在看似无关的合约中,极具欺骗性。
攻击原理与技术细节
核心逻辑
攻击者通过部署一个“中间合约”(如 MoneyMaker),在构造函数中传入一个恶意合约地址(而非受害者预期的合法合约地址)。受害者由于未核验部署交易,盲目信任了攻击者提供的合约地址,最终导致资金被盗。
关键点:
- 受害者预期:合约 A 调用合约 B(合法逻辑)
- 实际执行:合约 A 调用合约 C(恶意逻辑)
技术实现示例
以下是一个典型的攻击代码示例,展示了攻击者如何通过隐藏的恶意合约窃取资金。
// 看似合法的资金倍增合约
contract MoneyMaker {
Vault vault;
constructor(address _vault) {
vault = Vault(payable(_vault));
}
function makeMoney(address recipient) public payable {
require(msg.value >= 1, "You are so poor!");
uint256 amount = msg.value * 2;
(bool success, ) = address(vault).call{value: msg.value, gas: 2300}("");
require(success, "Send failed");
vault.transfer(recipient, amount);
}
}
// 受害者预期的合法金库合约
contract Vault {
address private maker;
address private owner;
uint256 transferGasLimit;
constructor() payable {
owner = msg.sender;
transferGasLimit = 2300;
}
modifier OnlyMaker() {
require(msg.sender == maker, "Not MoneyMaker contract!");
_;
}
modifier OnlyOwner() {
require(msg.sender == owner, "Not owner!");
_;
}
function setMacker(address _maker) public OnlyOwner {
maker = _maker;
}
function transfer(address recipient, uint256 amount) external OnlyMaker {
require(amount <= address(this).balance, "Game Over~");
(bool success, ) = recipient.call{value: amount, gas: transferGasLimit}("");
require(success, "Send failed");
}
function withrow() public OnlyOwner {
(bool success, ) = owner.call{value: address(this).balance, gas: transferGasLimit}("");
require(success, "Send failed");
}
receive() external payable {}
fallback() external payable {}
}
// 隐藏的恶意合约(攻击者部署)
contract Hack {
event taunt(string message);
address private evil;
constructor(address _evil) {
evil = _evil;
}
modifier OnlyEvil() {
require(msg.sender == evil, "What are you doing?");
_;
}
function transfer() public payable {
emit taunt("Haha, your ether is mine!");
}
function withrow() public OnlyEvil {
// 窃取资金逻辑
}
}
攻击流程详解
-
部署阶段:攻击者 Evil 部署
Vault合约,并存入 100 ETH。随后,Evil 部署Hack合约,并将自己的地址传入构造函数。 -
欺骗阶段:Evil 向受害者 Bob 展示
MoneyMaker合约的源码,声称该合约会调用Vault合约实现资金倍增。Bob 验证了MoneyMaker和Vault的源码,未发现恶意代码。 -
部署陷阱:Evil 部署
MoneyMaker合约时,在构造函数中传入的是Hack合约的地址,而非Vault合约地址。Bob 未检查这笔部署交易,盲目信任了 Evil。 -
资金窃取:
- Bob 调用
MoneyMaker.makeMoney()并转入 20 ETH。 - 实际执行路径:
MoneyMaker.makeMoney()→Hack.transfer()→ 触发事件"Haha, your ether is mine!" - Bob 未收到任何回报,20 ETH 被锁定在
MoneyMaker合约中。 - Evil 调用
Vault.withrow()提取 100 ETH,再通过Hack.withrow()窃取 Bob 的 20 ETH。
- Bob 调用
攻击核心要素
- 信任盲区:受害者仅验证了合约源码,未核验部署交易中的实际参数。
- 隐藏逻辑:恶意代码被部署在独立的
Hack合约中,而非直接暴露在MoneyMaker中。 - 不可逆性:一旦部署完成,攻击路径无法更改,资金损失不可挽回。
防御策略与审计建议
1. 交易验证是唯一真理
在以太坊的黑暗森林中,不要相信任何人的口头承诺或展示的源码。每笔交易都记录在链上,只有亲自验证部署交易中的实际参数,才能确认合约的真实行为。
2. 审计检查清单
- 构造函数参数检查:审计时需重点检查合约构造函数中传入的地址是否与预期一致,是否存在被替换的风险。
- 多合约交互审计:当合约涉及多个外部合约调用时,需完整追踪调用链,确保每个环节的合约地址都经过验证。
- 部署交易溯源:建议使用区块浏览器(如 Etherscan)查看部署交易,确认
input data中的参数是否与源码一致。
3. 用户安全实践
- 拒绝盲信:即使项目方提供了完整的开源代码,仍需通过链上交易验证实际部署参数。
- 使用安全工具:可借助自动化审计工具检查合约间的调用关系,识别潜在的地址替换风险。
- 小额测试:在参与任何资金倍增或高收益项目前,先进行小额测试交易,观察实际执行路径。
总结
“移花接木”式攻击手法利用了开发者和用户对合约源码的信任,通过隐藏恶意代码实现了资金窃取。在区块链安全审计中,地址验证和交易溯源是防范此类攻击的关键。希望本期的技术分享能帮助大家提升对合约安全的理解,在实际开发与审计中避免陷入类似的陷阱。
注:本文参考自《Solidity by Example》,并结合查找币安全团队的实际审计经验进行深度解析。
本文由查找币安全团队整理发布