Очистить / перезаписать HTML на стороне клиента

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

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

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

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

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

Не доверяйте ответам, которые могут сделать это с помощью регулярных выражений stackoverflow.com/questions/1732348/…

Mikko Ohtamaa 08.07.2012 13:39

Насколько это безопасно? Пользователи не могут редактировать javascript страницы?

Daniel says Reinstate Monica 14.06.2015 23:44

да, это небезопасно, если вы просто не пытаетесь предотвратить ошибки доверенных пользователей.

Scott 27.08.2016 21:51
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
83
4
95 636
10

Ответы 10

Вы не можете предвидеть все возможные странные типы искаженной разметки, которые какой-то браузер где-то может споткнуться, чтобы избежать попадания в черный список, поэтому не заносите в черный список. Вам может потребоваться удалить больше структур много, чем просто 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, '&lt;');
}

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

Отлично, похоже, здесь есть небольшая документация: code.google.com/p/google-caja/wiki/JsHtmlSanitizer

tmcw 11.10.2011 21:36

Возможно ли использование дезинфицирующего средства на стороне клиента без какого-либо HTML-тега в белом списке? Даже когда я модифицировал JSON, он по-прежнему действует, поскольку значения по умолчанию занесены в белый список ...

Almad 11.05.2012 21:23

@ Альмад, может я неправильно понял твой вопрос. Если вы изменяете белые списки, вам необходимо повторно сгенерировать html4-defs.js, который представляет собой файл JavaScript, сгенерированный из JSON. Это связано с бегущим муравьем.

Mike Samuel 11.05.2012 22:38

Код дезинфицирующего средства Caja HTML выглядит великолепно, но требует некоторого связующего кода (соседний cssparser.js, но, что более важно, объект html4). Кроме того, это загрязняет глобальное свойство window. Есть ли версия этого кода для Интернета? Если нет, видите ли вы лучший способ создать и поддерживать его, чем создавать для него отдельный проект?

phihag 03.07.2012 22:10

@phihag, Спроси у google-caja-обсудить и могут указать на упакованный. Я считаю, что загрязнение оконных объектов предназначено для обратной совместимости, и поэтому любой новой версии пакета это может не понадобиться.

Mike Samuel 03.07.2012 23:16

Оказывается, уже существует это пакет для веб-браузеров.

phihag 04.07.2012 16:00

@phihag Этот пакет предназначен для nodejs, а не для браузеров.

Jeffery To 08.07.2012 05:39

@MikeSamuel Я думаю, while(html !== (html = html.replace(tagOrComment, ''))){} можно использовать вместо var oldHtml; do {oldHtml = html; html = html.replace(tagOrComment, ''); } while (html !== oldHtml);

Oriol 31.07.2013 15:20

Что вы думаете об идее использования iframe в песочнице (конечно, с отключенным js) для синтаксического анализа HTML, а затем копирования этого дерева DOM и исключения небезопасных элементов? См., Например, код в моем новом ответе на этот вопрос.

aldel 16.02.2015 05:21

@aldel, allow-same-origin еще не разрешает скрипты в атрибутах? А как насчет мета-перенаправления родительских фреймов? Возможно, display:none предотвращает утечку через загрузку изображений и трюки CSS, но не через загрузку таблиц стилей. Встроенные фреймы по-прежнему позволяют управлять загрузками.

Mike Samuel 16.02.2015 19:56

@MikeSamuel, нет, вам понадобится allow-scripts для запуска любых скриптов, в том числе в атрибутах. allow-same-origin необходим для доступа к DOM документа в iframe. Я думаю, что другие проблемы, о которых вы упомянули, не должны быть проблемой, поскольку весь смысл песочницы заключается в том, чтобы разрешить отображение ненадежного HTML на вашей странице. Например, мета-перенаправления родительских страниц (если я неправильно понимаю, что вы имеете в виду) были бы невозможны без разрешения allow-top-navigation.

aldel 16.02.2015 21:07

Меня больше беспокоит (1) мой код обнаружения песочницы дает ложное срабатывание; то есть он думает, что браузер полностью поддерживает песочницу, когда это не так; или (2) в очищенное дерево DOM копируется что-то, что на самом деле небезопасно, например тег изображения, использующий уязвимость в декодере GIF или что-то в этом роде.

aldel 16.02.2015 21:14

@MikeSamuel, этот сценарий - лучшее, что я видел. Есть ли способ сделать это, при котором в следующем примере также будет отображаться строка «Моя ссылка»? <a href='#'> Моя ссылка </a>

chris.nesbit1 15.05.2015 19:45

@MikeSamuel Я попытался использовать вышеупомянутый тег img и экранировать html с помощью create elm, назначить innerHTML и вернуть его innerHTML элементу здесь: jsbin.com/butofezipi/edit?html,css,js,output Я не знаю, сделал ли я это неправильно ... но похоже, что метод кодирования делает работать на этот экземпляр? Просто пытаюсь сыграть адвоката дьявола. Я сам ищу хороший метод: b Просто хотел отметить, что метод, который вы предлагаете, соответствует моим потребностям, поэтому спасибо!

Dustin 28.04.2016 17:30

ужасный код ... вот что вы получаете, когда заставляете разработчиков C++ / JAVA писать Javascript.

vsync 15.05.2017 12:17

Сработает ли назначить innerText, а затем получить innerHTML?

Arlen Beiler 09.08.2017 17:51

@ArlenBeiler, что бы конвертировать <b>foo</b> в &lt;b&gt;foo&lt;/b&gt;, верно? Это, вероятно, не решит проблему OP.

