Parent : [[INDEX-CYBERSECU]]
Case Study: wheel.c8re.store - Anatomy of a Secure Prize Wheel
Context: Analysis performed on
https://wheel.c8re.store/for educational purposes. All findings demonstrate proper security architecture.
1. Architecture Overview
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ CLIENT SIDE │────▶│ PRIZES.PHP │────▶│ DISCORD BOT │
│ (HTML/JS) │ │ (PHP/Backend) │ │ (Validation) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
Canvas rendering Cryptographic hash Manual review
Winwheel.js HMAC signature Code distribution
2. Frontend Analysis
Files Identified
wheel.c8re.store/
├── index.html # Main page
├── Winwheel.js # Canvas wheel rendering
├── scripts.js # Spin logic + API calls
├── styles.css # UI styling
├── ticker.png # Pointer image
└── prizes.php # Backend API (JSON responses)
Key Logic (scripts.js)
function spinWheel() {
fetch('prizes.php?spin=true')
.then(data => {
let prizeIndex = data.prizeIndex + 1;
code = data.prizeCode; // Hash, not actual promo code
title = data.prizeTitle;
seed = data.prizeSeed; // Proof of randomness
// Calculate visual stop angle
let stopAngle = (prizeIndex * (360 / theWheel.numSegments))
- Math.floor(Math.random() * 30);
theWheel.animation.stopAngle = stopAngle;
theWheel.startAnimation();
});
}
Security Note: Frontend only handles visualization. Prize decision is server-side.
3. Backend Analysis
Information Disclosure
Vulnerability: prizes.php accepts ?list=all parameter exposing internal prize structure.
curl "https://wheel.c8re.store/prizes.php?list=all"
Response:
{
"error": "Unknown error",
"prizes": [
{"id": "1", "title": "10% OFF", "chance": "30.000", "auto": "10"},
{"id": "2", "title": "5% OFF", "chance": "35.000", "auto": "5"},
{"id": "3", "title": "15% OFF", "chance": "15.000", "auto": "15"},
{"id": "4", "title": "25% OFF", "chance": "10.000", "auto": "25"},
{"id": "5", "title": "35% OFF", "chance": "5.000", "auto": "0"},
{"id": "6", "title": "50% OFF", "chance": "0.800", "auto": "0"},
{"id": "7", "title": "75% OFF", "chance": "0.300", "auto": "0"},
{"id": "8", "title": "100% OFF", "chance": "0.005", "auto": "0"}
]
}
Prize Distribution Analysis
| Prize | Probability | Type | Distribution |
|---|---|---|---|
| 10% OFF | 30% | Low value | Automatic |
| 5% OFF | 35% | Low value | Automatic |
| 15% OFF | 15% | Low value | Automatic |
| 25% OFF | 10% | Low value | Automatic |
| 35% OFF | 5% | Medium value | Manual (Discord) |
| 50% OFF | 0.8% | High value | Manual (Discord) |
| 75% OFF | 0.3% | High value | Manual (Discord) |
| 100% OFF | 0.005% (1/20,000) | Jackpot | Manual (Discord) |
Hash Analysis
Sample Hash for 5% OFF:
76162c2aaf124c6a7bed7a947cb7b33d
Characteristics:
- Length: 32 chars → MD5
- Pattern: Likely HMAC with server-side secret key
- Format:
MD5(session_id + prize_index + timestamp + SECRET)
Why it's secure:
- Without
SECRET_KEY, cannot forge valid hashes - Timestamp/session binding prevents replay attacks
- Server validates hash before code distribution
4. Attack Vectors Tested
Vector 1: Frontend Manipulation
// Proxy fetch to force specific prize
fetch = new Proxy(fetch, {
apply: (target, thisArg, args) => {
if (args[0].includes('prizes.php?spin=true')) {
return Promise.resolve({
json: () => Promise.resolve({
error: 'Unknown error',
prizeIndex: 7, // Force 75% OFF segment
prizeCode: 'FAKE-HASH',
prizeTitle: '75% OFF',
prizeSeed: 'forced'
})
});
}
return Reflect.apply(target, thisArg, args);
}
});
Result: ✅ Visual manipulation works (wheel stops on 75%) Impact: ❌ Hash invalid for Discord validation
Vector 2: Information Gathering
# Test hidden parameters
prizes.php?debug=1
prizes.php?admin=true
prizes.php?export=csv
prizes.php?dump=true
Result: All return same data as ?list=all (insufficient access control)
Impact: Information disclosure only, no code execution
Vector 3: Multi-Accounting
- VPN rotation for IP diversity
- Clear cookies between sessions
- Create multiple accounts
Result: Technically possible but:
- Each spin still server-controlled
- 100% probability remains 0.005%
- Discord validation blocks automated abuse
5. Security Assessment
Strengths ✅
- Server-side randomization - Client cannot influence outcome
- Cryptographic validation - HMAC hashes prevent forgery
- Hybrid distribution - High-value prizes require manual review
- Session binding - Hash tied to specific user session
- Visual/actual separation - Wheel display ≠ prize validation
Weaknesses ⚠️
- Information disclosure -
?list=allexposes prize probabilities - Parameter pollution - Multiple unused params accepted
- No rate limiting observed - Could allow excessive spinning
Risk Rating: MEDIUM
Frontend is manipulable but backend validation prevents actual abuse.
6. Lessons Learned
For Defenders (Building Similar Systems)
// DO: Hide prize configuration
if (!isAdmin($_SESSION)) {
http_response_code(403);
exit('Access denied');
}
// DO: Use strong HMAC
$hash = hash_hmac('sha256', $user_id . $prize_id . $timestamp, $secret_key);
// DO: Validate on redemption
if (!verifyHMAC($submitted_hash, $user_id, $prize_id, $timestamp)) {
logSuspiciousActivity($user_id);
exit('Invalid code');
}
// DON'T: Expose internal config via URL params
// DON'T: Trust client-side validation alone
For Attackers (Educational)
- Always check server-side validation
- Information disclosure ≠ exploitable vulnerability
- HMAC/crypto prevents result manipulation
- Manual review layers add security depth
7. Conclusion
wheel.c8re.store demonstrates defense in depth:
- Frontend is cosmetic only
- Backend controls actual prize distribution
- Cryptographic hashes prevent forgery
- Human validation for high-value prizes
The 100% OFF jackpot remains statistically and technically protected.
References:
- MD5 Hash Analysis: https://en.wikipedia.org/wiki/MD5
- HMAC: RFC 2104
- Winwheel.js: http://dougtesting.net/winwheel