本文讨论了重入攻击在DeFi领域的风险及其挑战,并提出“运行时保护”作为一种有效的安全措施。Artela通过Aspect Programming为EVM区块链引入运行时保护,旨在确保合约的执行结果与预期一致,从而防范重入攻击。该方法强调了在智能合约执行后的保护措施,朝着更高的DeFi安全性迈出了一步。
原文链接:
Eliminate Reentrancy Attacks with On-chain Runtime Protection \ \ TL; DR\ \ medium.com
尽管重入攻击是一个众所周知的问题,并且出现了许多风险控制措施,但在过去的两年中,涉及此类攻击的安全事件仍在不断发生:
目前,防范重入攻击的重点 集中在智能合约的源代码层面,措施包括集成OpenZeppelin的ReentrancyGuard,以及对合约逻辑代码进行安全审计,以避免预定义的安全隐患。
这种方法被称为“白盒”解决方案,旨在在源代码层面规避漏洞,以最小化逻辑错误。然而, 其主要挑战在于无法防御未知隐患。
将合约从源代码“转化”为实际runtime是个具有挑战性的过程。每一步可能为开发人员带来无法预料的问题,而合约源代码本身可能无法全面涵盖所有潜在情况。在Curve的案例中, 由于编译器问题,即使协议源代码是正确的,最终运行结果与协议的预期设计之间仍可能存在差异。
仅仅依靠协议在源代码和编译层面的安全性是不足够的。即使源代码看起来毫无瑕疵,由于编译器问题,漏洞仍可能意外出现。
与现有的风险控制措施集中在协议源代码层面并在运行之前生效不同, 运行时保护涉及协议开发人员编写运行时保护规则和操作,以处理运行时的未预料情况。 这有助于对运行时执行结果进行实时评估及应对。
运行时保护在增强DeFi安全性方面至关重要,是现有安全措施的重要补充。 通过以“黑盒”方式保护协议,它通过确保最终运行结果与协议预期设计相一致来增强安全性,而无需直接干涉合约代码执行。
不幸的是, EVM设计不支持在链上实现运行时保护,因为智能合约无法访问完整的运行时上下文。
如何克服这一挑战?我们认为以下先决条件是必要的:
EVM在解决上述挑战中目前面临限制,难以容纳更多创新。在模块化区块链的范式下,执行层需要探索go beyond EVM的突破。
Artela的思路是EVM + native extension,通过构建EVM的WASM原生扩展层以实现go beyond EVM。
我们推出了Aspect Programming,这是支持Artela区块链的一种编程框架,支持在区块链上进行原生扩展。
Aspect是可编程的原生扩展模块,用于在运行时动态集成自定义功能到区块链中,作为智能合约的模块化补充,增强链上功能性。
Aspect的特性是能够访问区块链基础层的系统级API,并在交易生命周期的各个切点(Join Point)添加扩展逻辑。智能合约可以绑定指定的Aspect以触发扩展功能。当交易调用智能合约时,该交易也会经由与该合约关联的Aspect处理。
Aspect可以记录每个函数调用的执行状态,并防止在回调函数执行期间发生重入。当在回调函数执行期间发生重入调用时,Aspect会检测到并立即回撤该交易,防止攻击者利用重入漏洞。通过这种方法,Aspect有效地消除了重入攻击,确保智能合约的安全性和稳定性。
Aspect实现运行时保护的关键属性:
本章我们探讨如何在链上实现Aspect的运行时保护。👇👇
可以在“ preContractCall”和“ postContractCall”的切点(Join Point)中部署一个实际的“合约保护意图”Aspect,以防止重入攻击。
💡💡
preContractCall: 在跨合约调用执行之前触发
postContractCall: 在跨合约调用执行后触发
为进行重入保护,我们的目标是在调用结束之前阻止合约重入。通过Aspect,我们可以通过在交易生命周期的切点处添加特定逻辑来实现这一目标。
在“ preContractCall”切点中,Aspect监控合约调用堆栈。如果在调用堆栈中有任何重复调用(这意味着我们锁定的调用中出现了意外重入),Aspect将会回撤该调用。
/**
* preContractCall is a join-point which will be invoked before the contract call is executed.
*
* @param ctx context of the given join-point
* @return result of Aspect execution
*/
preContractCall(ctx: PreContractCallCtx): AspectOutput {
// 获取当前调用合约的方法。
let currentCallMethod = utils.praseCallMethod(ctx.currInnerTx!.data);
// 定义不易受重入影响的函数。
// - 0xec45ef89: add_liquidity的函数签名
// - 0xe446bfca: remove_liquidity的函数签名
let lockMethods = ["0xec45ef89", "0xe446bfca"];
// 验证当前方法是否在不易受重入影响的函数范畴内。
if (lockMethods.includes(currentCallMethod)) {
// 从上下文中检索调用栈,表示当前合约方法调用路径上的所有合约调用。
let rawCallStack = ctx.getCallStack();
// 创建一个链表以封装调用栈的原始数据。
let callStack = utils.wrapCallStack(rawCallStack);
// 检查当前调用路径中是否已经存在非重入的方法。
callStack = callStack!.parent;
while (callStack != null) {
let callStackMethod = utils.praseCallMethod(callStack.data);
if (lockMethods.includes(callStackMethod)) {
// 如果是,回撤该交易。
ctx.revert("illegal transaction: reentrancy attack");
}
callStack = callStack.parent;
}
}
return new AspectOutput(true);
}
我们编写了Curve模拟合约并复刻重入攻击,以更易理解的方式重现了这个过程。合约代码如下 👇
event AddLiquidity:
excuted: uint256
event RemoveLiquidity:
excuted: uint256
deployer: address
@external
def __init__():
self.deployer = msg.sender
@external
@view
def isOwner(user: address) -> bool:
return user == self.deployer
@external
@nonreentrant('lock')
def add_liquidity():
log AddLiquidity(1)
@external
@nonreentrant('lock')
def remove_liquidity():
raw_call(msg.sender, b"")
log RemoveLiquidity(1)
可以看到,上述合约的 add_liquidity
和 remove_liquidity
都由同一个重入锁 lock
进行了保护,这意味着如果重入保护正常工作,无法通过改锁重入被保护函数(例如,在 remove_liquidity
中调用 add_liquidity
)。
使用 vyper
编译器 0.2.15
、 0.2.16
或 0.3.0
(这些版本存在已知的重入保护问题)编译上述合约。
然后,我们部署上述受害者合约,并使用以下合约对其进行攻击 👇
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
interface CurveContract {
event AddLiquidity(uint256 executed);
event RemoveLiquidity(uint256 executed);
function add_liquidity() external;
function remove_liquidity() external;
}
contract Attack {
CurveContract public curve;
constructor(address _curveContract) {
curve = CurveContract(_curveContract);
}
function attack() external payable {
curve.remove_liquidity();
}
fallback() external {
curve.add_liquidity();
}
}
模拟实际攻击,此合约的 attack 方法尝试通过其 fallback 函数从 remove_liquidity 方法重入 add_liquidity。如果实际发生了重入,可在receipt中观察到在 RemoveLiquidity 事件之前记录了一个 AddLiquidity 事件。
transaction receipt -> {
"txHash": ...,
"events": [{\
"topic": "AddLiquidity",\
...\
}, {\
"topic": "RemoveLiquidity",\
...\
}]
}
现在让我们使用Aspect来保护受攻击的合约。在执行以下操作之前,请先完成以下步骤:
如果对Aspect操作不熟悉,可以首先查看我们的 开发者指南 先行了解。
完成上述操作后,现在让我们尝试再次调用 attack
方法,以检查操作是否会成功进行。
从动图中我们可以看到,重入交易已经被revert,这意味着我们的Aspect正在成功保护受害合约免受重入攻击。
最近对Curve的攻击再次说明了没有100%完全安全的协议。仅仅将重心放在协议的源代码和编译级别的安全性上是不足够的。即使源代码看起来毫无瑕疵,由于编译器问题,漏洞仍然可能意外出现。
为了增强DeFi的安全性,运行时保护变得至关重要。通过以“黑盒”方式保护协议,确保协议的执行与其预期设计一致,可以有效地防止运行时的重入攻击。
我们复刻了Curve合约并完全模拟了其近期的重入攻击,并以更易理解的方式再现了整个过程。利用Aspect编程作为一种新方法,实现链上运行时保护,我们逐步展示了如何用Aspect保护受害合约。我们的目标是帮助根除Curve等DeFi协议可能遭受的重入攻击,从而增强整个DeFi领域的安全性。
通过Aspect Programming,开发人员不仅可以在安全领域实现链上运行时保护,还能实现诸如意图、JIT和链上自动化等前所未有的创新用例。除此之外,这个以Cosmos SDK为基础的通用框架将不仅支持Artela区块链,更能支持其他区块链构建基于自己执行层的native extension。
关注Artela的 Twitter 并加入我们的 中文开发者社区,以便及时了解Artela的最新动态。也可以在我们的 网站 上了解更多关于Artela的信息
- 原文链接: medium.com/@artela_chine...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!