Mike Samuel 11.08.2017 04:30

Да, верно. Просто подумал, что спрошу, так как я так думал, но ты прав, это не решит исходный вопрос.

Arlen Beiler 11.08.2017 04:50

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

[править] Простите, я неправильно понял вопрос. Однако я верю своему совету. Ваши пользователи, вероятно, будут в большей безопасности, если вы продезинфицируете сервер перед отправкой им.

Фактически, с ростом популярности node.js решение javascript также может быть серверным решением. По крайней мере, так я здесь оказался. Тем не менее, это отличный совет, по которому нужно жить.

Nicholas Flynt 29.08.2011 17:22

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

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

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

phihag 04.07.2012 11:36

Затем отправьте ему фрагмент DOM, просто потому, что он находится в DOM в заданной форме или форме, на самом деле не означает, что он был выполнен. Предполагая, что он загружает его через AJAX, он может использовать это вместе с importNode.

John 04.07.2012 12:36

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

Отличный ответ. Джеффри, не могли бы вы объяснить, почему санитарная обработка должна выполняться веб-воркером?

Austin Wang 02.02.2014 01:43

@AustinWang Web-воркеры не являются строго обязательными, но поскольку очистка потенциально может быть дорогостоящей в вычислительном отношении и не требует взаимодействия с пользователем, она хорошо подходит для этой задачи. (Я также упомянул, что в основном ответе есть глобальные переменные.)

Jeffery To 02.02.2014 09:14

Я не могу найти достойную документацию для этой библиотеки. Где / как мне указать белый список элементов и атрибутов?

AsGoodAsItGets 15.03.2016 13:21

@AsGoodAsItGets Как описано в комментарий в текущей версии, nameIdClassTransformer вызывается для каждого имени HTML, идентификатора элемента и списка классов; возврат null удалит атрибут. Редактируя файлы JSON в SRC / com / google / caja / lang / html, вы также можете настроить, какие элементы и атрибуты будут добавлены в белый список.

Jeffery To 15.03.2016 14:09

@JefferyTo извини, может я слишком тупой, но я не понимаю. Файлы JSON, на которые вы ссылаетесь, не используются в приведенном выше примере и демонстрации. Я хочу использовать библиотеку в браузере, поэтому посмотрел вашу демонстрацию. Можете ли вы изменить приведенную выше функцию nameIdClassTranformer, например. отклонить все теги <script> и принять теги <b> и <i>?

AsGoodAsItGets 15.03.2016 16:50

@AsGoodAsItGets Вам необходимо проверить исходный код, отредактировать файлы JSON, а затем запустить ant для создания файлов JS, подходящих для вашего варианта использования.

Jeffery To 15.03.2016 16:55

@vsync importScripts является частью API Web Workers.

Jeffery To 15.05.2017 12:39
Uncaught ReferenceError: importScripts is not defined на FF / Chrome
vsync 16.05.2017 17:30

@vsync Вы пробовали внутри Веб-воркер?

Jeffery To 16.05.2017 17:42

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

vsync 16.05.2017 19:48

@vsync Нет, капли не нужны; Предлагаю взглянуть на статью о веб-воркерах выше.

Jeffery To 16.05.2017 20:41

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

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

pwray 28.05.2015 03:53

Как можно программно создать скрытый iframe «с отключенным JavaScript»? Насколько мне известно, это невозможно. В ту минуту, когда вы выполните iframe.contentDocument.body.innerHTML = input, все теги сценария будут выполнены.

AsGoodAsItGets 15.03.2016 13:25

@AsGoodAsItGets - найдите атрибут песочницы в окнах iframe.

aldel 15.03.2016 18:13

@aldel Действительно, я не знал об этом. Для нас это все еще непозволительно из-за отсутствия поддержки в IE9. Думаю, ваше решение может сработать, но я думаю, вам следует пояснить в своем ответе, что вы зависите от атрибута sandbox.

AsGoodAsItGets 15.03.2016 18:18

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

aldel 16.03.2016 02:44

@vsync У меня это работает в Firefox 53, и я почти уверен, что он работал в любой более ранней версии, которая была выпущена, когда я писал ответ (44, я думаю). Может быть, у вас есть плагин, который мешает?

aldel 15.05.2017 17:09

@aldel - извините, мой VPN по какой-то причине блокирует файл сценария.

vsync 16.05.2017 17:45

Атрибуты @pwray href могут содержать javascript: <a href = "javascript: alert('hello')">Test</a>

Explosion 24.01.2021 21:24

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

Итак, это 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 здесь, это тяжеловато (пол мегабайта зависимостей)

user1464581 30.01.2018 01:43

2020 здесь, sanitize-html предназначен для Node, и, насколько я могу судить, до сих пор нет хорошего варианта для браузеров

Mick 10.11.2020 16:34

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

Мы написали для этого библиотеку с открытым исходным кодом «только для Интернета» (т.е. «требуется браузер»), 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">
&lt;!-- This removes both the src and onerror attributes because src is not a valid url. --&gt;
&lt;img src=&quot;error&quot; onerror=&quot;alert('XSS')&quot;&gt;
&lt;div id=&quot;something_harmless&quot; onload=&quot;alert('More XSS')&quot;&gt;
   &lt;b&gt;Bold text!&lt;/b&gt; and &lt;em&gt;Italic text!&lt;/em&gt;, some more text. &lt;del&gt;Deleted text!&lt;/del&gt;
&lt;/div&gt;
 &lt;script&gt;
    alert(&quot;This would be XSS&quot;);
  &lt;/script&gt;
</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>

Другие вопросы по теме