{"author":{"address":"0xC94d05e4dd4e9B06143D6089B52cAF78023D828c","user":"https://learnblockchain.cn/people/22428"},"content":{"body":"\u003cbr/\u003e\r\n\r\n|发生层次|漏洞名称|漏洞原因|\r\n|:--:|:--:|:--:|\r\n|高级语言层面|可重入漏洞|Fallback 函数中存在对外 部合约函数的递归调用|\r\n||整数溢出漏洞|数值超出或低于定义的整数类型范围|\r\n||算术精度漏洞|精度丢失|\r\n||拒绝服务漏洞|意外执行自毁指令; 访问控制策略出错; \u003cbr/\u003eGas 达到区块上限; 非预期异常抛出|\r\n||以太冻结漏洞|合约被未经授权的用户销毁|\r\n||未处理异常漏洞|未检查调用结果|\r\n||合约初始化漏洞|代码错误或者访问权限|\r\n|虚拟机层面|tx.origin漏洞|tx.origin 全局变量用于智能合约身份验证|\r\n||短地址攻击|合约地址不符合规范 (小于 20 个字节)|\r\n|区块链层面|区块链依赖漏洞|使用区块数据产生随机数|\r\n||交易顺序依赖漏洞|交易顺序不一致|\r\n\r\n\u003cbr/\u003e\r\n\r\n## 4.1 高级语言层面\r\n\r\n### 1、可重入漏洞\r\n\r\n2016年6月17日，攻击者利用重入漏洞盗取DAO合约中价值5000万美元的以太币。这是区块链历史上最严重的一次攻击，直接导致了以太坊的分叉。智能合约可以由外部账户调用，也可以由合约账户调用。每个合约都隐式包含了一个回调函数fallback（）。如果调用的函数不存在 或 接 收 到 以 太 币，合约会自动执行fallback（）函数。攻击者通过重写合约中的fallback（）函数，比如让它再次调用转账函数，从而实现重入攻击。目前重入攻击主要存在３种形式：跨函数（cross-function）、委托调用（delegated）和基于构造函数（created-based）。下图给出了一个存在重入漏洞的示例，withdraw（）函数是一个提款函数，用户可以通过调用它取回自己的以太币。合约使用balances记录每个账户的余额（第３行），withdraw（）函数使用call（）方法转移以太币（第４行），并且在执行转移操作之后对账户余额进行清零（第５行）。\r\n\r\n\u003cbr/\u003e\r\n\r\n攻击者首先调用withdraw（）函数向自己的另一个合约账号转账，并且在执行第５行命令之前，利用目标合约中的fallback（）函数多次调用withdraw（）函数。通过重入不断超额取款，直至取走合约账户内全部的以太币。\r\n\r\n\r\n![5815da5048c812b4536d51694b2e1b19.png](https://img.learnblockchain.cn/attachments/2024/09/5p9QJtoQ66d5e3af07237.png)\r\n\r\n### 2、整数溢出漏洞\r\n\r\n整数溢出是编程中最常见的漏洞，==分为上溢和下溢==，加法、减法、乘法都会造成整数溢出。在Solidity中，合约通常使用无符号定长的整数类型，从unit 8到unit 256按８位递增。EVM在整数越界时，会根据数据类型从高位截断，因此可能会产生整数溢出。攻击者可以利用整数溢出绕过合约中的条件限制语句（如require、if等）实现超额转账。2018年4月22日，知名代币Beth - erchip（BEC）遭受攻击，攻击者利用乘法溢出凭空转出了远超发行量的ＢＥＣ代币，引起持有者的恐慌和抛售。此次攻击最终导致BEC近65亿人民币的市值几乎归零。\r\n\r\n### 3、算术精度漏洞\r\n\r\nSolidity不支持浮点型计算，在整数除法中，小数位会自动取０从而导致精度损失。因此，不同的运算顺序可能产生不同的结果。例如，在下图中，开发者的本意是根据预先设定的费率feePercentage，收取10％的手续费，然而在第３行中可以看出，10/100＝0，而不是0.1，因此手续费的计算结果将始终为０。\r\n\r\n![0e9cb4cc684d913de350d0d44c090cc2.png](https://img.learnblockchain.cn/attachments/2024/09/2G2A7v9C66d5e3bd172e1.png)\r\n### 4、拒绝服务漏洞\r\n\r\n合约拒绝服务是指攻击者通过破坏合约中原有的逻辑，消耗以太坊中的汽油费或计算资源，从而使合约在一段时间或永久无法正常执行。针对智能合约的拒绝服务攻击主要有以下３种：\r\n\r\n１）基于代码逻辑的拒绝服务攻击。由于合约代码存在漏洞导致合约暂时或永久无法使用。最典型的是当合约中存在对传入的映射或数组循环遍历操作时，由于外部的输入长度是不可控的，攻击者可能传入过大的映射或数组。然而每个以太坊区块都设定了汽油费上限，如果外部传入的参数导致后续的交易运行所需的汽油费超过区块上限，会导致交易一直处于失败状态。\r\n\r\n２）基于外部调用的拒绝服务攻击。这种情况是由于合约没有正确处理外部调用。例如，如果合约依赖于外部函数执行的结果，但合约没有对外部函数执行失败进行处理，此时如果外部调用失败或者由于外部原因而被拒绝时，会导致每次执行交易时都会因为这个失败问题导致交易回滚，合约无法继续执行。\r\n\r\n３）合约Owner导致的拒绝服务攻击。一些合约中设置了Owner作为管理员角色，Owner具有很高的权限，例如可以开启或关闭转账功能。如果合约Owner的密钥泄露、丢失，则可能让合约遭受非主观意愿上的拒绝服务攻击。\r\n\r\n### 5、以太冻结漏洞\r\n\r\n智能合约拥有自己的余额，可以接收以太币。然而如果合约存在漏洞，则可能会将合约中所有的以太币锁定。2022年4月，一个名为AkuDreams的NFT项目由于合约存在逻辑错误，锁定了参与拍卖人员的 11593.5枚以太币。2017年，Parity钱包合约由于漏洞导致价值超过2.8亿美元的以太币被锁定在合约中。Parity合约使用了==代理模式将合约的数据存储和业务逻辑分离==，用一个合约存储数据，然后通过DELEGATECALL指令调用另一个合约来实现业务处理。这种模式可以实现代码复用，从而降低合约的部署费用。然而Parity的业务合约存在权限漏洞，攻击者可以获取业务合约的管理 员 权 限，并 在 获 得 权 限 后 执 行 自 毁（SELFDESTRUCT）指令，导致Parity的存储合约无法执行任何操作，从而将所有的以太币锁定在合约中。\r\n\r\n### 6、未处理异常漏洞\r\n\r\nTransfer（）指令执行失败会抛出异常并回滚交易，而send（）和call（）则会返回一个布尔值，并继续执行后续代码。下图中不仅存在重入漏洞，还存在未处理异常，withdraw（）函数没有处理第４行call（）调用的返回值，如果调用失败（例如汽油费数量不够），第５行仍然会将账号的余额清零，从而导致合约状态和实际情况不一致。\r\n\r\n\r\n![5815da5048c812b4536d51694b2e1b19.png](https://img.learnblockchain.cn/attachments/2024/09/0qGzD8CN66d5e3d094ddf.png)\r\n\r\n### 7、合约初始化漏洞\r\n\r\n在Solidity中，合约有２种初始化方式：==构造函数和自定义初始化函数==。在小于0.4.22版本的Solidity中，构造函数的名称应该和合约名称一致。在开发过程中，如果构造函数名称写错，那么原本的构造函数就会变成人人都可以调用的普通函数。MorphToken合约就曾因为构造函数大小写不一致问题，致使合约存在所有权丢失的风险。在0.4.22版本之后的版本中，合约使用了Constructor作为构造函数的名称。然而一些合约为了避免处理复杂的构造函数参数，使用了自定义的Initialize函数对合约进行初始化。然而，Initialize函数跟普通函数并无本质区别，并不能像Constructor一样只能执行一次，开发者如果没有正确设置函数的访问权限，则Initialize函数可能可以被任何人再次调用。例如2021年3月9日，DODO的合约遭遇攻击，造成约200万美元损失，原因在于初始化函数没有校验调用者，并且可以多次调用。\r\n\r\n\u003cbr/\u003e\r\n\r\n## 4.2 虚拟机层面\r\n\r\n### 1、tx.origin漏洞\r\n\r\n一些合约错误地使用tx.origin进行鉴权。tx.origin会递归栈的调用，找到交易调用的最初发起者。因此，使用tx.origin进行鉴权时存在钓鱼攻击的风险。下图演示了利用tx.origin漏洞的钓鱼攻击。如图所示，Alice拥有一个钱包合约 （wallet contract）用于管理自己的以太币，钱包合约中存在一个转账函数transfer（），使用了tx.origin进行鉴权。Bob发现钱包合约存在漏洞，因此他构造了一个钓鱼合约（phishing contract），并引诱 Alice调用钓鱼合约的buy（）函数。一旦Alice调用了buy（）函数，buy（）函数就会自动调用 Wallet.transfer（）函数。Bob可以利用合约最初签名者是Alice，从而绕过require(tx.origin == owner)，盗取钱包合约中的以太币。\r\n\r\n\r\n![88a0dc57141e64aa51f6f31de7aee061.png](https://img.learnblockchain.cn/attachments/2024/09/yHVFx0n666d5e3da5ac2a.png)\r\n\r\n### 2、短地址攻击\r\n\r\n短地址攻击利用了EVM的自动补全机制。例如在调用ERC－20合约的transfer（）函数时，正常情况下，需要输入68字节的字节码，其中地址和金额2个参数相邻，并且都是高位补０的32字节。然而，EVM并不会严格校验地址的位置，并且还会对缺失的位数自动补全。调用者可以首先通过碰撞生成一个末几位为０的收款地址（假定是２位）。然后在调用合约中的转账功能时，故意少写收款地址的后两位，此时，EVM会自动用后续金额的高位对地址进行补齐。但由于后续金额的位数不足，EVM会在参数的低位自动补齐，假定原来金额的值为0x1，自动补齐后就变成了0x100，扩大了256倍。\r\n\r\n\u003cbr/\u003e\r\n\r\n## 4.3 区块链层面\r\n\r\n### 1、区块链依赖漏洞\r\n\r\n合约可以通过Solidity全局变量访问到当前区块的相关数据，例如区块号、区块哈希值、区块时间戳等，合约执行时应当避免依赖这些数据。最典型的错误是使用这些数据生成随机数。合约通常是公开的，因此攻击者可以根据随机数的生成算法推测出随机数从而发动攻击。例如 2018年11月11日，EOS.WIN遭受随机数攻击，损失了20,000枚EOS代币。下图演示了一个存在不安全随机数的合约。Victim合约使用当前区块的难度值block.diffculty和时间戳block.timestamp作为随机种子生成随机数，同时，bet（）函数根据随机数是否为偶数判断参与者是否获胜。由于任何合约都可以访问当前区块的信息，攻击者可以构造一个合约 Attack，使用同样方法生成随机数，并判断当前是否可以获胜。若获胜，则合约会自动调用受害者的bet（）函数下注，否则跳过。事实上，攻击者可以在attack（）函数中设置一个循环，一旦获胜就不断地调用bet（）函数，直到取光受害者合约中所有的以太币。\r\n\r\n![535bb57cd44bd550459af10b74b86450.png](https://img.learnblockchain.cn/attachments/2024/09/h6MG6Afn66d5e3e60e8f3.png)\r\n### 2、交易顺序依赖漏洞\r\n\r\n矿工在打包交易时，通常会优先选择手续费高的交易。在同一个区块中，如果２个交易交换执行顺序会影响最终的结果，那么此合约就存在\r\n交易顺序依赖。这让合约的执行存在不确定性，并且可能导致合约执行失败或出现意料之外的结果。例如在一个谜题奖励合约中，合约拥有者\r\n可以监听交易池，当发现有参与者提交结果后，立刻发布一个相同的答案或调用合约减少奖励，通过更多的手续费让矿工优先打包到区块中，利\r\n用交易顺序依赖漏洞来实现不公平行为。","title":"智能合约的漏洞"},"history":null,"timestamp":1725293659,"version":1}