telegram筛选器 (www.ad6868.vip):以太坊彩票(www.326681.com)_智能合约平安审计入门篇 —— 抢跑
赌大小技巧(www.ad6868.vip)实时更新最新最有效的赌大小技巧登录网址、赌大小技巧备用网址、赌大小技巧最新网址、赌大小技巧手机网址、赌大小技巧管理网址、赌大小技巧会员网址。提供赌大小技巧APP下载,赌大小技巧APP包含赌大小技巧代理登录线路、赌大小技巧会员登录线路、赌大小技巧信用网开户、赌大小技巧现金网开户、赌大小技巧会员注册、赌大小技巧线上投注等业务。
靠山概述
在上篇文章中我们领会了合约中隐藏的恶意代码,本次我们来领会一个非经常见的攻击手法 —— 抢跑。
前置知识
提到抢跑,人人第一时间想到的一定是田径竞赛,在田径运动中各个选手的体能素质险些相同,起步越早的人获得第一名的概率越大。那么在以太坊中是若何抢跑的呢?
想领会抢跑攻击必须先领会以太坊的生意流程,我们通过下面这个发送生意的流程图来领会以太坊上一笔生意发出后履历的流程:
可以看到图中一笔生意从署名到被打包一共会履历 7 个阶段:
1. 使用私钥对生意内容署名;
2. 选择 Gas Price;
3. 发送署名后的生意;
4. 生意在各个节点之间广播;
5. 生意进入生意池;
6. 矿工取出 Gas Price 高的生意;
7. 矿工打包生意并出块。
生意送出之后会被丢进生意池里,守候被矿工打包。矿工从生意池中取出生意举行打包与出块。凭证 Eherscan 的数据,现在区块的 Gas 限制在 3000 万左右这是一个动态调整的值。若以一笔基础生意 21,000 Gas 来盘算,则现在一个以太坊区块可以容纳约 1428 笔生意。因此当生意池里的生意量大时,会有许多生意没设施即时被打包而滞留在池子中守候。这里就衍生出了一个问题,生意池中有那么多笔生意,矿工先打包谁的生意呢?
矿工节点可以自行设置参数,不外大多数矿工都是根据手续费的若干排序。手续费高的会被优先打包出块,手续费低的则需要等前面手续费高的生意所有被打包完才气被打包。固然进入生意池中的生意是源源不停的,不管生意进入生意池时间的先后,手续费高的永远会被优先打包,手续费过低的可能永远都不会被打包。
那么手续费是怎么来的呢?
我们先看以太坊手续费盘算公式:
Tx Fee(手续费)= Gas Used(燃料用量)* Gas Price(单元燃料价钱)
其中 Gas Used 是由系统盘算得出的,Gas Price 是可以自界说的,以是最终手续费的若干取决于 Gas Price 设置的若干。
举个例子:
例如 Gas Price 设置为 10 GWEI,Gas Used 为 21,000(WEI 是以太坊上最小的单元 1 WEI = 10^-18 个 Ether,GWEI 则是 1G 的 WEI,1 GWEI = 10^-9 个 Ether)。因此,凭证手续费盘算公式可以算脱手续费为:
10 GWEI(单元燃料价钱)* 21,000(燃料用量)= 0.00021 Ether(手续费)
在合约中我们常见到 Call 函数会设置 Gas Limit,下面我们来看看它是什么器械:
Gas Limit 可以从字面意思明晰,就是 Gas 限制的意思,设置它是为了示意你愿意花若干数目的 Gas 在这笔生意上。当生意涉及庞大的合约交互时,不太确定现实的 Gas Used,可以设置 Gas Limit,被打包时只会收取现实 Gas Used 作为手续费,多给的 Gas 会退返回来,固然若是现实操作中 Gas Used > Gas Limit 就会发生 Out of gas,造成生意回滚。
固然,在现实生意中选择一个合适的 Gas Price 也是有讲求的,我们可以在 ETH GAS STATION 上看到实时的 Gas Price 对应的打包速率:
由上图可见,当前最快的打包速率对应的 Gas Price 为 2,我们只需要在发送生意时将 Gas Price 设置为 >= 2 的值就可以被尽快打包。
好了,到这里信托人人已经可以大致猜出抢跑的攻击方式了,就是在发送生意时将 Gas Price 调高从而被矿工优先打包。下面我们照样通过一个合约代码来带人人领会抢跑是若何完成攻击的。
合约示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract FindThisHash {
bytes32 public constant hash =
0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2;
constructor() payable {}
function solve(string memory solution) public {
require(hash == keccak256(abi.encodePacked(solution)), "Incorrect answer");
(bool sent, ) = msg.sender.call{value: 10 ether}("");
require(sent, "Failed to send Ether");
}
}
攻击剖析
通过合约代码可以看到 FindThisHash 合约的部署者给出了一个哈希值,任何人都可以通过 solve() 提交谜底,只要 solution 的哈希值与部署者的哈希值相同就可以获得 10 个以太的奖励。我们这里清扫部署者自己拿取奖励的可能。
,,,,telegram筛选器(www.ad6868.vip)实时更新最新最有效的telegram筛选器登录网址、telegram筛选器备用网址、telegram筛选器最新网址、telegram筛选器手机网址、telegram筛选器管理网址、telegram筛选器会员网址。提供telegram筛选器APP下载,telegram筛选器APP包含telegram筛选器代理登录线路、telegram筛选器会员登录线路、telegram筛选器信用网开户、telegram筛选器现金网开户、telegram筛选器会员注册、telegram筛选器线上投注等业务。www.u-healer.com采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。
我们照样请出老同伙 Eve(攻击者) 看看他是若何使用抢跑攻击拿走本该属于 Bob(受害者)的奖励的:
1. Alice(合约部署者)使用 10 Ether 部署 FindThisHash 合约;
2. Bob 找到哈希值为目的哈希值的准确字符串;
3. Bob 挪用 solve("Ethereum") 并将 Gas 价钱设置为 15 Gwei;
4. Eve 正在监控生意池,守候有人提交准确的谜底;
5. Eve 看到 Bob 发送的生意,设置比 Bob 更高的 Gas Price(100 Gwei),挪用 solve("Ethereum");
6. Eve 的生意先于 Bob 的生意被矿工打包;
7. Eve 赢得了 10 个以太币的奖励。
这里 Eve 的一系列操作就是尺度的抢跑攻击,我们这里就可以给以太坊中的抢跑下一个界说:抢跑就是通过设置更高的 Gas Price 来影响生意被打包的顺序,从而完成攻击。
那么这类攻击该若何制止呢?
修复建议
在编写合约时可以使用 Commit-Reveal 方案:
https://medium.com/swlh/exploring-commit-reveal-schemes-on-ethereum-c4ff5a777db8
Solidity by Example 中提供了下面这段修复代码,我们来看看它是否可以完善地防御抢跑攻击。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/Strings.sol";
contract SecuredFindThisHash {
// Struct is used to store the commit details
struct Commit {
bytes32 solutionHash;
uint commitTime;
bool revealed;
}
// The hash that is needed to be solved
bytes32 public hash =
0x564ccaf7594d66b1eaaea24fe01f0585bf52ee70852af4eac0cc4b04711cd0e2;
// Address of the winner
address public winner;
// Price to be rewarded
uint public reward;
// Status of game
bool public ended;
// Mapping to store the commit details with address
mapping(address => Commit) commits;
// Modifier to check if the game is active
modifier gameActive() {
require(!ended, "Already ended");
_;
}
constructor() payable {
reward = msg.value;
}
/*
Commit function to store the hash calculated using keccak256(address in lowercase + solution + secret).
Users can only commit once and if the game is active.
*/
function commitSolution(bytes32 _solutionHash) public gameActive {
Commit storage commit = commits[msg.sender];
require(commit.commitTime == 0, "Already committed");
commit.solutionHash = _solutionHash;
commit.commitTime = block.timestamp;
commit.revealed = false;
}
/*
Function to get the commit details. It returns a tuple of (solutionHash, commitTime, revealStatus);
Users can get solution only if the game is active and they have committed a solutionHash
*/
function getMySolution() public view gameActive returns (bytes32, uint, bool) {
Commit storage commit = commits[msg.sender];
require(commit.commitTime != 0, "Not committed yet");
return (commit.solutionHash, commit.commitTime, commit.revealed);
}
/*
Function to reveal the commit and get the reward.
Users can get reveal solution only if the game is active and they have committed a solutionHash before this block and not revealed yet.
It generates an keccak256(msg.sender + solution + secret) and checks it with the previously commited hash.
Front runners will not be able to pass this check since the msg.sender is different.
Then the actual solution is checked using keccak256(solution), if the solution matches, the winner is declared,
the game is ended and the reward amount is sent to the winner.
*/
function revealSolution(
string memory _solution,
string memory _secret
) public gameActive {
Commit storage commit = commits[msg.sender];
require(commit.commitTime != 0, "Not committed yet");
require(commit.commitTime < block.timestamp, "Cannot reveal in the same block");
require(!commit.revealed, "Already commited and revealed");
bytes32 solutionHash = keccak256(
abi.encodePacked(Strings.toHexString(msg.sender), _solution, _secret)
);
require(solutionHash == commit.solutionHash, "Hash doesn't match");
require(keccak256(abi.encodePacked(_solution)) == hash, "Incorrect answer");
winner = msg.sender;
ended = true;
(bool sent, ) = payable(msg.sender).call{value: reward}("");
if (!sent) {
winner = address(0);
ended = false;
revert("Failed to send ether.");
}
}
}
首先可以看到修复代码中使用了却构体 Commit 纪录玩家提交的信息,其中:
commit.solutionHash = _solutionHash = keccak256(玩家地址 + 谜底 + 密码)【纪录玩家提交的谜底哈希】
commit.commitTime = block.timestamp 【纪录提交时间】
commit.revealed = false 【纪录状态】
下面我们看这个合约是若何运作的:
1. Alice 使用十个以太部署 SecuredFindThisHash 合约;
2. Bob 找到哈希值为目的哈希值的准确字符串;
3. Bob 盘算 solutionHash = keccak256 (Bob’s Address + “Ethereum” + Bob’s secret);
4. Bob 挪用 commitSolution(_solutionHash),提交刚刚算出的 solutionHash;
5. Bob 在下个区块挪用 revealSolution("Ethereum",Bob's secret) 函数,传入谜底和自己设置的密码,领取奖励。
这里我们看下这个合约是若何制止抢跑的,首先在第四步的时刻,Bob 提交的是(Bob’s Address + “Ethereum” + Bob’s secret)这三个值的哈希,以是没有人知道 Bob 提交的内容到底是什么。这一步还纪录了提交的区块时间而且在第五步的 revealSolution() 中就先检查了区块时间,这是为了防止在统一个区块开奖被抢跑,由于挪用 revealSolution() 时需要传入明文谜底。最后使用 Bob 输入的谜底和密码验证与之条件交的 solutionHash 哈希是否匹配,这一步是为了防止有人不走 commitSolution() 直接去挪用 revealSolution()。验证乐成后,检查谜底是否准确,最后发放奖励。
以是这个合约真的完善地防止了 Eve 抄谜底吗?
Of course not!
咋回事呢?我们看到在 revealSolution() 中仅限制了 commit.commitTime < block.timestamp ,以是假设 Bob 在第一个区块提交了谜底,在第二个区块立马挪用 revealSolution("Ethereum",Bob's secret) 并设置 Gas Price = 15 Gwei Eve ,通过监控生意池拿到谜底,拿到谜底后他立刻设置 Gas Price = 100 Gwei ,在第二个区块中挪用 commitSolution() ,提交谜底并组织多笔高 Gas Price 的生意,将第二个区块填满,从而将 Bob 提交的生意挤到第三个区块中。在第三个区块中以 100 Gwei 的 Gas Price 挪用 revealSolution("Ethereum",Eve's secret) ,获得奖励。
那么问题来了,若何才气有用地防止此类攻击呢?
很简朴,只需要设置 uint256 revealSpan 值并在 commitSolution() 中检查 require(commit.commitTime + revealSpan >= block.timestamp, "Cannot commit in this block");,这样就可以防止 Eve 抄谜底的情形。然则在开奖的时刻,照样无法防止提交过谜底的人争先领奖。
另外尚有一点,本着代码严谨性,修复代码中的 revealSolution() 函数执行完后并没有将 commit.revealed 设为 True,虽然这并不会影响什么,然则在编写代码的时刻照样建议养成优越的编码习惯,执行完函数逻辑后将开关设置成准确的状态。
查看更多,
21点赔率(www.ad6868.vip)实时更新最新最有效的21点赔率登录网址、21点赔率备用网址、21点赔率最新网址、21点赔率手机网址、21点赔率管理网址、21点赔率会员网址。提供21点赔率APP下载,21点赔率APP包含21点赔率代理登录线路、21点赔率会员登录线路、21点赔率信用网开户、21点赔率现金网开户、21点赔率会员注册、21点赔率线上投注等业务。
版权声明
本文仅代表作者观点,
不代表本站欧博网址的立场。
本文系作者授权发表,未经许可,不得转载。
评论