Дезінфікуйте / перепишіть HTML на стороні клієнта


82

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

Можна використовувати String # stripScripts прототипу для видалення скриптових блоків. Але обробники, такі як onclickабоonerror все ще існують.

Чи є якась бібліотека, яка хоча б може

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

Тож чи є посилання та приклади, пов’язані з JavaScript?


12
Не довіряйте відповіді , які могли б зробити це з допомогою регулярних виразів stackoverflow.com/questions/1732348 / ...
Мікко Ohtamaa


Як це безпечно? Чи не можуть користувачі редагувати javascript сторінки?
Даніель каже «Поновити Моніку»

так, це не "безпечно", якщо ви просто не намагаєтесь запобігти помилкам надійних користувачів.
Скотт

Відповіді:


112

Оновлення 2016: Зараз існує пакет Google Closure на основі дезінфікуючого засобу Caja.

Він має чистіший API, перероблений з урахуванням API, доступних у сучасних браузерах, та краще взаємодіє з компілятором закриття.


Безсоромний штекер: див. 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.



3
Код дезінфікуючого засобу Caja HTML виглядає чудово, але вимагає певного коду клею (сусідній cssparser.js, але що важливіше, html4об’єкт). Крім того, це забруднює глобальну windowвласність. Чи існує веб-версія цього коду? Якщо ні, то чи бачите ви кращий спосіб створити та підтримувати такий, ніж створити для нього окремий проект?
phihag

1
@phihag, запитайте на google-caja-обсудити, і вони можуть вказати вам упакований. Я вважаю, що забруднення віконних об'єктів має зворотну сумісність, і тому будь-яка нова версія пакету може не потребувати цього.
Mike Samuel

1
Виявляється, там вже є пакет для веб-переглядачів.
phihag

2
@phihag Цей пакет призначений для nodejs, а не для браузерів.
Jeffery До

40

Дезінфікуючий засіб Google Caja HTML можна зробити «готовим до роботи в Інтернеті », включивши його до веб-працівника . Будь-які глобальні змінні, введені дезінфікуючим засобом, будуть міститися в робочому середовищі, а обробка відбувається у його власному потоці.

Для браузерів, які не підтримують Web Workers, ми можемо використовувати iframe як окреме середовище для роботи дезінфікуючого засобу. Тімоті Chien має поліфіл, який робить саме це, використовуючи 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


Чудова відповідь. Джеффрі, ти можеш пояснити, чому санітарну обробку взагалі повинен робити веб-працівник?
Остін Ван,

Веб-працівники @AustinWang не є суворо необхідними, але оскільки санітарна обробка може бути обчислювально дорогою і не вимагати взаємодії з користувачем, вона добре підходить для цього завдання. (Я також згадав, що містять глобальні змінні в основній відповіді.)
Джеффрі До 02

Я не можу знайти гідну документацію для цієї бібліотеки. Де / як я можу вказати свій білий список елементів та атрибутів?
AsGoodAsItGets

@AsGoodAsItGets Як описано коментарем у поточній версії , nameIdClassTransformerвикликається для кожного імені HTML, ідентифікатора елемента та списку класів; повернення nullвидалить атрибут. Редагуючи файли JSON у src / com / google / caja / lang / html, ви також можете налаштувати, які елементи та атрибути входять до білого списку.
Джеффрі До

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

20

Ніколи не довіряйте клієнту. Якщо ви пишете серверну програму, припустимо, що клієнт завжди подаватиме антисанітарні шкідливі дані. Це емпіричне правило, яке убереже вас від неприємностей. Якщо ви можете, я б порадив виконати всі перевірки та санітарію в коді сервера, який, як ви знаєте (до розумної міри), не буде перекручений. Можливо, ви могли б використовувати веб-програму на стороні сервера як проксі-сервер для коду на стороні клієнта, який отримує від третьої сторони та виконує санітарну обробку перед тим, як відправити його самому клієнту?

