/**
* π SECURITY EXPLOITATION TEST SUITE
*
* Testa TODAS as formas possΓveis de fraudar o sistema de crΓ©ditos.
* Se algum teste PASSAR, significa que hΓ‘ uma VULNERABILIDADE!
*/
import { createClient } from '@supabase/supabase-js';
import * as fs from 'fs';
import * as path from 'path';
// Load .env
const envPath = path.join(__dirname, '../../.env');
if (fs.existsSync(envPath)) {
fs.readFileSync(envPath, { encoding: 'utf8' }).split('\n').forEach(line => {
const [key, value] = line.split('=');
if (key && value) process.env[key.trim()] = value.trim();
});
}
const SUPABASE_URL = process.env.SUPABASE_URL || '';
const SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || '';
const ANON_KEY = process.env.SUPABASE_ANON_KEY || '';
if (!SUPABASE_URL) {
console.error('β Missing SUPABASE_URL');
process.exit(1);
}
// Two clients: one with service role (admin), one with anon (hacker simulation)
const adminClient = createClient(SUPABASE_URL, SERVICE_KEY);
const hackerClient = createClient(SUPABASE_URL, ANON_KEY);
const VICTIM_EMAIL = `victim_${Date.now()}@test.com`;
const HACKER_EMAIL = `hacker_${Date.now()}@test.com`;
interface TestResult {
name: string;
vulnerable: boolean;
details: string;
}
const results: TestResult[] = [];
function logTest(name: string, vulnerable: boolean, details: string) {
const icon = vulnerable ? 'π¨ VULNERABLE' : 'β
PROTECTED';
console.log(`\n${icon}: ${name}`);
console.log(` Details: ${details}`);
results.push({ name, vulnerable, details });
}
async function runSecurityTests() {
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('π SECURITY EXPLOITATION TEST SUITE');
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log(`Victim: ${VICTIM_EMAIL}`);
console.log(`Hacker: ${HACKER_EMAIL}`);
// Setup: Give victim 100 credits
await adminClient.rpc('add_credits', { user_email: VICTIM_EMAIL, credits_amount: 100 });
console.log('\n[SETUP] Victim has 100 credits');
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 1: Negative Credit Injection (Infinite Money Glitch)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[1] NEGATIVE CREDIT INJECTION (Infinite Money)');
console.log(' Attack: use_credits(-1000) to ADD credits instead of subtract');
const { data: negResult } = await hackerClient.rpc('use_credits', {
user_email: HACKER_EMAIL,
credits_to_use: -1000
});
const { data: hackerBal1 } = await adminClient.rpc('get_credits', { user_email: HACKER_EMAIL });
logTest(
'Negative Credit Injection',
hackerBal1 > 0,
negResult === true ? `HACKED! Balance: ${hackerBal1}` : `Blocked. Balance: ${hackerBal1}`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 2: SQL Injection via Email Field
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[2] SQL INJECTION via Email');
console.log(' Attack: Inject SQL to steal credits from victim');
const sqlInjectionEmails = [
`' OR 1=1; --`,
`${VICTIM_EMAIL}'; UPDATE user_credits SET credits=0 WHERE email='${VICTIM_EMAIL}'; --`,
`test@test.com' UNION SELECT credits FROM user_credits WHERE email='${VICTIM_EMAIL}' --`,
`'; DROP TABLE user_credits; --`
];
let sqlInjectionWorked = false;
for (const maliciousEmail of sqlInjectionEmails) {
try {
await hackerClient.rpc('use_credits', { user_email: maliciousEmail, credits_to_use: 1 });
} catch (e) {
// Expected to fail
}
}
const { data: victimAfterSql } = await adminClient.rpc('get_credits', { user_email: VICTIM_EMAIL });
sqlInjectionWorked = victimAfterSql !== 100;
logTest(
'SQL Injection via Email',
sqlInjectionWorked,
`Victim balance after attack: ${victimAfterSql} (should be 100)`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 3: Steal Credits from Another User
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[3] CROSS-USER CREDIT THEFT');
console.log(' Attack: Try to use victim\'s email to steal their credits');
// Hacker tries to use victim's credits
const { data: stealResult } = await hackerClient.rpc('use_credits', {
user_email: VICTIM_EMAIL,
credits_to_use: 50
});
const { data: victimAfterTheft } = await adminClient.rpc('get_credits', { user_email: VICTIM_EMAIL });
// This is actually allowed by design if no auth check - depends on RLS
logTest(
'Cross-User Credit Theft',
stealResult === true && victimAfterTheft < 100,
`Theft ${stealResult === true ? 'succeeded' : 'blocked'}. Victim balance: ${victimAfterTheft}`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 4: Direct Database Manipulation (Bypass RPC)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[4] DIRECT TABLE MANIPULATION (Bypass RPC)');
console.log(' Attack: INSERT/UPDATE directly into user_credits table');
// Try to insert directly
const { error: insertError } = await hackerClient
.from('user_credits')
.insert({ email: HACKER_EMAIL, credits: 999999 });
// Try to update directly
const { error: updateError } = await hackerClient
.from('user_credits')
.update({ credits: 999999 })
.eq('email', HACKER_EMAIL);
const { data: hackerBal4 } = await adminClient.rpc('get_credits', { user_email: HACKER_EMAIL });
const directManipWorked = hackerBal4 > 100;
logTest(
'Direct Table Manipulation',
directManipWorked,
`Insert error: ${insertError?.code || 'allowed'}, Update error: ${updateError?.code || 'allowed'}, Balance: ${hackerBal4}`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 5: Race Condition Exploitation (Double Spending)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[5] RACE CONDITION (Double Spending)');
console.log(' Attack: Send 100 requests of 1 credit when only 10 available');
// Reset hacker to exactly 10 credits
await adminClient.rpc('add_credits', { user_email: HACKER_EMAIL, credits_amount: 10 });
const { data: startBal } = await adminClient.rpc('get_credits', { user_email: HACKER_EMAIL });
console.log(` Starting balance: ${startBal}`);
const RACE_REQUESTS = 100;
const promises = [];
for (let i = 0; i < RACE_REQUESTS; i++) {
promises.push(hackerClient.rpc('use_credits', { user_email: HACKER_EMAIL, credits_to_use: 1 }));
}
const raceResults = await Promise.all(promises);
const successCount = raceResults.filter(r => r.data === true).length;
const { data: finalBal } = await adminClient.rpc('get_credits', { user_email: HACKER_EMAIL });
// If more than 10 succeeded, there's a race condition
const raceVulnerable = successCount > startBal || finalBal < 0;
logTest(
'Race Condition (Double Spending)',
raceVulnerable,
`${successCount}/${RACE_REQUESTS} succeeded. Final balance: ${finalBal} (should be 0, not negative)`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 6: Floating Point Exploitation
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[6] FLOATING POINT EXPLOITATION');
console.log(' Attack: Use 0.0001 credits to round down to 0');
await adminClient.rpc('add_credits', { user_email: HACKER_EMAIL, credits_amount: 5 });
const { data: floatStart } = await adminClient.rpc('get_credits', { user_email: HACKER_EMAIL });
// Try using fractional credits
const { data: floatResult, error: floatError } = await hackerClient.rpc('use_credits', {
user_email: HACKER_EMAIL,
credits_to_use: 0.0001
});
const { data: floatEnd } = await adminClient.rpc('get_credits', { user_email: HACKER_EMAIL });
const floatWorked = floatResult === true && floatEnd === floatStart;
logTest(
'Floating Point Exploitation',
floatWorked,
`Used 0.0001, result: ${floatResult}, balance: ${floatStart} -> ${floatEnd}`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 7: Zero Credit Usage
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[7] ZERO CREDIT USAGE (Free Service)');
console.log(' Attack: Use 0 credits but still get service');
const { data: zeroResult } = await hackerClient.rpc('use_credits', {
user_email: HACKER_EMAIL,
credits_to_use: 0
});
logTest(
'Zero Credit Usage',
zeroResult === true,
`use_credits(0) returned: ${zeroResult} (should be false)`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 8: Integer Overflow
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[8] INTEGER OVERFLOW');
console.log(' Attack: Use MAX_INT to overflow to negative');
const MAX_INT = 2147483647;
const { data: overflowResult } = await hackerClient.rpc('use_credits', {
user_email: HACKER_EMAIL,
credits_to_use: MAX_INT
});
const { data: overflowBal } = await adminClient.rpc('get_credits', { user_email: HACKER_EMAIL });
logTest(
'Integer Overflow',
overflowBal < 0 || overflowBal > 1000000,
`Used MAX_INT, balance: ${overflowBal}`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 9: NULL/Undefined Injection
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[9] NULL/UNDEFINED INJECTION');
console.log(' Attack: Pass null values to bypass validation');
try {
const { data: nullResult } = await hackerClient.rpc('use_credits', {
user_email: null as any,
credits_to_use: 1
});
logTest('NULL Email Injection', nullResult === true, `Returned: ${nullResult}`);
} catch (e: any) {
logTest('NULL Email Injection', false, `Exception thrown: ${e.message}`);
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TEST 10: Audit Log Tampering
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('[10] AUDIT LOG TAMPERING');
console.log(' Attack: Delete or modify transaction history');
const { error: deleteAuditError } = await hackerClient
.from('credit_transactions')
.delete()
.eq('email', HACKER_EMAIL);
const { error: updateAuditError } = await hackerClient
.from('credit_transactions')
.update({ amount: 1000000 })
.eq('email', HACKER_EMAIL);
logTest(
'Audit Log Tampering',
!deleteAuditError || !updateAuditError,
`Delete: ${deleteAuditError?.code || deleteAuditError?.message || 'no error returned'}, Update: ${updateAuditError?.code || updateAuditError?.message || 'no error returned'} (Note: RLS may block silently with no error, check count instead)`
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// FINAL REPORT
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('π SECURITY REPORT');
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
const vulnerabilities = results.filter(r => r.vulnerable);
const protections = results.filter(r => !r.vulnerable);
console.log(`\nβ
PROTECTED: ${protections.length}/${results.length}`);
protections.forEach(r => console.log(` β’ ${r.name}`));
if (vulnerabilities.length > 0) {
console.log(`\nπ¨ VULNERABILITIES FOUND: ${vulnerabilities.length}`);
vulnerabilities.forEach(r => console.log(` β’ ${r.name}: ${r.details}`));
console.log('\nβ οΈ ACTION REQUIRED: Fix these vulnerabilities before production!');
} else {
console.log('\nπ NO VULNERABILITIES FOUND - System is secure!');
}
console.log('\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
// Save results to JSON file
const jsonResults = {
timestamp: new Date().toISOString(),
totalTests: results.length,
vulnerabilities: vulnerabilities.length,
protected: protections.length,
details: results
};
fs.writeFileSync(
path.join(__dirname, 'security_results.json'),
JSON.stringify(jsonResults, null, 2)
);
console.log('\nResults saved to: src/test/security_results.json');
// Cleanup
// Note: In production, you might want to keep test data for investigation
}
runSecurityTests().catch(console.error);