Обеззараживать / переписывать HTML на стороне клиента - PullRequest
68 голосов
/ 17 ноября 2008

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

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

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

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

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

Ответы [ 10 ]

100 голосов
/ 10 января 2009

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

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


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

Это белый список, а не черный список, но белые списки настраиваются согласно 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.

38 голосов
/ 05 июля 2012

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

Для браузеров, которые не поддерживают Web Workers, мы можем использовать iframe в качестве отдельной среды для работы sanitizer. Timothy Chien имеет polyfill , который делает это, используя 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

16 голосов
/ 10 января 2009

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

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

12 голосов
/ 17 ноября 2008

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

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

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

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

11 голосов
/ 16 февраля 2015

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

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

7 голосов
/ 23 августа 2016

Итак, сейчас 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);

5 голосов
/ 27 мая 2012
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,"");
}

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

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

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

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

1 голос
/ 21 января 2019

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

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

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

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

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

1 голос
/ 16 марта 2016

Предложенная выше библиотека 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

0 голосов
/ 04 июля 2012

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

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#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.');
 }
}
...