Обеззараживать / переписывать HTML на стороне клиента

Мне нужно отображать внешние ресурсы, загруженные через междоменные запросы, и отображать только "безопасный" контент.

Может использовать Prototype's String # stripScripts для удаления блоков скриптов. Но обработчики, такие как onclick или же onerror все еще там.

Есть ли библиотека, которая может хотя бы

  • блоки скриптовых блоков,
  • убить обработчики DOM,
  • удалить черные перечисленные теги (например: embed или же object).

Так есть ли ссылки и примеры, связанные с JavaScript?

11 ответов

Обновление 2016: теперь существует пакет Google Closure, основанный на дезинфицирующем средстве Caja.

Он имеет более чистый API, был переписан с учетом API, доступных в современных браузерах, и лучше взаимодействует с Closure Compiler.


Бесстыдный плагин: см. Caja / plugin / html-sanitizer.js для html-дезинфицирующего средства на стороне клиента, который был тщательно рассмотрен.

Это белый список, не черный список, но белые списки настраиваются в соответствии с CajaWhitelists


Если вы хотите удалить все теги, сделайте следующее:

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, '&lt;');
}

Люди скажут вам, что вы можете создать элемент и назначить innerHTML а затем получить innerText или же textContent и затем избегайте сущностей в этом. Не делай этого. Это уязвимо для инъекций XSS, так как <img src=bogus onerror=alert(1337)> будет управлять onerror обработчик, даже если узел никогда не подключен к DOM.

HTML-дезинфицирующее средство Google Caja можно сделать "готовым к работе в Интернете ", внедрив его в веб-приложение. Любые глобальные переменные, введенные дезинфицирующим средством, будут содержаться внутри рабочего, плюс обработка выполняется в собственном потоке.

Для браузеров, которые не поддерживают Web Workers, мы можем использовать iframe как отдельную среду для работы с дезинфицирующим средством. У Тимоти Чина есть полифилл, который делает именно это, используя iframes для имитации Web Workers, поэтому эта часть для нас уже сделана.

У проекта 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

Никогда не доверяй клиенту. Если вы пишете серверное приложение, предположите, что клиент всегда будет отправлять антисанитарные, вредоносные данные. Это эмпирическое правило, которое избавит вас от неприятностей. Если вы можете, я бы посоветовал сделать все проверки и санитарии в коде сервера, с которым вы знаете (в разумной степени) не будет возиться. Возможно, вы могли бы использовать серверное веб-приложение в качестве прокси для вашего клиентского кода, который выбирается у стороннего производителя и выполняет санитарную обработку перед отправкой его самому клиенту?

[править] Извините, я неправильно понял вопрос. Тем не менее, я придерживаюсь своего совета. Ваши пользователи, вероятно, будут в большей безопасности, если вы очистите сервер перед отправкой им.

Теперь, когда все основные браузеры поддерживают встроенные фреймы с песочницей, я думаю, что существует гораздо более простой способ обеспечения безопасности. Я был бы рад, если бы этот ответ был рассмотрен людьми, которые более знакомы с такой проблемой безопасности.

ПРИМЕЧАНИЕ. Этот метод определенно не будет работать в IE 9 и более ранних версиях. См. Эту таблицу для версий браузера, которые поддерживают песочницу.

Идея состоит в том, чтобы создать скрытый 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), чтобы убедиться, что он был запущен перед выполнением любых сценариев. Мне было бы интересно узнать, есть ли какие-либо браузеры, которые имеют проблемы с этим, или какие-либо крайние случаи, которые я пропускаю.

Итак, это 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);

Вы не можете предвидеть все возможные странные типы искаженной разметки, которые какой-то браузер может отключить, чтобы избежать внесения в черный список, поэтому не создавайте черный список Есть гораздо больше структур, которые вы, возможно, должны удалить, чем просто скрипт / встраивание / объект и обработчики.

