确认过眼神,地址不是对的人—— 权限验证错误 | 漏洞分析连载之五

合约安全:地址漏洞解析

640?wx_fmt=jpeg

安全,区块链领域举足轻重的话题,为什么一行代码能瞬间蒸发几十亿市值?合约底层函数的使用不当会引起哪些漏洞?在合约中发送以太币存在哪些风险?权限验证错误又有哪些?


「区块链大本营」携手「成都链安科技」团队重磅推出「合约安全漏洞解析连载」,以讲故事的方式,带你回顾区块链安全走过的历程;分析漏洞背后的玄机。让开发者在趣味中学习,写出更加牢固的合约,且防患于未然。


当然,这些文章并不是专为开发者而作的,即使你不是开发者,当你读完本连载,相信再有安全问题爆出时,你会有全新的理解。



引子:横看成岭侧成峰,远近高低各不同。不识庐山真面目,只缘身在此山中。

—— 《题西林壁》苏轼


上回说到 :

底层函数调用险象环生,

外部功能慎用防患未然,

真假难辨黑客诡计多端,

完善规则杜绝内忧外患。


没看过的请戳:合约安全漏洞连载之四


本回咱们聊聊:

地址恢复功亏一篑,

身份判断轻虑浅谋。

逻辑补全自圆其说,

原理辨析帷幄运筹。


智能合约作为以太坊上各种加密数字货币的基础,承载着巨大的经济利益。放眼以太坊的繁忙景象,日交易频率在50-75万次之间,而五月和二月更有两日交易次数高达100万次。合约的调用在整个以太坊生态系统犹如战场上穿梭的子弹,不计其数。可见其地位举足轻重,功能五花八门,但万变不离其宗的是,每个合约都有一个共性,会继承一个地址对象,也就是说合约的基础是地址。回顾前四期漏洞分析,我们了解攻击者使出了浑身解数从合约的各个部分试图不劳而获,而从合约的地址漏洞入手,釜底抽薪自然也会成为攻击手段之一。

640?wx_fmt=jpeg

下面我们就来聊聊,与地址有关的tx.origin变量和ecrecover()函数相关漏洞。



一言以蔽之


Solidity作为以太坊的官方语言,将地址定为20个字节,160位。所有合约都有一个地址对象,也可以对其他地址的代码进行调用。地址类型的成员有我们上一期讲到的call()和delegatecall(),这两个函数相关的漏洞请见右方链接:偷天换日合约易主,地址变脸移花接木——底层函数误用漏洞 | 漏洞分析连载之四 。不同的合约地址代表了不同的合约,也代表了他们所拥有的权限。所以合约地址就相当于合约的“身份证”。


本期相关两个名词专业的解释是, tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址ecrecover()是内嵌的函数,可以用来恢复签名公钥,传值正确的情况下,可以利用这个函数来验证地址。


通俗来说,tx.origin是整个交易过程最初的那个合约拥有者。当这个合约A直接调用合约B的时候,拥有者也是中间人没错,都是合约A。


但是当中间多出了一个合约C之后,合约A调用合约C,合约C调用合约B,这时中间人是C,但是tx.origin是合约A。


利用tx.origin的漏洞可以比喻为:


小明未满十八岁,但对手中的游戏爱不释手,但是游戏方为了限制未成年人的游戏时间,只有身份证号验证已满十八周岁的玩家才能无限制畅玩。于是小明使用长辈的身份证号进行防沉迷验证。这样,本来是游戏方对小明身份的验证,变成了对其长辈身份的验证,验证结果予以通过,给予小明他不应有的权限和利益。

640?wx_fmt=jpeg

至于ecrecover函数的问题,上面说到传值正确的情况下,使用是没有问题的,但普遍存在的问题是,传值如果异常,没有进行相应的判定和排除。


我们来具体分析案例进行代码层面的剖析。



深度分析


1. tx.origin使用错误


漏洞分析


tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址。在智能合约中使用此变量进行身份验证会使合约容易受到类似网络钓鱼的攻击。有关进一步阅读,请参阅StackExchangeQuestion, PeterVenesses博客Solidity-tx.origin攻击


对如下案例合约进行分析:

640?wx_fmt=jpeg

该合约有三个函数:constructor构造函数,指定合约owner;fallback函数,通过添加payable关键字以便接收用户转账;withdrawAll函数,对tx.origin进行判断,如果tx.origin是owner,则将合约地址所拥有的ether发送到_recipient中。


现在,一个攻击者创建了下列合约:

640?wx_fmt=jpeg

