fix(security): убрать stored-XSS в блоке columns урока (Спринт1 #4)
Блок columns хранит rich-HTML из мини-редактора и рендерился сырым в innerHTML (единственный неэкранированный блок) — учитель мог внедрить <img onerror>/script, исполняемый у каждого ученика (кража JWT из localStorage). Добавлен санитайзер sanitizeRichHtml (инертный template + вырезание on*/script/iframe/javascript:), сохраняет форматирование, но блокирует исполнение. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-1
@@ -897,6 +897,23 @@
|
|||||||
|
|
||||||
/* ── helpers ── */
|
/* ── helpers ── */
|
||||||
function escAll(s) { return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
function escAll(s) { return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
||||||
|
/* Санитайзер rich-HTML (блок columns хранит форматированный HTML из мини-редактора).
|
||||||
|
Парсим в инертный <template> (картинки/скрипты НЕ исполняются), вырезаем опасное,
|
||||||
|
сериализуем обратно. Блокирует on*-обработчики, script/iframe, javascript:/data: URL. */
|
||||||
|
function sanitizeRichHtml(html) {
|
||||||
|
const tpl = document.createElement('template');
|
||||||
|
tpl.innerHTML = String(html || '');
|
||||||
|
tpl.content.querySelectorAll('script,style,iframe,object,embed,link,meta,form,base').forEach(n => n.remove());
|
||||||
|
tpl.content.querySelectorAll('*').forEach(el => {
|
||||||
|
for (const attr of Array.from(el.attributes)) {
|
||||||
|
const name = attr.name.toLowerCase();
|
||||||
|
if (name.startsWith('on')) el.removeAttribute(attr.name);
|
||||||
|
else if (name === 'style') el.removeAttribute(attr.name);
|
||||||
|
else if (/^(href|src|xlink:href)$/.test(name) && /^\s*(javascript|data|vbscript):/i.test(attr.value || '')) el.removeAttribute(attr.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tpl.innerHTML;
|
||||||
|
}
|
||||||
function fmtTime(s) {
|
function fmtTime(s) {
|
||||||
const d = new Date(s && s.includes('T') ? s : (s||'').replace(' ','T')+'Z');
|
const d = new Date(s && s.includes('T') ? s : (s||'').replace(' ','T')+'Z');
|
||||||
const diff = Date.now() - d.getTime();
|
const diff = Date.now() - d.getTime();
|
||||||
@@ -1376,7 +1393,7 @@
|
|||||||
case 'columns': {
|
case 'columns': {
|
||||||
const cols = Array.isArray(d.cols) ? d.cols : [];
|
const cols = Array.isArray(d.cols) ? d.cols : [];
|
||||||
return `<div class="lesson-block block-columns cols-${cols.length}">
|
return `<div class="lesson-block block-columns cols-${cols.length}">
|
||||||
${cols.map(c => `<div class="block-col">${c.content || ''}</div>`).join('')}
|
${cols.map(c => `<div class="block-col">${sanitizeRichHtml(c.content || '')}</div>`).join('')}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user