Мне нужно отображать внешние ресурсы, загруженные через междоменные запросы, и следить за тем, чтобы отображался только контент «безопасно».
Можно использовать Строка # stripScripts Prototype для удаления блоков скрипта. Но такие обработчики, как onclick или onerror, все еще существуют.
Есть ли какая-нибудь библиотека, которая может хотя бы
embed или object).Так есть ли какие-нибудь ссылки и примеры, связанные с JavaScript?
Насколько это безопасно? Пользователи не могут редактировать javascript страницы?
да, это небезопасно, если вы просто не пытаетесь предотвратить ошибки доверенных пользователей.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы не можете предвидеть все возможные странные типы искаженной разметки, которые какой-то браузер где-то может споткнуться, чтобы избежать попадания в черный список, поэтому не заносите в черный список. Вам может потребоваться удалить больше структур много, чем просто script / embed / object и обработчики.
Вместо этого попытайтесь разобрать HTML на элементы и атрибуты в иерархии, а затем запустить все имена элементов и атрибутов в минимально возможном белом списке. Также проверьте все пропускаемые атрибуты URL-адресов в белом списке (помните, что существуют более опасные протоколы, чем просто javascript :).
Если ввод - это правильно сформированный XHTML, первая часть вышеизложенного намного проще.
Как всегда с дезинфекцией HTML, если вы можете найти другой способ избежать этого, сделайте это вместо этого. Есть много-много потенциальных ям. Если основные службы веб-почты по прошествии многих лет все еще находят уязвимости, что заставляет вас думать, что вы можете добиться большего?
Обновление 2016: теперь существует пакет Закрытие Google на основе дезинфицирующего средства Caja.
Он имеет более чистый API, был переписан с учетом API, доступных в современных браузерах, и лучше взаимодействует с Closure Compiler.
Бесстыдный плагин: см. caja / плагин / html-sanitizer.js, чтобы узнать о дезинфицирующем средстве для HTML на стороне клиента, которое было тщательно проверено.
Он внесен в белый список, а не в черный, но белые списки настраиваются в соответствии с Caja
Если вы хотите удалить все теги, сделайте следующее:
var tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
var tagOrComment = new RegExp(
'<(?:'
// Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)'
// Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
// Regular name
+ '|/?[a-z]'
+ tagBody
+ ')>',
'gi');
function removeTags(html) {
var oldHtml;
do {
oldHtml = html;
html = html.replace(tagOrComment, '');
} while (html !== oldHtml);
return html.replace(/</g, '<');
}
Люди скажут вам, что вы можете создать элемент и назначить innerHTML, а затем получить innerText или textContent, а затем экранировать сущности в нем. Не делай этого. Он уязвим для внедрения XSS, поскольку <img src=bogus onerror=alert(1337)> будет запускать обработчик onerror, даже если узел никогда не подключен к DOM.
Отлично, похоже, здесь есть небольшая документация: code.google.com/p/google-caja/wiki/JsHtmlSanitizer
Возможно ли использование дезинфицирующего средства на стороне клиента без какого-либо HTML-тега в белом списке? Даже когда я модифицировал JSON, он по-прежнему действует, поскольку значения по умолчанию занесены в белый список ...
@ Альмад, может я неправильно понял твой вопрос. Если вы изменяете белые списки, вам необходимо повторно сгенерировать html4-defs.js, который представляет собой файл JavaScript, сгенерированный из JSON. Это связано с бегущим муравьем.
Код дезинфицирующего средства Caja HTML выглядит великолепно, но требует некоторого связующего кода (соседний cssparser.js, но, что более важно, объект html4). Кроме того, это загрязняет глобальное свойство window. Есть ли версия этого кода для Интернета? Если нет, видите ли вы лучший способ создать и поддерживать его, чем создавать для него отдельный проект?
@phihag, Спроси у google-caja-обсудить и могут указать на упакованный. Я считаю, что загрязнение оконных объектов предназначено для обратной совместимости, и поэтому любой новой версии пакета это может не понадобиться.
Оказывается, уже существует это пакет для веб-браузеров.
@phihag Этот пакет предназначен для nodejs, а не для браузеров.
@MikeSamuel Я думаю, while(html !== (html = html.replace(tagOrComment, ''))){} можно использовать вместо var oldHtml; do {oldHtml = html; html = html.replace(tagOrComment, ''); } while (html !== oldHtml);
Что вы думаете об идее использования iframe в песочнице (конечно, с отключенным js) для синтаксического анализа HTML, а затем копирования этого дерева DOM и исключения небезопасных элементов? См., Например, код в моем новом ответе на этот вопрос.
@aldel, allow-same-origin еще не разрешает скрипты в атрибутах? А как насчет мета-перенаправления родительских фреймов? Возможно, display:none предотвращает утечку через загрузку изображений и трюки CSS, но не через загрузку таблиц стилей. Встроенные фреймы по-прежнему позволяют управлять загрузками.
@MikeSamuel, нет, вам понадобится allow-scripts для запуска любых скриптов, в том числе в атрибутах. allow-same-origin необходим для доступа к DOM документа в iframe. Я думаю, что другие проблемы, о которых вы упомянули, не должны быть проблемой, поскольку весь смысл песочницы заключается в том, чтобы разрешить отображение ненадежного HTML на вашей странице. Например, мета-перенаправления родительских страниц (если я неправильно понимаю, что вы имеете в виду) были бы невозможны без разрешения allow-top-navigation.
Меня больше беспокоит (1) мой код обнаружения песочницы дает ложное срабатывание; то есть он думает, что браузер полностью поддерживает песочницу, когда это не так; или (2) в очищенное дерево DOM копируется что-то, что на самом деле небезопасно, например тег изображения, использующий уязвимость в декодере GIF или что-то в этом роде.
@MikeSamuel, этот сценарий - лучшее, что я видел. Есть ли способ сделать это, при котором в следующем примере также будет отображаться строка «Моя ссылка»? <a href='#'> Моя ссылка </a>
@MikeSamuel Я попытался использовать вышеупомянутый тег img и экранировать html с помощью create elm, назначить innerHTML и вернуть его innerHTML элементу здесь: jsbin.com/butofezipi/edit?html,css,js,output Я не знаю, сделал ли я это неправильно ... но похоже, что метод кодирования делает работать на этот экземпляр? Просто пытаюсь сыграть адвоката дьявола. Я сам ищу хороший метод: b Просто хотел отметить, что метод, который вы предлагаете, соответствует моим потребностям, поэтому спасибо!
ужасный код ... вот что вы получаете, когда заставляете разработчиков C++ / JAVA писать Javascript.
Сработает ли назначить innerText, а затем получить innerHTML?
@ArlenBeiler, что бы конвертировать <b>foo</b> в <b>foo</b>, верно? Это, вероятно, не решит проблему OP.
Да, верно. Просто подумал, что спрошу, так как я так думал, но ты прав, это не решит исходный вопрос.
Никогда не доверяйте клиенту. Если вы пишете серверное приложение, предполагайте, что клиент всегда будет отправлять антисанитарные вредоносные данные. Это эмпирическое правило, которое убережет вас от неприятностей. Если вы можете, я бы посоветовал провести всю проверку и санацию в коде сервера, который, как вы знаете (в разумной степени), не будет возиться. Возможно, вы могли бы использовать серверное веб-приложение в качестве прокси для вашего клиентского кода, который извлекается от третьей стороны и выполняет очистку перед отправкой самому клиенту?
[править] Простите, я неправильно понял вопрос. Однако я верю своему совету. Ваши пользователи, вероятно, будут в большей безопасности, если вы продезинфицируете сервер перед отправкой им.
Фактически, с ростом популярности node.js решение javascript также может быть серверным решением. По крайней мере, так я здесь оказался. Тем не менее, это отличный совет, по которому нужно жить.
Я рекомендую исключить рамки из вашей жизни, это значительно упростит вам жизнь в долгосрочной перспективе.
cloneNode: при клонировании узла копируются все его атрибуты и их значения, кроме копирует ли НЕТ слушателей событий.
https://developer.mozilla.org/en/DOM/Node.cloneNode
Следующее не проверено, хотя я уже некоторое время использую Treewalkers, и они являются одной из самых недооцененных частей JavaScript. Вот список типов узлов, которые вы можете сканировать, обычно я использую SHOW_ELEMENT или SHOW_TEXT.
http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-NodeFilter
function xhtml_cleaner(id)
{
var e = document.getElementById(id);
var f = document.createDocumentFragment();
f.appendChild(e.cloneNode(true));
var walker = document.createTreeWalker(f,NodeFilter.SHOW_ELEMENT,null,false);
while (walker.nextNode())
{
var c = walker.currentNode;
if (c.hasAttribute('contentEditable')) {c.removeAttribute('contentEditable');}
if (c.hasAttribute('style')) {c.removeAttribute('style');}
if (c.nodeName.toLowerCase()=='script') {element_del(c);}
}
alert(new XMLSerializer().serializeToString(f));
return f;
}
function element_del(element_id)
{
if (document.getElementById(element_id))
{
document.getElementById(element_id).parentNode.removeChild(document.getElementById(element_id));
}
else if (element_id)
{
element_id.parentNode.removeChild(element_id);
}
else
{
alert('Error: the object or element \'' + element_id + '\' was not found and therefore could not be deleted.');
}
}
Этот код предполагает, что входные данные для очистки уже проанализированы и даже вставлены в дерево документа. В этом случае вредоносные скрипты уже выполнены. Ввод должен быть строкой.
Затем отправьте ему фрагмент DOM, просто потому, что он находится в DOM в заданной форме или форме, на самом деле не означает, что он был выполнен. Предполагая, что он загружает его через AJAX, он может использовать это вместе с importNode.
Google Caja Дезинфицирующее средство HTML можно сделать "готовым к работе в сети", встроив его в веб-работник. Любые глобальные переменные, введенные дезинфицирующим средством, будут содержаться внутри рабочего, плюс обработка происходит в его собственном потоке.
Для браузеров, которые не поддерживают веб-воркеров, мы можем использовать iframe как отдельную среду для работы дезинфицирующего средства. У Тимоти Чиена есть полифил, который делает именно это, используя фреймы для имитации веб-воркеров, так что эта часть сделана за нас.
У проекта Caja есть вики-страница на как использовать Caja в качестве автономного дезинфицирующего средства на стороне клиента:
ant.html-sanitizer-minified.js или html-css-sanitizer-minified.js на свою страницуhtml_sanitize(...)Рабочий сценарий должен только следовать этим инструкциям:
importScripts('html-css-sanitizer-minified.js'); // or 'html-sanitizer-minified.js'
var urlTransformer, nameIdClassTransformer;
// customize if you need to filter URLs and/or ids/names/classes
urlTransformer = nameIdClassTransformer = function(s) { return s; };
// when we receive some HTML
self.onmessage = function(event) {
// sanitize, then send the result back
postMessage(html_sanitize(event.data, urlTransformer, nameIdClassTransformer));
};
(Для работы библиотеки simworker требуется немного больше кода, но это не важно для данного обсуждения.)
Демо: https://dl.dropbox.com/u/291406/html-sanitize/demo.html
Отличный ответ. Джеффри, не могли бы вы объяснить, почему санитарная обработка должна выполняться веб-воркером?
@AustinWang Web-воркеры не являются строго обязательными, но поскольку очистка потенциально может быть дорогостоящей в вычислительном отношении и не требует взаимодействия с пользователем, она хорошо подходит для этой задачи. (Я также упомянул, что в основном ответе есть глобальные переменные.)
Я не могу найти достойную документацию для этой библиотеки. Где / как мне указать белый список элементов и атрибутов?
@AsGoodAsItGets Как описано в комментарий в текущей версии, nameIdClassTransformer вызывается для каждого имени HTML, идентификатора элемента и списка классов; возврат null удалит атрибут. Редактируя файлы JSON в SRC / com / google / caja / lang / html, вы также можете настроить, какие элементы и атрибуты будут добавлены в белый список.
@JefferyTo извини, может я слишком тупой, но я не понимаю. Файлы JSON, на которые вы ссылаетесь, не используются в приведенном выше примере и демонстрации. Я хочу использовать библиотеку в браузере, поэтому посмотрел вашу демонстрацию. Можете ли вы изменить приведенную выше функцию nameIdClassTranformer, например. отклонить все теги <script> и принять теги <b> и <i>?
@AsGoodAsItGets Вам необходимо проверить исходный код, отредактировать файлы JSON, а затем запустить ant для создания файлов JS, подходящих для вашего варианта использования.
Uncaught ReferenceError: importScripts is not defined на FF / Chrome
@vsync Вы пробовали внутри Веб-воркер?
ха, я думал, что сам скрипт был скриптом, который был выгружен веб-воркеру в основном потоке браузера. вы вызываете это из капли?
@vsync Нет, капли не нужны; Предлагаю взглянуть на статью о веб-воркерах выше.
Теперь, когда все основные браузеры поддерживают изолированные фреймы iframe, существует гораздо более простой способ защиты считать. Я был бы рад, если бы этот ответ мог быть просмотрен людьми, более знакомыми с такого рода проблемами безопасности.
ПРИМЕЧАНИЕ. Этот метод определенно не будет работать в IE 9 и более ранних версиях. См. этот стол для версий браузеров, поддерживающих песочницу. (Примечание: таблица, похоже, говорит, что это не будет работать в Opera Mini, но я только что попробовал, и это сработало.)
Идея состоит в том, чтобы создать скрытый iframe с отключенным JavaScript, вставить в него ненадежный HTML и позволить ему проанализировать его. Затем вы можете пройтись по дереву DOM и скопировать теги и атрибуты, которые считаются безопасными.
Показанные здесь белые списки являются лишь примерами. Что лучше всего добавить в белый список, будет зависеть от приложения. Если вам нужна более сложная политика, чем просто белые списки тегов и атрибутов, это можно сделать с помощью этого метода, но не с помощью этого примера кода.
var tagWhitelist_ = {
'A': true,
'B': true,
'BODY': true,
'BR': true,
'DIV': true,
'EM': true,
'HR': true,
'I': true,
'IMG': true,
'P': true,
'SPAN': true,
'STRONG': true
};
var attributeWhitelist_ = {
'href': true,
'src': true
};
function sanitizeHtml(input) {
var iframe = document.createElement('iframe');
if (iframe['sandbox'] === undefined) {
alert('Your browser does not support sandboxed iframes. Please upgrade to a modern browser.');
return '';
}
iframe['sandbox'] = 'allow-same-origin';
iframe.style.display = 'none';
document.body.appendChild(iframe); // necessary so the iframe contains a document
iframe.contentDocument.body.innerHTML = input;
function makeSanitizedCopy(node) {
if (node.nodeType == Node.TEXT_NODE) {
var newNode = node.cloneNode(true);
} else if (node.nodeType == Node.ELEMENT_NODE && tagWhitelist_[node.tagName]) {
newNode = iframe.contentDocument.createElement(node.tagName);
for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
if (attributeWhitelist_[attr.name]) {
newNode.setAttribute(attr.name, attr.value);
}
}
for (i = 0; i < node.childNodes.length; i++) {
var subCopy = makeSanitizedCopy(node.childNodes[i]);
newNode.appendChild(subCopy, false);
}
} else {
newNode = document.createDocumentFragment();
}
return newNode;
};
var resultElement = makeSanitizedCopy(iframe.contentDocument.body);
document.body.removeChild(iframe);
return resultElement.innerHTML;
};
Вы можете попробовать здесь.
Обратите внимание, что в этом примере я запрещаю атрибуты стиля и теги. Если вы позволите им, вы, вероятно, захотите проанализировать CSS и убедиться, что он безопасен для ваших целей.
Я тестировал это в нескольких современных браузерах (Chrome 40, Firefox 36 Beta, IE 11, Chrome для Android) и в одном старом (IE 8), чтобы убедиться, что он вышел из строя перед выполнением каких-либо скриптов. Мне было бы интересно узнать, есть ли какие-либо браузеры, у которых есть проблемы с этим, или какие-либо крайние случаи, которые я упускаю.
Этот пост заслуживает внимания экспертов, так как это кажется очевидным и простым решением. Это действительно безопасно?
Как можно программно создать скрытый iframe «с отключенным JavaScript»? Насколько мне известно, это невозможно. В ту минуту, когда вы выполните iframe.contentDocument.body.innerHTML = input, все теги сценария будут выполнены.
@AsGoodAsItGets - найдите атрибут песочницы в окнах iframe.
@aldel Действительно, я не знал об этом. Для нас это все еще непозволительно из-за отсутствия поддержки в IE9. Думаю, ваше решение может сработать, но я думаю, вам следует пояснить в своем ответе, что вы зависите от атрибута sandbox.
Извините, я подумал, что это ясно из моего открытия «Теперь, когда все основные браузеры поддерживают изолированные фреймы в песочнице». Я добавлю менее тонкое замечание.
@vsync У меня это работает в Firefox 53, и я почти уверен, что он работал в любой более ранней версии, которая была выпущена, когда я писал ответ (44, я думаю). Может быть, у вас есть плагин, который мешает?
@aldel - извините, мой VPN по какой-то причине блокирует файл сценария.
Атрибуты @pwray href могут содержать javascript: <a href = "javascript: alert('hello')">Test</a>
Предложенная выше библиотека Google Caja была слишком сложной для настройки и включения в мой проект веб-приложения (то есть, работающего в браузере). Вместо этого, поскольку мы уже используем компонент CKEditor, я прибег к использованию встроенной функции очистки HTML и внесения в белый список, которую гораздо проще настроить. Итак, вы можете загрузить экземпляр CKEditor в скрытый iframe и сделать что-то вроде:
CKEDITOR.instances['myCKEInstance'].dataProcessor.toHtml(myHTMLstring)
Конечно, если вы не используете CKEditor в своем проекте, это может быть немного излишним, поскольку размер самого компонента составляет около половины мегабайта (минимизирован), но если у вас есть исходники, возможно, вы можете изолировать код, выполняющий белый список (CKEDITOR.htmlParser?) и сделать его намного короче.
Итак, это 2016 год, и я думаю, что многие из нас сейчас используют модули npm в нашем коде. sanitize-html кажется ведущим параметром в npm, хотя есть другие.
Другие ответы на этот вопрос дают отличный вклад в то, как свернуть свой собственный, но это достаточно сложная проблема, и хорошо протестированные решения сообщества, вероятно, являются лучшим ответом.
Запустите это в командной строке для установки:
npm install --save sanitize-html
ES5:
var sanitizeHtml = require('sanitize-html');
// ...
var sanitized = sanitizeHtml(htmlInput);
ES6:
import sanitizeHtml from 'sanitize-html';
// ...
let sanitized = sanitizeHtml(htmlInput);
2018 здесь, это тяжеловато (пол мегабайта зависимостей)
2020 здесь, sanitize-html предназначен для Node, и, насколько я могу судить, до сих пор нет хорошего варианта для браузеров
[Отказ от ответственности: я один из авторов]
Мы написали для этого библиотеку с открытым исходным кодом «только для Интернета» (т.е. «требуется браузер»), https://github.com/jitbit/HtmlSanitizer, которая удаляет все tags/attributes/styles, кроме «белых».
Использование:
var input = HtmlSanitizer.SanitizeHtml("<script> Alert('xss!'); </scr"+"ipt>");
P.S. Работает намного быстрее, чем решение «на чистом JavaScript», поскольку оно использует браузер для анализа и управления DOM. Если вас интересует "чистое JS" решение, попробуйте https://github.com/punkave/sanitize-html (не аффилированный)
Вместо использования регулярного выражения я подумал о способе использования нативных материалов DOM. Таким образом, вы можете преобразовать HTML в документ, получить этот HTML и легко получить все определенные элементы, а также элементы и атрибуты белого списка для удаления. При этом используется список атрибутов либо в виде массива простых строк разрешенных атрибутов, либо он может использовать регулярное выражение для проверки их значений и разрешать только определенные теги.
const sanitize = (html, tags = undefined, attributes = undefined) => {
var attributes = attributes || [
{ attribute: "src", tags: "*", regex: /^(?:https|http|//):/ },
{ attribute: "href", tags: "*", regex: /^(?!javascript:).+/ },
{ attribute: "width", tags: "*", regex: /^[0-9]+$/ },
{ attribute: "height", tags: "*", regex: /^[0-9]+$/ },
{ attribute: "id", tags: "*", regex: /^[a-zA-Z]+$/ },
{ attribute: "class", tags: "*", regex: /^[a-zA-Z ]+$/ },
{ attribute: "value", tags: ["INPUT", "TEXTAREA"], regex: /^.+$/ },
{ attribute: "checked", tags: ["INPUT"], regex: /^(?:true|false)+$/ },
{
attribute: "placeholder",
tags: ["INPUT", "TEXTAREA"],
regex: /^.+$/,
},
{
attribute: "alt",
tags: ["IMG", "AREA", "INPUT"],
//"^" and "$" match beggining and end
regex: /^[0-9a-zA-Z]+$/,
},
{ attribute: "autofocus", tags: ["INPUT"], regex: /^(?:true|false)+$/ },
{ attribute: "for", tags: ["LABEL", "OUTPUT"], regex: /^[a-zA-Z0-9]+$/ },
]
var tags = tags || [
"I",
"P",
"B",
"BODY",
"HTML",
"DEL",
"INS",
"STRONG",
"SMALL",
"A",
"IMG",
"CITE",
"FIGCAPTION",
"ASIDE",
"ARTICLE",
"SUMMARY",
"DETAILS",
"NAV",
"TD",
"TH",
"TABLE",
"THEAD",
"TBODY",
"NAV",
"SPAN",
"BR",
"CODE",
"PRE",
"BLOCKQUOTE",
"EM",
"HR",
"H1",
"H2",
"H3",
"H4",
"H5",
"H6",
"DIV",
"MAIN",
"HEADER",
"FOOTER",
"SELECT",
"COL",
"AREA",
"ADDRESS",
"ABBR",
"BDI",
"BDO",
]
attributes = attributes.map((el) => {
if (typeof el === "string") {
return { attribute: el, tags: "*", regex: /^.+$/ }
}
let output = el
if (!el.hasOwnProperty("tags")) {
output.tags = "*"
}
if (!el.hasOwnProperty("regex")) {
output.regex = /^.+$/
}
return output
})
var el = new DOMParser().parseFromString(html, "text/html")
var elements = el.querySelectorAll("*")
for (let i = 0; i < elements.length; i++) {
const current = elements[i]
let attr_list = get_attributes(current)
for (let j = 0; j < attr_list.length; j++) {
const attribute = attr_list[j]
if (!attribute_matches(current, attribute)) {
current.removeAttribute(attr_list[j])
}
}
if (!tags.includes(current.tagName)) {
current.remove()
}
}
return el.documentElement.innerHTML
function attribute_matches(element, attribute) {
let output = attributes.filter((attr) => {
let returnval =
attr.attribute === attribute &&
(attr.tags === "*" || attr.tags.includes(element.tagName)) &&
attr.regex.test(element.getAttribute(attribute))
return returnval
})
return output.length > 0
}
function get_attributes(element) {
for (
var i = 0, atts = element.attributes, n = atts.length, arr = [];
i < n;
i++
) {
arr.push(atts[i].nodeName)
}
return arr
}
}* {
font-family: sans-serif;
}
textarea {
width: 49%;
height: 300px;
padding: 10px;
box-sizing: border-box;
resize: none;
}<h1>Sanitize HTML client side</h1>
<textarea id='input' placeholder = "Unsanitized HTML">
<!-- This removes both the src and onerror attributes because src is not a valid url. -->
<img src="error" onerror="alert('XSS')">
<div id="something_harmless" onload="alert('More XSS')">
<b>Bold text!</b> and <em>Italic text!</em>, some more text. <del>Deleted text!</del>
</div>
<script>
alert("This would be XSS");
</script>
</textarea>
<textarea id='output' placeholder = "Sanitized HTML will appear here" readonly></textarea>
<script>
document.querySelector("#input").onkeyup = () => {
document.querySelector("#output").value = sanitize(document.querySelector("#input").value);
}
</script>
Не доверяйте ответам, которые могут сделать это с помощью регулярных выражений stackoverflow.com/questions/1732348/…