攻击者诱使原合约(Phishable.sol)的owner发送ether到攻击合约(POC.sol)地址,然后调用攻击合约的fallback函数,执行attack()函数,此时phOwner == msg.sender,将会调用原合约的withdrawAll()函数,程序执行进入原合约,此时msg.sender是攻击合约的地址,tx.origin是最初发起交易的地址,即原合约的owner,require(tx.origin == owner);条件满足,_recipient.transfer(this.balance);可以执行,即将原合约地址里的ether转给攻击者。


漏洞修复


tx.origin不应该用于智能合约的授权。这并不是说永远不应该使用tx.origin变量。它在智能合约中确实有一些合法的用例。例如,如果想要拒绝外部合约调用当前合约,他们可以通过require(tx.origin == msg.sender)实现。这可以防止使用中间合约来调用当前合约[2]


  • 参考链接:https://blog.sigmaprime.io/solidity-security.html#tx-origin


2.ecrecover 未作0地址判断


漏洞分析


keccak256() 和 ecrecover()都是内嵌的函数,keccak256() 可以用于计算公钥的签名,ecrecover()可以用来恢复签名公钥。传值正确的情况下,可以利用这两个函数来验证地址。


我们来看一个案例合约:

640?wx_fmt=jpeg

这个合约看似正常,但思索再三,如果ecrecover传入错误参数(例如_v = 29,),函数返回0地址。如果合约函数传入的校验地址也为零地址,那么将通过断言,导致合约逻辑错误。

640?wx_fmt=jpeg 

函数transferProxy中,如果传入的参数_from为0,那么ecrecover函数因为输入参数错误而返回0值之后,if判断将通过,从而导致合约漏洞[2]。

640?wx_fmt=jpeg 

函数decode()传入经过签名后的数据,用于验证返回地址是否是之前用于签名的私钥对应的公钥地址[3]。以太坊提供了web3.eth.sign方法来对数据生成数字签名。上面的签名数据可以通过下面的js代码获得:

640?wx_fmt=jpeg 

js代码运行结果如下:

640?wx_fmt=jpeg

漏洞修复


对0x0地址做过滤,例如:

640?wx_fmt=jpeg

参考资料

  • transferProxy-keccak256

  • approveProxy-keccak256



高枕无忧居安思危

细心的朋友会发现,我们这一期并没有相应的事件回顾,那是因为早在2016年6月25日就有用户在github上向Solidity官方反应存在tx.origin的概念混淆,并建议移除tx.origin这个变量。

640?wx_fmt=jpeg


事关地址权限验证,而且theDAO事件风波未平(发生于6月17日),大量的讨论者参与了进来。究竟应不应该停止使用这个变量呢?我们在漏洞修复的环节中已经提到,如果想要拒绝外部合约调用当前合约,可以通过require(tx.origin ==msg.sender)实现。这可以防止使用中间合约来调用当前合约,这可以防止外部函数调用时产生的风险,我们在上一期已经讨论过相关的漏洞,详情请见右方链接:偷天换日合约易主,地址变脸移花接木——底层函数误用漏洞 | 漏洞分析连载之四


于是,有用户新提出了一个使用tx.origin产生警告的建议。

640?wx_fmt=jpeg

官方警惕性非常高,及时处理了这个问题,于是这个漏洞暂时还没有被利用于恶性攻击的历史事件。

 

那是不是可以说,这个漏洞并不重要呢?


目前合约的发展趋势正在发生变化,重心渐渐向以太坊游戏倾斜。以太坊游戏以娱乐和休闲为外衣,对数字货币的交易进行了整合和包装。但是游戏智能合约的漏洞依然层出不穷,不但有新型的非合约漏洞,而且还存在已经报道出的一些漏洞。更有甚者,故意暴露出看似明显的合约或机制缺陷,引诱投机倒把的玩家或者黑客,但暗中附加套路,玩起了“螳螂捕蝉黄雀在后”。这种新型的诈骗合约我们称为“蜜罐”。一些已知的缺陷通过黑客的修饰摇身一变,成为新型的骗局合约,例如近期有相关安全公司报道的名为QUESTION (中文含义:问题)的游戏合约,利用的是etherscan.io的缺陷,明修栈道,暗度陈仓,卷走玩家的钱财。



面币思过

 

