总览
本文从源代码层面对Solidity编译器(0.5.8<=version<0.8.16)在ABIReencoding过程中,由于对固定长度的uint和bytes32类型数组的错误处理所导致的漏洞问题进行详细分析,并提出相关的解决方案及规避措施。
漏洞详情
ABI编码格式是用在用户或合约对合约进行函数调用,传递参数时的标准编码方式。具体可以参考Solidity官方关于ABI编码的详细表述。
在合约开发过程中,会从用户或其他合约传来的calldata数据中,获取需要的数据,之后可能会将获取的数据进行转发或emit等操作。限于evm虚拟机的所有opcode操作都是基于memory、stack和storage,所以在Solidity中,涉及到需要对数据进行ABI编码的操作,都会将calldata中的数据根据新的顺序按照ABI格式进行编码,并存储到memory中。
该过程本身并没有大的逻辑问题,但是当和Solidity的cleanup机制结合时,由于Solidity编译器代码本身的疏漏,就导致了漏洞的存在。
根据ABI编码规则,在去掉函数选择符之后,ABI编码的数据分为head和tail两部分。当数据格式为固定长度的uint或bytes32数组时,ABI会将该类型的数据都存储在head部分。而Solidity对memory中cleanup机制的实现是在当前索引的内存被使用后,将下一个索引的内存置空,以防止下一索引的内存使用时被脏数据影响。并且,当Solidity对一组参数数据进行ABI编码时,是按照从左到右的顺序进行编码!!
原Solana生态流支付协议Streamflow在Aptos主网上线:12月29日消息,原Solana生态流支付协议Streamflow宣布在Aptos主网正式上线。Streamflow表示,对Aptos的跨链扩展允许Streamflow向Aptos链上用户和项目提供代币归属服务和工资单。[2022/12/29 22:14:46]
为了便于后面的漏洞原理探索,考虑如下形式的合约代码:
contractEocene{
eventVerifyABI(bytes,uint);
functionverifyABI(bytescalldataa,uintcalldatab)public{
emitVerifyABI(a,b);//Event数据会按照ABI格式编码之后存储到链上
}
}
合约Eocene中verifyABI函数的作用,仅仅是将函数参数中的不定长bytesa和定长uintb进行emit。
这里需要注意,event事件也会触发ABI编码。这里参数a,b会编码成ABI格式后再存储到链上。
我们使用v0.8.14版本的Solidity对合约代码进行编译,通过remix进行部署,并传入verifyABI(,)。
首先,我们看一看对verifyABI(,)的正确编码格式:
数据:Solana链上NFT销售总额突破29亿美元:12月12日消息,据Cryptoslam数据显示,Solana链上NFT销售总额已突破29亿美元,截至目前为2,901,130,214美元,交易量达到15,468,355笔。当前Solana位列NFT销售额第三,仅次于以太坊和Ronin。[2022/12/12 21:39:01]
0x52cd1a9c//bytes4(sha3("verify(btyes,uint)"))
0000000000000000000000000000000000000000000000000000000000000060//indexofa
0000000000000000000000000000000000000000000000000000000000011111//b
0000000000000000000000000000000000000000000000000000000000022222//b
0000000000000000000000000000000000000000000000000000000000000002//lengthofa
0000000000000000000000000000000000000000000000000000000000000040//indexofa
0000000000000000000000000000000000000000000000000000000000000080//indexofa
Neodyme:solana代币借贷合同中的漏洞已修复:金色财经报道,据Neodyme的安全研究人员称,我们最近在 solana-program-library (SPL) 的代币借贷合同中发现了一个严重错误。存在风险的TVL总额约为26亿美元。其中一些价值被借出,其他一些低价值的代币在经济上是不可行的,但潜在的利润很容易达到数亿。
这个漏洞被修复了,dapps也及时更新,关闭了这个漏洞。我们相信最安全的代码是开源的,作为审计师,我们相信编写更好的代码的最好方法之一是了解漏洞。[2021/12/5 12:52:40]
0000000000000000000000000000000000000000000000000000000000000003//lengthofa
aaaaaa0000000000000000000000000000000000000000000000000000000000//a
0000000000000000000000000000000000000000000000000000000000000003//lengthofa
bbbbbb0000000000000000000000000000000000000000000000000000000000//a
如果Solidity编译器正常,当参数a,b被event事件记录到链上时,数据格式应该和我们发送的一样。让我们实际调用合约试试看,并对链上的log进行查看,如果想自己对比,可以查看该TX。
Antier Solutions通过开发加密友好型银行解决方案扩展其产品:区块链开发公司Antier Solutions已扩大服务范围,提供加密友好型银行开发解决方案,目标群体是寻求机会推出加密友好型银行的初创企业、银行和金融机构。
该公司提供的银行软件是一个白标解决方案,包含所有基本银行功能:IBAN账户、非接触式信用卡/借记卡、支付、交易、借贷和用户登录。希望建立其数字资产银行的初创企业、成熟的组织或金融机构可以利用此白标解决方案快速启动其银行,并为其客户提供更好的加密友好型银行解决方案。
Antier Solutions提供的白标银行软件无缝支持法定货币和加密货币。此外,将TextBit集成到银行平台中,可以通过短信在用户之间直接传输加密货币,而无需支付任何挖矿费或记住很长的钱包地址。(Bitcoin.com)[2020/12/16 15:21:58]
成功调用后,合约event事件记录如下:
!!震惊,紧跟b的,存储a参数长度的值被错误的删除了!!
0000000000000000000000000000000000000000000000000000000000000060//indexofa
0000000000000000000000000000000000000000000000000000000000011111//b
0000000000000000000000000000000000000000000000000000000000022222//b
动态 | 以太坊智能合约编程语言新版本Solidity0.5.11已发布:据github数据显示,针对以太坊智能合约的编程语言新版本Solidity0.5.11已发布。据悉该版本更新了诸多功能,并能帮助开发者修复错误编码。[2019/8/16]
0000000000000000000000000000000000000000000000000000000000000000//lengthofa??whybecome0??
0000000000000000000000000000000000000000000000000000000000000040//indexofa
0000000000000000000000000000000000000000000000000000000000000080//indexofa
0000000000000000000000000000000000000000000000000000000000000003//lengthofa
aaaaaa0000000000000000000000000000000000000000000000000000000000//a
0000000000000000000000000000000000000000000000000000000000000003//lengthofa
bbbbbb0000000000000000000000000000000000000000000000000000000000//a
为什么会这样?
正如我们前面所说,在Solidity遇到需要进行ABI编码的系列参数时,参数的生成顺序是从左至,具体对a,b的编码逻辑如下
Solidity先对a进行ABI编码,按照编码规则,a的索引放在头部,a的元素长度以及元素具体值均存放在尾部。
处理b数据,因为b数据类型为uint格式,所以数据具体值被存放在head部分。但是,由于Solidity自身的cleanup机制,在内存中存放了b之后,将b数据所在的后一个内存地址(被用于存放a元素长度的内存地址)的值置0。
ABI编码操作结束,错误编码的数据存储到了链上,SOL-2022-6漏洞出现。
在源代码层面,具体的错误逻辑也很明显,当需要从calldata获取定长bytes32或uint数组数据到memory中时,Solidity总是会在数据复制完毕后,将后一个内存索引数据置为0。又由于ABI编码存在head和tail两部分,且编码顺序也是从左至右,就导致了漏洞的存在。
具体漏洞的Solidity编译代码如下:
当源数据存储位置为Calldata,且源数据类型为ByteArray,String,或者源数组基础类型为uint或bytes32时进入ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup()
进入之后,会首先通过fromArrayType.isDynamicallySized()对源数据是否为定长数组来对源数据进行判断,只有定长数组才符合漏洞触发条件。
将isByteArrayOrString()判断结果传递给YulUtilFunctions::copyToMemoryFunction(),根据判断结果来确定是否在calldatacopy操作完成后,对后一个索引位置进行cleanup。
上诉几个约束条件结合,就只有位于calldata中的源数据格式为定长的uint或bytes32的数组复制到内存时才能触发漏洞。也即是漏洞触发的约束条件产生的原因。
由于ABI进行参数编码时,总是从左到右的顺序,考虑到漏洞的利用条件,我们必须要明白,必须在定长的uint和bytes32数组前,存在动态长度类型的数据被存储到ABI编码格式的tail部分,且定长的uint或bytes32数组必须位于待编码参数的最后一个位置。
原因很明显,如果定长的数据没有位于最后一个待编码参数位置,那么对后一内存位置的置0不会有任何影响,因为下个编码参数会覆盖该位置。如果定长数据前面没有数据需要被存储到tail部分,那么即便后一内存位置被置0也没有关系,因为该位置并不背ABI编码使用。
另外,需要注意的是,所有的隐式或显示的ABI操作,以及符合格式的所有Tuple,都会受到该漏洞的影响。
具体的涉及到的操作如下:
event
error
abi.encode*
returns//thereturnoffunction
struct//theuserdefinedstruct
allexternalcall
解决方案
当合约代码中存在上诉受影响的操作时,保证最后一个参数不为定长的uint或bytes32数组
使用不受漏洞影响的Solidity编译器
寻求专业的安全人员的帮助,对合约进行专业的安全审计
关于我们
AtEoceneResearch,weprovidetheinsightsofintentionsandsecuritybehindeverythingyouknowordon'tknowofblockchain,andempowereveryindividualandorganizationtoanswercomplexquestionswehadn'tevendreamedofbackthen.
Learnmore:Website|Medium|Twitter
我一直在思考,如今构建一个App已经不是什么难事,这时的我们就会面临这些问题:「价值会在哪里被捕获」、「是否应该建立护城河」、「如何建立护城河」、「如何应对种种竞争」……本文是我对这些问题的思考.
1900/1/1 0:00:00在2月24日至3月5日举办的ETHDenver创新节是全球最大、持续时间最长的以太坊活动之一。本次活动将面向以太坊和其他区块链协议爱好者、设计者和开发人员.
1900/1/1 0:00:00自Web3.0兴起以来,不知道大家有没有听说过tokenomics这个加密专有术语?Tokenomics指的是token与持有者及其各自生态系统的创建、管理和交互.
1900/1/1 0:00:00Pantos宣布其多链协议的公开测试版今日正式启动。Pantos是由Bitpanda打造的多链代币系统,开发人员和用户可使用测试版发送代币、包装所支持的链的原生代币,不久后将支持仅需数次点击即轻.
1900/1/1 0:00:00数字经济下,Web2.0与Web3.0的融合大势所趋,催生出一个无限增量市场。Web3.0成为必争的主战场,巨头们围绕规则、标准与话语权的抢夺展开竞争。加密行业的创业者们也迎来了一次机会窗口.
1900/1/1 0:00:00本文来自火星财经,星球日报经授权转载。蓝色巨人”IBM公司和全球反贫穷慈善项目GlobalCitizen向全球开发人员发出了一项挑战:使用区块链技术,彻底改变人道主义事业的捐赠方式.
1900/1/1 0:00:00