T09 · Insecure Skill Coding Practices
Error
- Location
- assets/campaign-mode.html:2142
- Finding
- Unauthenticated Network Messages Enable DOM-Based Cross-Site Scripting<![CDATA[ ## Vulnerability Details **File Location**: `assets/campaign-mode.html:2142-2144, 2164, 2183-2192`; `assets/skirmish-mode.html:2608-2610, 2631, 2651-2664, 2814`; `net-server.js:91-106` **Vulnerability Type**: DOM-based cross-site scripting through untrusted multiplayer state **Risk Level**: High ### Vulnerable Code Campaign mode accepts arbitrary relay messages and dispatches them according to the attacker-controlled `t` property: ```js ws=new WebSocket(opts.url||wsUrlVal); ws.onopen=()=>{ try{ ws.send(JSON.stringify({t:'HELLO', room, role})); }catch(e){} flush(); }; ws.onmessage=e=>{ let m; try{ m=JSON.parse(e.data); }catch(e){ return; } if(m&&m.t&&h[m.t]) h[m.t](m); }; ``` The campaign snapshot contains rendered HTML: ```js function snapshot(){ return { t:'STATE', turn, weather, combo, over:battleResolved, winner:lastWinner, turnCount, units: JSON.parse(JSON.stringify(units)), terrain: JSON.parse(JSON.stringify(terrain)), scenario: JSON.parse(JSON.stringify(scenario)), logHtml: (document.getElementById('log')?document.getElementById('log').innerHTML:''), myFormation, enemyFormation, myStrats, enStrats, myStratGenName, enStratGenName, stratUsed: {me:stratUsed.me, en:stratUsed.en}, myCountry, enemyCountry }; } ``` A received `STATE` message is trusted and its `logHtml` property is assigned directly to `innerHTML`: ```js function onClientState(m){ units = m.units.map(u=>Object.assign({},u)); terrain = m.terrain; scenario = m.scenario; turn=m.turn; weather=m.weather; combo=m.combo; turnCount=m.turnCount; battleResolved=m.over; lastWinner=m.winner; myCountry=m.enemyCountry; enemyCountry=m.myCountry; myFormation=m.enemyFormation; enemyFormation=m.myFormation; myStrats=m.enStrats; enStrats=m.myStrats; myStratGenName=m.enStratGenName; enStratGenName=m.myStratGenName; stratUsed={me:m.stratUsed.en, en:m.stratUsed.me}; PLAYER=m.enemyCountry; battleEnemyKey=m.myCountry; currentBattleO=null; const le=docu ...[truncated 2954 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not transmit rendered HTML. Replace `logHtml` and HTML-formatted `logLines` with structured records containing fixed event types and plain-text fields. 2. Render all remote text with `textContent`, not `innerHTML`. 3. If limited formatting is required, construct approved DOM elements programmatically and assign every dynamic value through `textContent`. 4. Validate each received message against a strict schema before dispatch: - Require an exact set of properties. - Enforce property types, lengths, and enumerated values. - Reject unknown fields and unexpected message types. 5. Authenticate each room member and authorize which role may send `STATE`. 6. Add a restrictive Content Security Policy that disallows inline script and inline event handlers. 7. Treat BroadcastChannel messages as untrusted as well; another same-origin page can send forged channel messages. 8. Add automated tests using malicious strings in every remotely supplied field to verify that no markup is interpreted. ]]>