无论是地址验证的欺骗,还是蜜罐合约的引诱,不怀好意的人总是瞄准了深陷币圈投资者的口袋。成都链安团队提醒玩家和投资者们提高安全意识,以及对智能合约的了解程度,切不可盲目跟风。我们从这个早期漏洞可以受到的启发是,要辨析一些容易混淆的原理之后,才能认清某些不易察觉的陷阱和漏洞,提高警惕,理智投资,稳健操作,会当凌绝顶,一览众山小。



杨霞  

成都链安科技CEO,创始人。电子科技大学副教授,最早研究区块链形式化验证的专家。一直为航空航天、军事领域提供形式化验证服务。主持国家核高基、装发重大软件课题等近10项国家课题。CC国际安全标准成员、CCF区块链专委会委员。发表学术论文30多篇,申请20多项专利。



本文引用:

[1] Solidity Security https://blog.sigmaprime.io/solidity-security.html#tx-origin

[2]  ERC20 Token 合约安全风险问题汇总transferProxy-keccak256

[3]  ERC20 Token 合约安全风险问题汇总approveProxy-keccak256


相关阅读:


640?wx_fmt=jpeg

大力戳↑↑↑  加入区块链大本营读者7群

(群满加微信 qk15732632926 入群)

(内容转载请联系微信:qk15732632926)

(商务合作请联系微信:fengyan-1101)



640?wx_fmt=png



640?wx_fmt=gif



了解更多区块链技术及应用内容

敬请关注:

640?wx_fmt=gif

### 电源电路工作原理分析 该电路是一个基于MOSFET和电感的DC-DC降压(Buck)转换器,其输入为+20V,输出目标为+5V,并通过MCU的PWM信号进行控制。其核心原理是利用电感储能和MOSFET开关控制,将高电压转换为低电压,同时通过电容滤波以获得稳定的输出电压。 #### 输入与输出路径 输入电压+20V经过二极管D1后进入电感L1,再通过MOSFET的漏极连接至负载Rload,最终通过电容Cout滤波后输出稳定的5V电压。其中,D1通常为续流二极管,用于在MOSFET关断时为电感L1提供回路,防止反向电压损坏MOSFET,这一功能在感性负载中尤为重要[^2]。 #### MOSFET与PWM控制 MOSFET作为开关器件,其栅极连接至MCU的PWM输出引脚。MCU通过调节PWM信号的占空比控制MOSFET的导通与关断时间,从而控制输出电压的大小。具体而言,当MOSFET导通时,输入电压+20V通过L1向负载供电并储能;当MOSFET关断时,L1通过D1释放能量继续供电,使输出电压保持稳定。 该结构属于非隔离型Buck电路,其输出电压由下式近似决定: ``` Vout ≈ Vin × Duty Cycle ``` 其中,Vin为输入电压,Duty Cycle为PWM占空比。例如,若占空比为25%,则理论上输出电压为5V(20V × 0.25 = 5V)。 #### 电感L1的作用 电感L1在Buck电路中起储能和续流作用。在MOSFET导通期间,电感吸收能量并存储于磁场中;在MOSFET关断期间,电感释放能量,维持负载电流连续,从而实现平滑的输出电压波形。电感值的选择影响输出电压的稳定性与响应速度。 #### 输出电容Cout的作用 电容Cout用于滤除输出电压中的高频纹波,提高电压稳定性。较大的电容值可有效降低输出电压纹波,但会增加响应时间。因此,需根据负载变化速率与稳定性要求选择合适的电容容量和类型。 #### 负载Rload的作用 Rload代表实际的用电设备,如LED、电机或传感器等。其阻值影响电路的输出电流与功率。Buck电路需根据负载功率调整MOSFET的导通能力与散热设计,以确保系统稳定运行。 ### 示例代码:MCU PWM控制逻辑(标准库实现) 以下代码展示了如何使用STM32标准库配置PWM信号以控制MOSFET导通: ```c #include "stm32f10x.h" void PWM_Init(void) { // 使能GPIO和定时器时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 配置PA0为复用推挽输出(PWM输出) GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置TIM2定时器 TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_TimeBaseStruct.TIM_Period = 999; // 自动重载值(周期) TIM_TimeBaseStruct.TIM_Prescaler = 71; // 预分频值(72MHz/72 = 1MHz) TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct); // 配置PWM通道 TIM_OCInitTypeDef TIM_OCStruct; TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCStruct.TIM_Pulse = 250; // 占空比25%(250/1000) TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCStruct); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); // 启动定时器 TIM_Cmd(TIM2, ENABLE); TIM_CtrlPWMOutputs(TIM2, ENABLE); } int main(void) { PWM_Init(); while (1) { // 主循环可添加其他控制逻辑 } } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值