【漏洞分析】Penpie 攻击事件:重入攻击构造奖励金额
背景信息
2024 年 9月 3日,Penpie 合约遭受重入攻击,攻击者在重入阶段向合约添加流动性来冒充奖励金额,从而获取合约内原有的奖励代币。资产损失高达 2734 万美元。
2024 年 5月,Penpie 平台新增了推出了无需许可的资产池功能,即允许 Pendle 上的用户可以在该平台上自建任何 PT 或 YT 代币的 LP 资金池,用户在 Penpie 平台上存入 LP 后,可以额外多获得一份代币奖励。
- X 告警:https://x.com/PeckShieldAlert/status/1831072230651941093
- 前置交易(Create Pool):https://app.blocksec.com/explorer/tx/eth/0xfda0dde38fa4c5b0e13c506782527a039d3a87f93f9208c104ee569a642172d2
- 其中一笔攻击交易:https://app.blocksec.com/explorer/tx/eth/0x56e09abb35ff12271fdb38ff8a23e4d4a7396844426a94c4d3af2e8b7a0a2813
- 被攻击合约:https://etherscan.io/address/0x6e799758cee75dae3d84e09d40dc416ecf713652
Trace 分析
攻击者首先在前置交易中新建了一个 market,并将 SY
地址设置为攻击合约 0x4af4
随后在攻击交易中,攻击者闪电贷了四种资产(由于四种资产的操作类似,我们选择其中一种资产 wstETH 进行分析)
闪电贷内进行了这几类操作
在 batchHarvestMarketRewards
函数中进行了重入攻击
代币流向分析
- 0x6e79.batchHarvestMarketRewards:
- redeemRewards:
- [Reentrancy] addLiquiditySingleTokenKeepYt:deposit
16010
wstETH to [Pendle: RouterV4], get8860
[MarketToken] - [Reentrancy] depositMarket:deposite
1751
[MarketToken] to [0x6e79], received1751
[StakingToken]
- [Reentrancy] addLiquiditySingleTokenKeepYt:deposit
- queueNewRewards:Claim
1751
[MarketToken] to 0xd128
- redeemRewards:
- multiclaim:Claim
1751
[MarketToken] from 0xd128 - withdrawMarket:burn
1715
[StakingToken], get1715
[MarketToken] - removeLiquiditySingleToken:burn
10611
[MarketToken], get18733
wstETH - transfer:Repay flashloan
漏洞分析
CreatePool
任何用户都可以在 Pendle 上注册 Pool(market)
https://etherscan.io/address/0x588f5e5d85c85cac5de4a1616352778ecd9110d3#code
其中的 onlyVerifiedMarket
检查,会检查 pool 地址是否在 allMarkets
中。而任何人都可以创建池子,绕过这个限制。
https://vscode.blockscan.com/ethereum/0x45cF29F501d218Ad045EB8d622B69968E2d4Ef5C
batchHarvestMarketRewards
在 batchHarvestMarketRewards
函数中,通过计算调用 market.redeemRewards
函数前后的 MarketToken 数量差值,来得到作为奖励代币的 wstETH 数量。
攻击者利用这个设计缺陷,调用 0x6e79.batchHarvestMarketRewards
函数触发重入攻击,使得 bounsTokens
的值增大。
https://vscode.blockscan.com/ethereum/0xff51c6b493c1e4df4e491865352353eadff0f9f8
batchHarvestMarketRewards(Part1)
redeemRewards
由于 market 合约为攻击者创建的合约,其 SY
在创建时被设为了攻击合约的地址。
https://vscode.blockscan.com/ethereum/0x40789E8536C668c6A249aF61c81b9dfaC3EB8F32
函数调用流程
redeemRewards -> _redeemRewards -> _updateAndDistributeRewards -> _updateAndDistributeRewardsForTwo -> _updateRewardIndex -> _redeemExternalReward -> StandardizedYield.claimRewards
SY
为攻击合约地址,在 claimRewards
函数中进行重入。
[Reentrancy] addLiquiditySingleTokenKeepYt & depositMarket
[Reentrancy] addLiquiditySingleTokenKeepYt:deposit 16010 wstETH to [Pendle: SY-stETH Token], get 8860 [MarketToken][Reentrancy] depositMarket:deposite 1751 [MarketToken] to [StakingToken], received 1751 [StakingToken]
重入攻击 trace,通过 addLiquiditySingleTokenKeepYt
和 depositMarket
操作将 wstETH 转换为 MarketToken ,并质押到 0x6e79合约中。
batchHarvestMarketRewards(Part2)
通过重入攻击,使得合约在计算 originalBonusBalance
奖励数量时误以为获得了 1751 的奖励(实际上并没有获得任何奖励,余额多出来的部分是重入的时候添加流动性那部分)。
originalBonusBalance
和 leftBonusBalance
的值会按照 _harvestBatchMarketRewards -> _sendRewards -> _queueRewarder
的调用路径传递到 _queueRewarder
函数中。
此时合约会向 0xd128 地址发送 1751
_rewardToken
。
攻击者在通过重入添加流动性时,所添加的代币数量 1751 等于 0x6e79 合约中代币余额的数量 1751
,其目的是构造“新增奖励”的数量等于“账户余额”,使得接下来的 queueRewarder
函数将 0x6e79 合约中的所有代币转移到 0xd128。
queueNewRewards
queueNewRewards:Claim 1751 [MarketToken] to 0xd128
0xd128 从 0x6e79 处转移奖励代币。其中0xd128是rewardPool合约。
multiclaim
multiclaim:get 1751 [MarketToken] from 0xd128
攻击者从 0xd128 合约中领取奖励(完成获利,这笔资金的来源是 0x6e79 合约的余额)
withdrawMarket
withdrawMarket:burn 1751 [StakingToken], get 1751 [MarketToken]
取回在重入中通过 depositMarket
存入的 1751
[MarketToken]
removeLiquiditySingleToken
removeLiquiditySingleToken:burn 10611 [MarketToken], get 18733 wstETH
此时攻击者手里持有原来的 8860
,加上攻击所得 1751
,一共持有 10611
[MarketToken]
最终攻击者移除 10611
流动性,获得 18733
的 wstETH。
Repay flashloan
向闪电贷归还 16010
wstETH,获利 2723
wstETH。
后记
这次的攻击事件影响挺大的,涉及的金额也是巨大。在事件发生以后,许多安全从业人员都对这件事情进行了分析,我也第一时间尝试着从 trace 去分析这个攻击事件。由于当时事发不久,还没有厂商公布详细的漏洞分析结果,再加上个人叛逆的心态想着难道不参考别人的分析报告我就分析不出来了吗,这次的攻击事件分析是在一天的时间内硬啃 trace 分析得来的。这样的分析对我来说进行得并不容易,且最终输出的分析文档,也会缺乏了一些对项目架构与设计的理解,有骨没肉,读起来很干巴。我反思了一下我为何会落入如此窘境,归根究底还是对项目的不熟悉。在不熟悉项目的前提下做的攻击分析,有形无意,味如嚼蜡。这是一个不可忽视的问题,我需要想想办法。
【漏洞分析】Penpie 攻击事件:重入攻击构造奖励金额的更多相关文章
- 使用timer定时器,防止事件重入
首先简单介绍一下timer,这里所说的timer是指的System.Timers.timer,顾名思义,就是可以在指定的间隔是引发事件.官方介绍在这里,摘抄如下: 1 2 Timer 组件是基于服务器 ...
- ACE handle_timeout 事件重入
当多线程运行反应器事件时, 注意handle_timeout会重入,单独线程不存在下列问题! 1. 一个timer事件 // test_ace_timer.cpp : Defines the entr ...
- 新浪微博XSS攻击事件
http://blog.csdn.net/terryzero/article/details/6575078 6月28日20时14分左右开始,新浪微博出现了一次比较大的XSS攻击事件.大量用户自动发送 ...
- 【漏洞三】跨站点脚本(XSS)攻击
[漏洞] 跨站点脚本(XSS)攻击 [原因] 跨站点脚本(也称为xss)是一个漏洞,攻击者可以发送恶意代码(通常在(Javascript的形式)给另一个用户.因为浏览器无法知道脚本是否值得信任,所以它 ...
- 风炫安全web安全学习第三十三节课 文件包含漏洞基础以及利用伪协议进行攻击
风炫安全web安全学习第三十三节课 文件包含漏洞基础以及利用伪协议进行攻击 文件包含漏洞 参考文章:https://chybeta.github.io/2017/10/08/php文件包含漏洞/ 分类 ...
- Java 重入锁 ReentrantLock 原理分析
1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...
- 可重入读写锁ReentrantReadWriteLock基本原理分析
前言 本篇适用于了解ReentrantLock或ReentrantReadWriteLock的使用,但想要进一步了解原理的读者.见于之前的分析都是借鉴大量的JDK源码,这次以流程图的形式代替源码,希望 ...
- malloc的可重入性和线程安全分析
malloc函数是一个我们经常使用的函数,如果不对会造成一些潜在的问题.下面就malloc函数的线程安全性和可重入性做一些分析. 我们知道一个函数要做到线程安全,需要解决多个线程调用函数时访问共享资源 ...
- 详解DNS重绑定攻击
0x00 前言 DNS重绑定攻击的用法有很多种,这篇文章主要理解DNS重绑定攻击的原理,并介绍如何通过DNS重绑定来攻击内网设备.为了更好的理解DNS重绑定攻击,我们先从Web浏览器的同源策略开始介绍 ...
- Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析
原文:Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析 一.RedissonLock#lock 源码分析 1.根据锁key计算出 slot,一个slot对 ...
随机推荐
- CM3调试系统简析
CM3 调试系统简析 **"一直以来,单片机的调试一直不是很突出的主题,很多简单些的程序在开发中,甚至都没有调试的概念,而只是把生成的映像直接烧入片子,再根据错误症状来判断问题,然后修改程序 ...
- 手动设置提示在此环境中不可导入Django
手动设置提示在此环境中不可导入Django 环境参数添加manage.py中的代码'DJANGO_SETTINGS_MODULE', 'codeProject.settings'
- [oeasy]python0104_指示灯_显示_LED_辉光管_霓虹灯
编码进化 回忆上次内容 x86.arm.riscv等基础架构 都是二进制的 包括各种数据.指令 但是我们接触到的东西 都是屏幕显示出来的字符 计算机 显示出来的 一个个具体的字型 ...
- Day 7 - 哈希与 KMP
字符串哈希 定义 我们定义一个把字符串映射到整数的函数 \(f\),这个 \(f\) 称为是 \(\text{Hash}\) 函数. 我们希望这个函数 \(f\) 可以方便地帮我们判断两个字符串是否相 ...
- ASP.NET Core 3.x 三种【输入验证】方式
验证要做三件事 定义验证规则 按验证规则进行检查 报告验证的错误. 在把错误报告给API消费者的时候,报告里并不包含到底是服务端还是API消费者引起的错误,这是状态码的工作.而通常响应的Body里面会 ...
- c++17 structure binding test
1 /*test for struct binding*/ 2 3 #include <string> 4 #include <iostream> 5 using namesp ...
- 使用ollama本地部署gemma记录
1.官网https://ollama.com/安装ollama 2.先配置一下环境变量 不然下载的东西会默认丢在C盘里 3.cmd执行ollama run gemma:2b (使用后推荐直接下7b,2 ...
- RESTful服务与swagger
一开始刚学springboot的时候 restful服务+swagger一点都看不懂,现在知识学了一些,再回头看这些东西就简单很多了. 自己跟视频做了一个零件项目,里面写了一些零零散散的模块,其中在视 ...
- 5/15课下作业:评价一下steam软件
用户界面: 登录后会弹出特惠广告,广告内容可能不常用.主界面简洁方便,启动游戏,购买游戏,浏览社区,浏览自己内容一目了然 记住用户选择: 登录一次后会记住用户的账户密码,可以直接进行用户间的切换,会记 ...
- CMake学习(一)
CMake学习(一) 1.简介 CMake是一个强大的软件构建系统,可以用简单的语句来描述所有平台的安装(编译过程) 可以编译源代码.制作程序库.产生适配器(wrapper).还可以用任意的顺序建构执 ...