[редагувати] Вибачте, я неправильно зрозумів питання. Однак я дотримуюся своєї поради. Ваші користувачі, мабуть, будуть у безпеці, якщо ви дезінфікуєте на сервері перед тим, як відправити їх їм.


19
Насправді, із зростанням популярності node.js, рішення javascript також може бути рішенням на сервері. Так я, принаймні, опинився тут. Тим не менш, це відмінна порада, яким слід жити.
Ніколас Флінт,

15

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

ПРИМІТКА. Цей метод точно не працюватиме в 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), щоб переконатися, що він виручав перед виконанням будь-яких сценаріїв. Мені було б цікаво дізнатись, чи є браузери, які мають проблеми з цим, або випадки, які я не помічаю.


10
Ця публікація заслуговує на певну увагу експертів, оскільки видається очевидним і найпростішим рішенням. Це справді безпечно?
pwray

Як можна програмно створити прихований iframe "з відключеним JavaScript"? Наскільки мені відомо, це неможливо. Щохвилини iframe.contentDocument.body.innerHTML = input, які б теги сценарію там не виконувались, будуть виконані.
AsGoodAsItGets

@AsGoodAsItGets - шукайте атрибут пісочниці у фреймах.
альдель

1
@aldel Справді, я не знав про це. Для нас це все ще заборона через відсутність підтримки в IE9. Я думаю, ваше рішення могло б спрацювати, але я думаю, що ви повинні пояснити у своїй відповіді, що ви залежате від sandboxатрибута.
AsGoodAsItGets

На жаль, я думав, що це було зрозуміло з мого відкриття "Тепер, коли всі основні браузери підтримують вбудовані фреймворки". Додам менш витончену ноту.
альдель

12

Ви не можете передбачити будь-який можливий дивний тип неправильної розмітки, про яку десь браузер може зіткнутися, щоб уникнути чорного списку, тому не вносити до чорного списку. Існує набагато більше структур, які вам може знадобитися видалити, ніж просто сценарій / вбудований / об'єкт та обробники.

Натомість намагайтеся проаналізувати HTML на елементи та атрибути в ієрархії, а потім запустіть усі імена елементів та атрибутів у білий список як мінімально можливого. Також перевірте будь-які атрибути URL, які ви пропускаєте, у білий список (пам’ятайте, що є більш небезпечні протоколи, ніж просто javascript :).

Якщо вводиться добре сформований XHTML, перша частина вищезазначеного набагато простіша.

Як завжди з дезінфекцією HTML, якщо ви можете знайти будь-який інший спосіб уникнути цього, зробіть це замість цього. Є багато-багато потенційних дір. Якщо основні служби веб-пошти все ще знаходять подвиги після багатьох років, що змушує вас думати, що ви можете зробити краще?


11

Отже, 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);


10
2018 рік тут, це занадто важко (
півмегабайт

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

3

[Застереження: я один з авторів]

Для цього ми написали бібліотеку з відкритим кодом "лише для Інтернету" (тобто "вимагає браузера"), 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 (не пов'язане)


2

Запропонована вище бібліотека 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

Я рекомендую вирізати рамки зі свого життя, це надовго полегшить вам роботу.

cloneNode: Клонування Вузла копії все своїх атрибутів і їх значень , але це НЕ копіювати слухач подій .

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

Далі не перевірено, хоча я вже деякий час використовую трекери, і вони є однією з найбільш недооцінених частин 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.');
 }
}

5
Цей код передбачає, що вхідні дані для очищення вже були проаналізовані і навіть вставлені в дерево документів. Якщо це так, шкідливі сценарії вже виконано. Вхідні дані повинні бути рядком.
phihag

Потім надішліть йому фрагмент DOM, лише тому, що він знаходиться у DOM у заданій формі або формі, насправді не означає, що він був виконаний. Припускаючи, що він завантажує його через AJAX, він може використовувати це спільно з importNode.
Джон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.