T09 · Insecure Skill Coding Practices
- Location
bucket-list.html:554- Finding
Stored DOM Cross-Site Scripting Through Unvalidated Wish IDs
- Content
View full analysis
w && typeof w.content === 'string' && w.content.trim()) .map(w => ({ id: String(w.id || generateId()), content: String(w.content || '').trim(), category: String(w.category || '其他'), status: ['pending', 'completed', 'cancelled'].includes(w.status) ? w.status : 'pending', createdAt: String(w.createdAt || new Date().toISOString().split('T')[0]).slice(0, 10), endedAt: w.endedAt ? String(w.endedAt).slice(0, 10) : null, endedBy: w.endedBy || null, completionNote: String(w.completionNote || ''), cancelReason: String(w.cancelReason || ''), timeline: Array.isArray(w.timeline) ? w.timeline : [] })) ``` The identifier is subsequently embedded in a single-quoted inline event-handler attribute and assigned to `innerHTML`: ```javascript container.innerHTML = wishes.map(w => { const idArg = JSON.stringify(String(w.id)); const endedText = w.endedAt ? ` · ${w.status === 'completed' ? '完成于' : '取消于'} ${escapeHtml(w.endedAt)}` : ''; const note = w.completionNote || w.cancelReason || ''; return `${escapeHtml(w.content)}添加于 ${escapeHtml(w.createdAt || '未知')}${endedText}${note ? `${escapeHtml(note)}` : ''}- Remediation
View remediation
completeWish(w.id)); ``` This keeps the identifier in a JavaScript closure rather than serializing it into HTML. 2. **Avoid rendering dynamic records with `innerHTML`.** Create elements with `document.createElement()` and assign untrusted values through `textContent`. 3. **Validate IDs on both client and server.** Reject identifiers outside a strict format and length limit, for example: ```javascript const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/; function validateId(id) { const value = String(id || ''); if (!SAFE_ID.test(value)) { throw new Error('Invalid wish identifier.'); } return value; } ``` Apply equivalent validation in `scripts/bucket-list-data.js` so imported and API-submitted data cannot retain dangerous identifiers. 4. **Regenerate invalid imported IDs.** For compatibility with existing data, replace malformed identifiers with values generated by `generateId()` rather than inserting them into the DOM. 5. **Deploy a restrictive Content Security Policy.** Move the inline script to a separate JavaScript file and configure a policy such as: ```http Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none' ``` A policy without `unsafe-inline` prevents inline event handlers from executing and provides defense in depth. 6. **Add regression tests.** Test imported IDs containing apostrophes, quotation marks, angle brackets, and event-handler payloads, and verify that none create executable DOM nodes or attributes. ]]>
