使用Glider横扫漏洞-实战篇1(permit-order鉴权)
对于没开源的后面再写,主要是类似于训练数据集的形式,非使用glider,后续在讲。
glider也可以查询opcode,不过我还没测试过….
写在前面
glider有点类似于codeql。不过是属于链上版本的(CodeQL for Smart Contracts)。
doc:https://glide.gitbook.io/main/glider-ide
相对于etherscan原生的工具来说:
https://etherscan.io/searchcontract 根据合约代码查找
https://etherscan.io/find-similar-contracts 根据智能合约查找
有如下优点:
可编程 + 灵活的查询语言
处理所有部署的合约 (比如一些合约开源,可以通过其特征查找)
更加自动化+组合过滤:比如你研究了某个仓库的某个方法可能在某些情况下出现一些问题,那么完全可以只搜索这个单一方法的功能而非function名称,或者调用状态等等
同时:Glider 的 query 组件里有很多工具,比如对函数/事件/状态变量/修饰符等属性的支持,以及对继承、接口、struct 等语言结构的访问。这使得你可以组合复杂查询(比如 “找出所有合约中有某种 modifier +外部调用 +状态变量写入”的情况)。Etherscan 的搜索虽然有过滤器,但通常没有这么细粒度也不能作为程序化查询。
Use
没什么好写的,看文档就行
语法也比较简单,Python写的,相当于只用一个库,来写代码就行了。
实战
这里真实合约漏洞为例。
这里以spoke合约受攻击为例:
受害者合约代码:
https://app.sentio.xyz/contract/56/0xdf4fFDa22270c12d0b5b3788F1669D709476111E?t=1
攻击合约:
https://bscscan.com/address/0xe4b8d09c12d15f2934f21ca8eaa5dcb1464f9ed1
攻击者地址:
https://bscscan.com/address/0xc6e8210e47602860c97edc0bd7556641f048868e
真实的一起攻击事件:
tx: 0xcd345310e491195f0500d45d6987eaef342bae24390f4da4f7e6749b8105b4c3
这个漏洞调用栈比较少, 跟着调用流程debug:
可以看到先进行sponsorOrderUsingPermit2()函数操作。
sponsorOrderUsingPermit2()接收3个参数()signature不说了。
看传参:
1 | order:[ |
1 | permit:[ |
根据参数可以拿出来主要信息:
order.fromToken:”0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d” //USDC
permit.permitted.token:”0xe4b8d09c12d15f2934f21ca8eaa5dcb1464f9ed1” //攻击者合约,里面有erc20实现,所以被认为token。
看一下sponsorOrderUsingPermit2方法的实现:
1 | /// @inheritdoc ISpoke |
可以看出来该函数是一个用于创建订单的接口。
通过 Permit2(这里不详细解释了) 机制,从 order.fromAddress 转移 permit.permitted.token 的 order.fromAmount 到合约,创建订单并标记状态为 CREATED。
还可以跨链或同链代币交易场景。
1 | struct Order { |
根据攻击者输入结合合约代码可以看出:合约未验证 permit.permitted.token == order.fromToken
那么攻击流程就很明了了:
攻击者存入假代币
记录USDC
后续可以通过 refundOrder() 提取USDC
现在类比下整个流程:
- 你告诉银行(合约)要存 1000 USDC(order.fromToken)
- 但你通过签名(permit)实际给了 1000 张废纸(假代币)
- 银行记录你存了 1000 USDC(orderHashToStatus = CREATED)
- 后来你说“退款refundOrder ”,银行按记录退给你 1000 USDC,而没检查你实际存了什么钱
现在我们就去编写出来这一查询
查询
先定位合约的主要特征:
这里我们先做个严格的查询,后续在写宽松查询:
1.确定目标函数
比如合约名字为:sponsorOrderUsingPermit2funcs = Functions().with_name("sponsorOrderUsingPermit2").exec(20)
这样我们就可以只聚焦在实现中使用该函数名的部分,而不需要全网扫描所有函数。
2.理解漏洞机制
这个合约的功能实现的一个重点是使用了Permit2。
permitWitnessTransferFrom是 Uniswap 的 Permit2 合约的一个关键函数
比如允许用户通过签名授权代币从其地址转移到指定目标地址
1 | function permitWitnessTransferFrom( |
也就是说,它允许在无需批准 (approve) 的情况下,直接基于签名进行代币转移。
但是,如果合约没有正确验证 permit.permitted.token 与 order.fromToken 一致,攻击者就可以伪造一个签名,
把假代币地址传进去,导致“用假币换真币”的严重漏洞。
3.编写
限制查询
基于这个思路,我们编写一个查询脚本,去检测哪些合约在使用 permitWitnessTransferFrom 时没有进行 permit.permitted.token == order.fromToken 的验证。
这里的token_checks_patterns写的有很大问题,因为实际情况可能完全不是这样,仅表示一个例子。
因为需要完全严格检查permitted.token == order.fromToken的验证,并不只是require
1 | from glider import * |
运行结果
宽松查询
我们对于这个简单的查询来说可以:
不指定函数名:直接在全合约范围内查找 permitWitnessTransferFrom
用正则匹配函数名:匹配包含 Order、Permit、Swap 等常见关键词的函数名,例如 with_name_regex("^.*Order.*$")。(测试了有bug)
但仍保留对 permit.permitted.token == order.fromToken 这类“必须存在的校验”作为核心检测条件
具体实现就不写了,因为还有一些合约存在这种变式漏洞。
