Ethernaut-刷题小记(三)
Ethernaut-刷题小记(三)
1 |
|
代码很短,可以一眼就看到关键点在tx.origin != msg.sender
先了解一下这两个是什么东西
Solidity官方文档解释如下
msg.sender
(address
):消息的发送者(当前调用)tx.origin
(address
):交易的发送者(完整调用链)注:
msg
的所有成员的值,包括msg.sender
和msg.value
可以在每次 外部 函数调用中变化。 这包括对库函数的调用。
tx.origin
将返回最初发送交易的地址,而msg.sender
将返回发起external
调用的地址。
写个例子理解一下
1 |
|
第一个合约部署后调用函数如下
第二个合约部署后调用函数如下
可以看到msg.sender
已经改变,不再是原来的地址了
那么我们就可以利用这一点进行攻击
直接上攻击代码
1 |
|
攻击前
攻击后
已经拿到合约所有者。
修复方案
避免使用
tx.origin
进行权限验证- 直接使用
msg.sender
,这样合约调用不会绕过限制:
1
2
3
4function changeOwner(address _newOwner) public {
require(msg.sender == owner, "Only owner can change owner");
owner = _newOwner;
}- 直接使用
使用
onlyOwner
修饰符- 采用 OpenZeppelin 的 Ownable 库:
1
2
3
4
5
6
7import "@openzeppelin/contracts/access/Ownable.sol";
contract Telephone is Ownable {
function changeOwner(address _newOwner) public onlyOwner {
transferOwnership(_newOwner);
}
}
继续第五关
1 |
|
问题出在require(balances[msg.sender] - _value >= 0);
这里
uint256
是无符号整数,不能表示负数。
如果 msg.sender
余额不足,balances[msg.sender] - _value
可能会下溢,变成一个极大的正数,导致 require
语句仍然通过。也就是整数下溢。
简单来说就是这样:比如我有两个uint4:1011和0101,相加得到的理应是10000,但是由于uint4只有4位,所以最前面那个1会被省略,变成0000,这就和油量表一样,超过最大值9999.99就回到最小值0000.00一样
同时由于Solidity它这些uint不存在负数,所以就会出现这种溢出的情况,比如0000-1=1111的情况
所以直接向有效地址传21token就ok了
修复方法
1、修改 require
语句:
1 |
|
这样可以确保 msg.sender
余额充足,防止整数下溢。
2、使用0.8.0以上的solidity
3、导入Openzeppelin
的SafeMath.sol