freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

智能合约安全审计入门篇 —— Contract Size Check
2023-09-16 13:58:42

​By:小白

背景概述

第一期文章中我们提过防止重入攻击的多种方式,其中一种是通过检查调用者身份并禁止合约调用来防止重入攻击。这期文章我们就来了解如何通过 Contract Size Check 判断调用者身份。

前置知识

众所周知,合约是由代码编写的,所以部署后的合约地址肯定有相应的字节码,而 EOA 地址是没有字节码的,所以我们只需要检查调用者地址是否有字节码就可以判断调用者是否为合约。这里需要使用 Solidity 内联汇编中的 extcodesize() 函数。下面我们还是通过实例来看看这个函数的实际效果。

代码示例

首先,我们用一个代码示例来了解 extcodesize() 的作用:

1694844097_650544c10453bfc85c840.png!small

我们在 Remix 上部署后,调用 Size() 并传入 Deployer 的 EOA 地址(0x787···cabaB),发现返回 0。

1694843415609877.jpg

我们再次调用 Size() 并传入合约自身地址(0xC58···905F2),返回 348。

1694843415732103.jpg

综上可知,当 account 为 EOA 地址时,extcodesize() 会返回 0 ,当 account 为合约地址时,extcodesize() 会返回合约部署后的字节码大小。到这里,相信大家不难想到可以通过调用 extcodesize() 并看其返回值是否为 0 来判断该地址是 EOA 还是部署后的合约。诶,漏洞就这么写出来了。

漏洞示例

漏洞分析

可以看到,isContract() 判断 account 字节码是否为 0 并返回 bool 类型的值。protected() 则会根据 isContract() 的返回值来修改 pwned 的状态。但是这里忽略了一个点,即合约部署时,constructor 函数中的代码逻辑会跟随部署合约的交易一起发送给矿工打包上链。由于此时合约部署的操作还没有完成,所以合约地址还没有存入相应的字节码,这时在 consturctor 函数中调用 extcodesize() 检查合约地址的字节码就会返回 0 。下面我们来看正常调用的情况下会返回什么:

调用 pwn() 并将合约地址 0x058···4c899 传入会被 revert,这里说明合约部署后调用 protected() 会无法通过 require(!isContract(msg.sender) 检查。

1694843415803326.jpg

攻击合约

下面我们将调用逻辑放在 constructor 函数中再次尝试:

部署 Hack 合约时传入 0xbd7···9EFB3 合约地址,发现成功绕过了 require(!isContract(msg.sender) 检查,而且成功修改了 pwned 的状态。

1694843415989491.jpg

修复建议

作为开发者

使用 extcodesize() 判断地址是否为 EOA 地址并不准确,在实际开发中最稳妥的判断调用者身份的方式还是通过 tx.origin 来判断。类似下面这样:

由于 tx.origin 只可能是 EOA 地址,我们只需要判断上层调用者的 msg.sender 是否与 tx.origin 为同一地址就可以。以刚刚的漏洞代码为例,我们只需要稍加修改就可以修复漏洞。

此时部署下面这个 Hack 合约再进行攻击,就会被 revert 并返回 "no contract allowed"。

1694843416075331.jpg

作为审计者

在审计过程中,如果遇到限制合约地址调用的逻辑,应当结合实际业务逻辑判断该检查是否严谨以及是否存在被绕过的可能。

# 安全审计 # 智能合约审计
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者