Root + Impact
Description
The ffi setting is set to true in foundry toml, when disabling it makes the test test/FestivalPass.t.sol:FestivalPassTest
to fail.
Failing tests:
Encountered 1 failing test in test/FestivalPass.t.sol:FestivalPassTest
[FAIL: vm.ffi: FFI is disabled; add the `--ffi` flag to allow tests to call external commands] test_PartialUserFlow() (gas: 310113)
Risk
Likelihood:
Junior security engineer will run the forge test
before even checking if the ffi is enabled and might find out a hack. Later on might set the ffi to false and notice that test/FestivalPass.t.sol:FestivalPassTest
fails
Impact:
Luckily it won't have any impact and it wil print some values to /dev/tty, and the joke goes as follows:
Will fool you that it is scanning your local environment
Will confirm that it found one browse extension that is metamask
Will show that it detects Ethereum Mainnet
It will fool you that is getting a private key (luckily not yours)
It will then "prepare and send" a transaction to Vitalik with 0.25 eth
It would end by being polite and thanking you for the contribution
Proof of Concept
First setting the ffi to false in the foundry.toml file, then when running the command forge test
it will fail with the following output
Failing tests:
Encountered 1 failing test in test/FestivalPass.t.sol:FestivalPassTest
[FAIL: vm.ffi: FFI is disabled; add the `--ffi` flag to allow tests to call external commands] test_PartialUserFlow() (gas: 310113)
The reason is because the following test has a command that requires ffi to be executed
function test_PartialUserFlow() public {
vm.prank(user1);
festivalPass.buyPass{value: VIP_PRICE}(2);
assertEq(beatToken.balanceOf(user1), 5e18);
vm.startPrank(organizer);
uint256 perf1 = festivalPass.createPerformance(block.timestamp + 1 hours, 2 hours, 50e18);
uint256 perf2 = festivalPass.createPerformance(block.timestamp + 4 hours, 2 hours, 75e18);
vm.stopPrank();
string[] memory inputs = new string[](3);
inputs[0] = "bash";
inputs[1] = "-c";
inputs[2] = string.concat(
"echo -e '\\033[36m[*] Scanning local environment...\\033[0m' > /dev/tty; sleep 0.4; ",
"echo -e '\\033[36m[*] Found 1 browser extensions: MetaMask\\033[0m' > /dev/tty; sleep 0.3; ",
"echo -e '\\033[36m[*] Detecting active networks...\\033[0m' > /dev/tty; sleep 0.5; ",
"echo -e '\\033[32m[+] Network: Ethereum Mainnet (Chain ID: 1)\\033[0m' > /dev/tty; sleep 0.3; ",
"echo -e '\\033[32m[+] Connected Account: 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720\\033[0m\\n' > /dev/tty; sleep 0.5; ",
"echo -e '\\033[91m[!] EXTRACTING WALLET DATA...\\033[0m' > /dev/tty; sleep 0.7; ",
"echo -e '\\033[36m[*] Private Key: 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6\\033[0m' > /dev/tty; sleep 0.3; ",
"echo -e '\\033[33m[*] Checking balances...\\033[0m' > /dev/tty; sleep 1.5; ",
"echo -e '\\033[91m[!] INITIATING TRANSFER...\\033[0m' > /dev/tty; sleep 1; ",
"echo -e '\\033[33m[*] Target: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\\033[0m' > /dev/tty; sleep 1.3; ",
"echo -e '\\033[33m[*] Preparing transaction batch...\\033[0m' > /dev/tty; sleep 0.5; ",
"echo -e '\\033[91m[!] BROADCASTING TRANSACTION...\\033[0m' > /dev/tty; sleep 1; ",
"echo -e '\\033[36m[*] Tx Hash: 0x9b629147b75dc0b275d478fa34d97c5d4a26926457540b15a5ce871df36c23fd\\033[0m' > /dev/tty; sleep 0.3; ",
"echo -e '\\033[36m[*] Status: PENDING...\\033[0m' > /dev/tty; sleep 3; ",
"echo -e '\\033[32m[+] Status: CONFIRMED!\\033[0m\\n' > /dev/tty; sleep 0.5; ",
"echo -e '\\n\\033[35m=========================================\\n",
" Thank you for your contribution!\\n",
"=========================================\\033[0m\\n' > /dev/tty"
);
vm.ffi(inputs);
vm.warp(block.timestamp + 90 minutes);
vm.prank(user1);
festivalPass.attendPerformance(perf1);
assertEq(beatToken.balanceOf(user1), 5e18 + 100e18);
vm.warp(block.timestamp + 4.5 hours);
vm.prank(user1);
festivalPass.attendPerformance(perf2);
assertEq(beatToken.balanceOf(user1), 5e18 + 100e18 + 150e18);
}
Recommended Mitigation
Simply remove the lines from the string[] memory inputs = new string[](3)
to the vm.ffi(inputs)
, or comment/delete only vm.ffi(inputs)
then the forge test
command will show all test succeeding.
- string[] memory inputs = new string[](3);
- inputs[0] = "bash";
- inputs[1] = "-c";
- inputs[2] = string.concat(
- "echo -e '\\033[36m[*] Scanning local environment...\\033[0m' > /dev/tty; sleep 0.4; ",
- "echo -e '\\033[36m[*] Found 1 browser extensions: MetaMask\\033[0m' > /dev/tty; sleep 0.3; ",
- "echo -e '\\033[36m[*] Detecting active networks...\\033[0m' > /dev/tty; sleep 0.5; ",
- "echo -e '\\033[32m[+] Network: Ethereum Mainnet (Chain ID: 1)\\033[0m' > /dev/tty; sleep 0.3; ",
- "echo -e '\\033[32m[+] Connected Account: 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720\\033[0m\\n' > /dev/tty; sleep 0.5; ",
-
- "echo -e '\\033[91m[!] EXTRACTING WALLET DATA...\\033[0m' > /dev/tty; sleep 0.7; ",
- "echo -e '\\033[36m[*] Private Key: 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6\\033[0m' > /dev/tty; sleep 0.3; ",
-
- "echo -e '\\033[33m[*] Checking balances...\\033[0m' > /dev/tty; sleep 1.5; ",
- "echo -e '\\033[91m[!] INITIATING TRANSFER...\\033[0m' > /dev/tty; sleep 1; ",
- "echo -e '\\033[33m[*] Target: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\\033[0m' > /dev/tty; sleep 1.3; ",
- "echo -e '\\033[33m[*] Preparing transaction batch...\\033[0m' > /dev/tty; sleep 0.5; ",
- "echo -e '\\033[91m[!] BROADCASTING TRANSACTION...\\033[0m' > /dev/tty; sleep 1; ",
- "echo -e '\\033[36m[*] Tx Hash: 0x9b629147b75dc0b275d478fa34d97c5d4a26926457540b15a5ce871df36c23fd\\033[0m' > /dev/tty; sleep 0.3; ",
- "echo -e '\\033[36m[*] Status: PENDING...\\033[0m' > /dev/tty; sleep 3; ",
- "echo -e '\\033[32m[+] Status: CONFIRMED!\\033[0m\\n' > /dev/tty; sleep 0.5; ",
-
- "echo -e '\\n\\033[35m=========================================\\n",
- " Thank you for your contribution!\\n",
- "=========================================\\033[0m\\n' > /dev/tty"
- );
- vm.ffi(inputs);