前言
昨天,一个风和日丽的中午,突传 cowSwap
被黑,作为奋斗在一线的辛勤打工仔,以必须先让老板跑了再说宗旨。自然是需要第一时间介入分析加各种预警。无奈手头工作太多,时间又紧迫,被迫祭出很久没用的快速分析大法来快速上手事件分析。本文分享一些用于快速分析事件的思路,希望大家下次遇到同样问题的时候可以及时快速响应。
How to 时间
首先,一般安全事件的第一来源都是一些安全公司。通过安全公司的简单介绍,其实就可以大概知道这件事情的来龙去脉。以这次为例。我第一时间接收到的消息是来自 PeckShield
, 从推文的介绍来看,这次被黑原因就是因为 CowSwap
的 GPV2Settlement
合约将 DAI
无限授权给了一个叫 SwapGuard
的合约,然后该合约得到授权后利用授权转走了 GPV2Settlement
的钱。OK 那么我们看到这段描述。我们就大概清楚了。是合约授权给合约导致了资金损失。所以到这里分析方向就变成了 为什么合约会授权给这个合约?。知道方向之后,很明显就自然能想到2个可能的解。分别是
-
合约有一个专门的函数用于设置授权的,可能
cowSwap
合约的管理员设置了一个恶意的合约但是不知道 -
合约存在任意调用接口,通过这个接口把代币授权给了外部合约
知道解了之后,我们就要开始分析究竟是哪一个。通过 Peckshield
官方,提供了一个 GPV2Settlement
授权给外部合约的调用图,是这样的
从图中我们不难发现其实这个授权操作的入口函数其实是在 GPV2Settlement
的 settle
函数中。到这里,如果不看代码,就可以得出推论 — 合约有一个专门的函数用于设置授权的,可能 cowSwap
合约的管理员设置了一个恶意的合约但是不知道,特别是在 settle
函数这个名字上,也会让人觉得:没错,就是这样。
但是做安全工作要的就是细致严谨,我们翻一下这个函数的逻辑, 如下:
function settle(
IERC20[] calldata tokens,
uint256[] calldata clearingPrices,
GPv2Trade.Data[] calldata trades,
GPv2Interaction.Data[][3] calldata interactions
) external nonReentrant onlySolver {
executeInteractions(interactions[0]);
(
GPv2Transfer.Data[] memory inTransfers,
GPv2Transfer.Data[] memory outTransfers
) = computeTradeExecutions(tokens, clearingPrices, trades);
vaultRelayer.transferFromAccounts(inTransfers);
executeInteractions(interactions[1]);
vault.transferToAccounts(outTransfers);
executeInteractions(interactions[2]);
emit Settlement(msg.sender);
}
从函数逻辑中,貌似继续维持上面轻而易举得出的结论好像也是可以的,就是一个设置函数嘛。。但是这里注意到了一个点,就是 vaultRelayer.transferFromAccounts(inTransfers);
。当你看到这个逻辑的时候同时结合这是一个 DEX Router
,就可以清楚的明白这里是一个兑换函数。
OK,那么到这里,先前得出的结论就已经被否决了,最后就只剩下–合约存在任意调用接口,通过这个接口把代币授权给了外部合约 这个结论了。再次分析函数逻辑,发现 executeInteractions
子函数。根据结论反推,这个子函数很可能可以执行任意代码,直接看逻辑
function executeInteractions(GPv2Interaction.Data[] calldata interactions)
internal
{
for (uint256 i; i < interactions.length; i++) {
GPv2Interaction.Data calldata interaction = interactions[i];
// To prevent possible attack on user funds, we explicitly disable
// any interactions with the vault relayer contract.
require(
interaction.target != address(vaultRelayer),
"GPv2: forbidden interaction"
);
GPv2Interaction.execute(interaction);
emit Interaction(
interaction.target,
interaction.value,
GPv2Interaction.selector(interaction)
);
}
}
分析了函数逻辑后,可以发现 executeInteractions
没有对 interactions
数据做任何检查。然后直接就到了 GPv2Interaction.execute(interaction);
。基本可以确定就是任意调用的问题了,但是其实这里还需要再去看 GPv2Interaction
的 execute
函数确认真的没有任何检查,结论才是靠谱的。但是这里为了节约篇幅,我直接告诉你就是靠谱的 😀
OK。一波操作下来,问题就已经快速定位完了,整个过程用了不到10分钟,多快好省。接下来就是报告给老板了 ;D
一些收尾问题
除了定位问题之外,还要评估影响和把整个攻击流程走通,上面虽然定位到了问题的原因,但其实还剩下2个问题
-
settle
函数是有调用者限制的 -
被黑的资金从那里来,为什么合约里头有钱?
篇幅原因,这里简单讲讲思路。
回答第一个问题有利于评估影响,即攻击会不会持续。答案也很简单,就是直接看这个 onlySolver
的限制,是 EOA
地址还是多签,EOA
的话比较难搞,因为涉及到私钥被黑的问题。如果是多签的话,就好点。这里 solver
是一个 EOA
地址,由于我之前有了解过一点 cowSwap
,结合一些前置知识,我知道这个 solver
其实只是用于转发交易兑换数据而已,所以其实只要 solver
服务下线,攻击就停止了。
回答第二个问题有助于走通整个攻击流程,即黑的是什么钱?答案的话其实需要一点联想。已知这是一个 DEX Router
合约,求解为什么 DEX Router
合约里头有钱?答案也是显而易见,就是兑换时收的手续费。怎么证明这个猜想呢?也很简单,直接去找经过合约的兑换交易,放到交易分析器里头,看看兑换子流程中资金从开始兑换到真正去到对应的 DEX (e.g. Uniswap)
的时候,资金是否存在减少,如果少了,那么就肯定是收了手续费了
结尾
以上就是一些快速分析的经验分享,晚上的时候,cowSwap
自己也发时候分析了,只能说和我的分析,大差不差了 😀
原文始发于微信公众号(蛋蛋的区块链笔记):分析基本法–如何快速上手一个被黑事件