T09 · Insecure Skill Coding Practices
- Location
assets/rhythm-game.html:952- Finding
Stored DOM-Based Cross-Site Scripting in the Local Leaderboard
- Content
View full analysis
{ const row = document.createElement('tr'); const rankClass = index < 3 ? `rank-${index + 1}` : ''; row.innerHTML = ` ${index + 1} ${entry.playerName} ${entry.score.toLocaleString()} ${entry.maxCombo} ${entry.difficulty.toUpperCase()} ${entry.date} `; tbody.appendChild(row); }); ``` ### Technical Analysis The leaderboard reads JSON records from `localStorage` and interpolates their properties directly into an HTML template assigned to `row.innerHTML`. No output encoding, schema validation, or type validation is applied to `playerName`, `score`, `maxCombo`, `difficulty`, or `date`. Although the visible player-name input has a `maxlength` attribute, this is only a user-interface restriction. Browser storage is mutable and is not a trusted security boundary. A malicious or corrupted leaderboard record can therefore contain HTML elements with executable event handlers. When `showLeaderboard()` renders such a record, the browser parses the attacker-controlled value as markup rather than displaying it as plain text. ### Attack Path 1. An attacker or another same-origin script places a crafted JSON record in the `rhythmLeaderboard` local-storage key. 2. The victim opens the local leaderboard. 3. `showLeaderboard()` parses the stored record. 4. Attacker-controlled record properties are inserted into `row.innerHTML`. 5. The browser parses the injected ...[truncated 839 chars]- Remediation
View remediation
{ const row = document.createElement('tr'); const rankClass = index < 3 ? `rank-${index + 1}` : ''; appendTextCell(row, index + 1, rankClass); appendTextCell(row, entry.playerName); appendTextCell(row, entry.score.toLocaleString()); appendTextCell(row, entry.maxCombo); appendTextCell(row, entry.difficulty.toUpperCase()); appendTextCell(row, entry.date); tbody.appendChild(row); }); ``` ]]>