Вместо этого попытайтесь разобрать HTML-код в элементы и атрибуты в иерархии, а затем запустите все имена элементов и атрибутов в минимально-возможном белом списке. Также проверьте любые атрибуты URL, которые вы пропускаете, в белый список (помните, что есть более опасные протоколы, чем просто javascript:).

Если вход XHTML правильно сформирован, то первая часть выше будет намного проще.

Как всегда в случае санации HTML, если вы можете найти другой способ избежать этого, сделайте это вместо этого. Есть много, много потенциальных дыр. Если основные службы веб-почты все еще находят эксплойты по прошествии стольких лет, что заставляет вас думать, что вы можете добиться большего успеха?

String.prototype.sanitizeHTML=function (white,black) {
   if (!white) white="b|i|p|br";//allowed tags
   if (!black) black="script|object|embed";//complete remove tags
   var e=new RegExp("(<("+black+")[^>]*>.*</\\2>|(?!<[/]?("+white+")(\\s[^<]*>|[/]>|>))<[^<>]*>|(?!<[^<>\\s]+)\\s[^</>]+(?=[/>]))", "gi");
   return this.replace(e,"");
}

-черный список -> полное удаление тега и содержимого

-белый список -> сохранить теги

другие теги удаляются, но содержимое тегов сохраняется

все атрибуты тегов белого списка (оставшиеся) удалены

[Отказ от ответственности: я один из авторов]

Для этого мы написали библиотеку с открытым исходным кодом "только для веб" (то есть "требуется браузер"), https://github.com/jitbit/HtmlSanitizer которая удаляет все tags/attributes/styles кроме "белых".

Использование:

var input = HtmlSanitizer.SanitizeHtml("<script> Alert('xss!'); </scr"+"ipt>");

PS Работает намного быстрее, чем "чистое JavaScript" решение, так как оно использует браузер для анализа и манипулирования DOM. Если вы заинтересованы в "чистом JS" решении, попробуйте https://github.com/punkave/sanitize-html (не аффилированный)

Предложенная выше библиотека Google Caja была слишком сложной для настройки и включения в мой проект для веб-приложения (то есть для работы в браузере). Вместо этого я прибег к тому, что, поскольку мы уже используем компонент CKEditor, это использование его встроенной функции очистки и внесения в белый список HTML, которую гораздо проще настроить. Таким образом, вы можете загрузить экземпляр CKEditor в скрытый iframe и сделать что-то вроде:

CKEDITOR.instances['myCKEInstance'].dataProcessor.toHtml(myHTMLstring)

Теперь, конечно, если вы не используете CKEditor в своем проекте, это может быть немного излишним, так как сам компонент занимает около половины мегабайта (свернуто), но если у вас есть источники, возможно, вы можете изолировать код, выполняя белый список (CKEDITOR.htmlParser?) и сделать его намного короче.

http://docs.ckeditor.com/

http://docs.ckeditor.com/

Вместо использования регулярных выражений я подумал о способе использования нативного материала DOM. Таким образом, вы можете преобразовать HTML в документ, получить этот HTML и легко получить все определенные элементы и элементы белого списка и атрибуты для удаления. При этом используется список атрибутов либо как массив простых строк атрибутов для разрешения, либо может использоваться регулярное выражение для проверки их значений и разрешения только для определенных тегов.

Я рекомендую вырезать рамки из вашей жизни, в долгосрочной перспективе вам будет намного проще.

cloneNode: клонирование узла копирует все его атрибуты и их значения, но НЕ копирует прослушиватели событий.

https://developer.mozilla.org/en/DOM/Node.cloneNode

Следующее не проверено, хотя я использую treewalker в течение некоторого времени, и они являются одной из наиболее недооцененных частей JavaScript. Вот список типов узлов, которые вы можете сканировать, обычно я использую SHOW_ELEMENT или SHOW_TEXT.

http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html

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.');
 }
}
Другие вопросы по тегам