Sanitize/Rewrite HTML на стороне клиента

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

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

Есть ли библиотека, которая может по крайней мере

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

Значит, есть ссылки на JavaScript и примеры?

Ответ 1

Обновление 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, даже если node никогда не привязан к DOM.

Ответ 2

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

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

В проекте Caja есть страница wiki на как использовать Caja в качестве отдельного дезинфицирующего средства на стороне клиента:

  • Оформить исходный код, а затем создать с помощью ant
  • Включите html-sanitizer-minified.js или html-css-sanitizer-minified.js на своей странице
  • Вызов html_sanitize(...)

Работодателю script необходимо выполнить следующие инструкции:

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

Ответ 3

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

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

Ответ 4

Вы не можете предвидеть все возможные странные типы искаженной разметки, которые некоторые браузеры могут отключить, чтобы избежать черных списков, так что не черный список. Существует гораздо больше структур, которые вам нужно удалить, чем просто script/embed/object и обработчики.

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

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

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

Ответ 5

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

ПРИМЕЧАНИЕ. Этот метод определенно не будет работать в 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), чтобы убедиться, что он выполнил запуск перед выполнением каких-либо скриптов. Мне было бы интересно узнать, есть ли у вас какие-либо браузеры, у которых есть проблемы с этим, или с любыми случаями, которые я пропускаю.

Ответ 6

Итак, это 2016 год, и я думаю, что многие из нас теперь используют модули npm в нашем коде. sanitize-html кажется ведущей опцией на npm, хотя есть others.

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

Запустите это в командной строке для установки: 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);

Ответ 7

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,"");
}

-черный список → завершить удаление тега и содержимого

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

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

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

Ответ 8

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

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

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

http://docs.ckeditor.com/#!/api

http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor

Ответ 9

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

Для этого мы написали библиотеку с открытым исходным кодом "только для веб" (то есть "требуется браузер"), 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 (не аффилированный)

Ответ 10

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

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

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

Не тестируется следующее, хотя я использую treewalkers в течение некоторого времени, и они являются одной из самых недооцененных частей JavaScript. Вот список типов node, которые вы можете сканировать, обычно я использую 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.');
 }
}