Vệ sinh / Viết lại HTML ở phía Máy khách


82

Tôi cần hiển thị các tài nguyên bên ngoài được tải qua các yêu cầu tên miền chéo và đảm bảo chỉ hiển thị nội dung " an toàn ".

Có thể sử dụng Chuỗi # dảiScripts của Prototype để xóa các khối tập lệnh. Nhưng những người xử lý như onclickhoặc onerrorvẫn ở đó.

Có thư viện nào ít nhất có thể

  • dải tập lệnh,
  • giết trình xử lý DOM,
  • loại bỏ các thẻ được liệt kê màu đen (ví dụ: embedhoặc object).

Vậy có bất kỳ liên kết và ví dụ nào liên quan đến JavaScript không?


12
Đừng tin tưởng câu trả lời mà có thể làm điều này bằng biểu thức thông thường stackoverflow.com/questions/1732348/...
Mikko Ohtamaa


Làm thế nào là điều này an toàn? Người dùng không thể chỉnh sửa javascript của một trang?
Daniel nói Hãy phục hồi Monica vào

vâng, nó không 'an toàn' trừ khi bạn chỉ đơn giản là cố gắng ngăn chặn những sai lầm của những người dùng đáng tin cậy.
Scott

Câu trả lời:


112

Cập nhật 2016: Hiện đã có gói Google Closure dựa trên chất khử trùng Caja.

Nó có một API sạch hơn, được viết lại để tính đến các API có sẵn trên các trình duyệt hiện đại và tương tác tốt hơn với Trình biên dịch đóng cửa.


Trình cắm không biết xấu hổ: xem caja / plugin / html-sanitizer.js để biết trình vệ sinh html phía máy khách đã được xem xét kỹ lưỡng.

Nó được liệt kê trong danh sách trắng, không phải danh sách đen, nhưng danh sách trắng có thể định cấu hình theo CajaWhitelists


Nếu bạn muốn xóa tất cả các thẻ, hãy làm như sau:

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

Mọi người sẽ nói với bạn rằng bạn có thể tạo một phần tử, gán innerHTMLvà sau đó lấy dấu innerTexthoặc textContent, rồi thoát các thực thể trong đó. Đừng làm thế. Nó dễ bị chèn XSS vì <img src=bogus onerror=alert(1337)>sẽ chạy onerrortrình xử lý ngay cả khi nút không bao giờ được gắn với DOM.


5
Tuyệt vời, trông giống như có một tài liệu nhỏ ở đây: code.google.com/p/google-caja/wiki/JsHtmlSanitizer
tmcw

3
Mã trình vệ sinh HTML Caja trông tuyệt vời, nhưng yêu cầu một số mã keo (lân cận cssparser.js, nhưng quan trọng hơn là html4đối tượng). Ngoài ra, nó gây ô nhiễm windowtài sản toàn cầu . Có phiên bản cho web của mã này không? Nếu không, bạn có thấy cách nào tốt hơn để sản xuất và duy trì một dự án hơn là tạo một dự án riêng cho nó không?
phihag

1
@phihag, Hãy hỏi tại google-caja-thảo luận và họ có thể chỉ cho bạn một địa chỉ đóng gói. Tôi tin rằng ô nhiễm đối tượng cửa sổ là để tương thích ngược và vì vậy bất kỳ phiên bản gói mới nào có thể không cần điều đó.
Mike Samuel

1
Hóa ra đã có một gói dành cho trình duyệt web.
phihag

2
@phihag Gói đó dành cho nodejs, không phải trình duyệt.
Jeffery Đến

40

Trình vệ sinh HTML của Google Caja có thể được tạo ở trạng thái "sẵn sàng cho web" bằng cách nhúng nó vào trình quản lý web . Bất kỳ biến toàn cục nào được đưa vào bởi sanitizer sẽ được chứa trong worker, cộng với quá trình xử lý diễn ra trong luồng của chính nó.

Đối với các trình duyệt không hỗ trợ Web Worker, chúng tôi có thể sử dụng iframe làm môi trường riêng biệt để sanitizer hoạt động. Timothy Chien có một polyfill thực hiện việc này, sử dụng iframe để mô phỏng Web worker , vì vậy phần đó được thực hiện cho chúng tôi.

Dự án Caja có một trang wiki về cách sử dụng Caja làm chất khử trùng độc lập phía máy khách :

  • Kiểm tra nguồn, sau đó xây dựng bằng cách chạy ant
  • Bao gồm html-sanitizer-minified.jshoặc html-css-sanitizer-minified.jstrong trang của bạn
  • Gọi html_sanitize(...)

Tập lệnh công nhân chỉ cần làm theo các hướng dẫn sau:

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

(Cần thêm một đoạn mã để thư viện simworker hoạt động, nhưng điều này không quan trọng đối với cuộc thảo luận này.)

Demo: https://dl.dropbox.com/u/291406/html-sanifying/demo.html


Câu trả lời chính xác. Jeffrey, bạn có thể giải thích tại sao việc vệ sinh lại cần được thực hiện bởi nhân viên web không?
Austin Wang

@AustinWang Web worker không hoàn toàn cần thiết, nhưng vì quá trình làm sạch có thể tốn kém về mặt tính toán và không yêu cầu tương tác của người dùng, nên nó rất phù hợp cho nhiệm vụ này. (Tôi cũng đã đề cập đến việc chứa các biến toàn cục trong câu trả lời chính.)
Jeffery Đến

Tôi không thể tìm thấy tài liệu tốt cho thư viện này. Tôi ở đâu / làm cách nào để chỉ định danh sách các phần tử và thuộc tính cho phép?
AsGoodAsItGets

@AsGoodAsItGets Như được mô tả bởi một nhận xét trong phiên bản hiện tại , nameIdClassTransformerđược gọi cho mọi tên HTML, ID phần tử và danh sách các lớp; quay lại nullsẽ xóa thuộc tính. Bằng cách chỉnh sửa các tệp JSON trong src / com / google / caja / lang / html, bạn cũng có thể tùy chỉnh các phần tử và thuộc tính nào được đưa vào danh sách trắng.
Jeffery Đến

@JefferyTo tôi xin lỗi, có lẽ tôi quá ngu ngốc, nhưng tôi không hiểu. Các tệp JSON mà bạn đề cập đến không được sử dụng trong ví dụ và bản trình diễn của bạn ở trên. Tôi muốn sử dụng thư viện trong trình duyệt, vì vậy tôi đã xem bản trình diễn của bạn. Bạn có thể sửa đổi nameIdClassTranformerchức năng ở trên, ví dụ: từ chối tất cả <script>các thẻ và chấp nhận <b><i>thẻ?
AsGoodAsItGets

20

Đừng bao giờ tin tưởng khách hàng. Nếu bạn đang viết một ứng dụng máy chủ, hãy giả sử rằng máy khách sẽ luôn gửi dữ liệu độc hại, mất vệ sinh. Đó là một quy tắc ngón tay cái sẽ giúp bạn không gặp rắc rối. Nếu bạn có thể, tôi khuyên bạn nên thực hiện tất cả xác thực và vệ sinh trong mã máy chủ, mà bạn biết (ở một mức độ hợp lý) sẽ không gặp khó khăn. Có lẽ bạn có thể sử dụng ứng dụng web phía máy chủ làm proxy cho mã phía máy khách của mình, mã này tìm nạp từ bên thứ 3 và thực hiện vệ sinh trước khi gửi đến chính máy khách?

[sửa] Tôi xin lỗi, tôi đã hiểu sai câu hỏi. Tuy nhiên, tôi đứng trước lời khuyên của mình. Người dùng của bạn có thể sẽ an toàn hơn nếu bạn làm sạch trên máy chủ trước khi gửi cho họ.


19
Trên thực tế, với sự phổ biến của node.js ngày càng tăng, một giải pháp javascript cũng có thể là một giải pháp bên máy chủ. Đó là cách tôi đã kết thúc ở đây ít nhất. Tuy nhiên, đây là lời khuyên tuyệt vời để sống.
Nicholas Flynt

15

Bây giờ tất cả các trình duyệt chính đều hỗ trợ iframe hộp cát, có một cách đơn giản hơn nhiều mà tôi nghĩ có thể an toàn. Tôi rất thích nếu câu trả lời này có thể được xem xét bởi những người quen thuộc hơn với loại vấn đề bảo mật này.

LƯU Ý: Phương pháp này chắc chắn sẽ không hoạt động trong IE 9 trở về trước. Xem bảng này để biết các phiên bản trình duyệt hỗ trợ hộp cát. (Lưu ý: bảng có vẻ nói rằng nó sẽ không hoạt động trong Opera Mini, nhưng tôi vừa thử nó và nó đã hoạt động.)

Ý tưởng là tạo một iframe ẩn với JavaScript bị vô hiệu hóa, dán HTML không đáng tin cậy của bạn vào đó và để nó phân tích cú pháp. Sau đó, bạn có thể đi qua cây DOM và sao chép các thẻ và thuộc tính được coi là an toàn.

Danh sách trắng được hiển thị ở đây chỉ là ví dụ. Điều tốt nhất để đưa vào danh sách trắng sẽ phụ thuộc vào ứng dụng. Nếu bạn cần một chính sách phức tạp hơn chỉ là danh sách trắng các thẻ và thuộc tính, thì phương pháp này có thể đáp ứng được bằng phương pháp này, mặc dù không phải bằng mã ví dụ này.

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;
};

Bạn có thể dùng thử tại đây .

Lưu ý rằng tôi không cho phép các thuộc tính và thẻ kiểu trong ví dụ này. Nếu bạn cho phép chúng, bạn có thể muốn phân tích cú pháp CSS và đảm bảo rằng nó an toàn cho mục đích của bạn.

Tôi đã thử nghiệm điều này trên một số trình duyệt hiện đại (Chrome 40, Firefox 36 Beta, IE 11, Chrome dành cho Android) và trên một trình duyệt cũ (IE 8) để đảm bảo rằng nó được bảo hành trước khi thực thi bất kỳ tập lệnh nào. Tôi muốn biết liệu có bất kỳ trình duyệt nào gặp sự cố với nó hoặc bất kỳ trường hợp phức tạp nào mà tôi đang bỏ qua hay không.


10
Bài đăng này đáng được các chuyên gia chú ý vì nó có vẻ là giải pháp rõ ràng và đơn giản nhất. Nó có thực sự an toàn không?
pwray

Làm cách nào bạn có thể lập trình tạo iframe ẩn "đã tắt JavaScript"? Theo hiểu biết tốt nhất của tôi thì điều này là không thể. Ngay khi bạn làm iframe.contentDocument.body.innerHTML = input, bất kỳ thẻ script nào trong đó sẽ được thực thi.
AsGoodAsItGets

@AsGoodAsItGets - tra cứu thuộc tính hộp cát trên iframe.
aldel

1
@aldel Thật vậy, tôi không biết về nó. Đối với chúng tôi, đó vẫn là một điều không nên vì thiếu hỗ trợ trong IE9. Tôi đoán giải pháp của bạn có thể hoạt động, nhưng tôi nghĩ bạn nên làm rõ trong câu trả lời của mình rằng bạn phụ thuộc vào sandboxthuộc tính.
AsGoodAsItGets

Xin lỗi, tôi đã nghĩ rằng điều đó đã rõ ràng từ phần mở đầu của tôi "Bây giờ tất cả các trình duyệt chính đều hỗ trợ iframe hộp cát". Tôi sẽ thêm một lưu ý ít tinh tế hơn.
aldel

12

Bạn không thể lường trước mọi loại đánh dấu không đúng định dạng kỳ lạ mà một số trình duyệt ở đâu đó có thể lướt qua để thoát khỏi danh sách đen, vì vậy đừng đưa vào danh sách đen. Có nhiều cấu trúc khác mà bạn có thể cần phải loại bỏ ngoài script / nhúng / đối tượng và các trình xử lý.

Thay vào đó, hãy cố gắng phân tích cú pháp HTML thành các phần tử và thuộc tính trong một hệ thống phân cấp, sau đó chạy tất cả các tên phần tử và thuộc tính trong danh sách trắng tối thiểu nhất có thể. Ngoài ra, hãy kiểm tra bất kỳ thuộc tính URL nào bạn cho phép so với danh sách trắng (hãy nhớ rằng có nhiều giao thức nguy hiểm hơn chỉ javascript :).

Nếu đầu vào là XHTML được định dạng tốt thì phần đầu tiên của phần trên sẽ dễ dàng hơn nhiều.

Như mọi khi với quá trình sanitisation HTML, nếu bạn có thể tìm thấy bất kỳ cách nào khác để tránh làm điều đó, hãy làm điều đó thay thế. Có rất nhiều, rất nhiều lỗ hổng tiềm ẩn. Nếu các dịch vụ email trực tuyến lớn vẫn đang tìm cách khai thác sau nhiều năm này, điều gì khiến bạn nghĩ rằng bạn có thể làm tốt hơn?


11

Vì vậy, đó là năm 2016 và tôi nghĩ rằng nhiều người trong chúng ta đang sử dụng npmcác mô-đun trong mã của chúng tôi bây giờ. sanitize-htmlcó vẻ như là tùy chọn hàng đầu trên npm, mặc dù có những tùy chọn khác .

Các câu trả lời khác cho câu hỏi này cung cấp thông tin đầu vào tuyệt vời về cách triển khai của riêng bạn, nhưng đây là một vấn đề đủ phức tạp mà các giải pháp cộng đồng đã được thử nghiệm kỹ lưỡng có lẽ là câu trả lời tốt nhất.

Chạy điều này trên dòng lệnh để cài đặt: 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 ở đây, đây là quá nặng (một nửa megabyte phụ thuộc)
user1464581

2020 đây, sanitize-html là cho Node và vẫn còn lựa chọn không tốt cho các trình duyệt như xa như tôi có thể nói
Mick

3

[Tuyên bố từ chối trách nhiệm: Tôi là một trong những tác giả]

Chúng tôi đã viết một thư viện nguồn mở "chỉ dành cho web" (tức là "yêu cầu trình duyệt") cho thư viện này, https://github.com/jitbit/HtmlSanitizer để xóa tất cả tags/attributes/stylesngoại trừ những cái "có trong danh sách cho phép".

Sử dụng:

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

PS Hoạt động nhanh hơn nhiều so với giải pháp "JavaScript thuần túy" vì nó sử dụng trình duyệt để phân tích cú pháp và thao tác DOM. Nếu bạn quan tâm đến giải pháp "JS thuần túy", vui lòng thử https://github.com/punkave/sanifying-html (không liên kết)


2

Thư viện Google Caja được đề xuất ở trên quá phức tạp để định cấu hình và đưa vào dự án của tôi cho một ứng dụng Web (vì vậy, chạy trên trình duyệt). Thay vào đó, những gì tôi đã sử dụng, vì chúng tôi đã sử dụng thành phần CKEditor, là sử dụng chức năng tạo danh sách trắng và làm sạch HTML được tích hợp sẵn của nó, dễ định cấu hình hơn nhiều. Vì vậy, bạn có thể tải một phiên bản CKEditor trong iframe ẩn và thực hiện một số việc như:

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

Bây giờ, được chấp nhận, nếu bạn không sử dụng CKEditor trong dự án của mình, điều này có thể hơi quá mức cần thiết, vì bản thân thành phần này có dung lượng khoảng nửa megabyte (được tối thiểu hóa), nhưng nếu bạn có nguồn, có thể bạn có thể tách mã đang hoạt động danh sách trắng ( CKEDITOR.htmlParser?) và làm cho nó ngắn hơn nhiều.

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

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


0

Tôi khuyên bạn nên cắt bỏ các khuôn khổ ra khỏi cuộc sống của mình, nó sẽ khiến mọi thứ trở nên dễ dàng hơn rất nhiều đối với bạn trong dài hạn.

cloneNode: Nhân bản một bản sao nút tất cả các thuộc tính của nó và giá trị của họ, nhưng không KHÔNG sao chép nghe sự kiện .

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

Phần sau không được kiểm tra mặc dù tôi đã sử dụng treewalker một thời gian và chúng là một trong những phần bị đánh giá thấp nhất của JavaScript. Đây là danh sách các loại nút mà bạn có thể thu thập thông tin, thường tôi sử dụng SHOW_ELEMENT hoặc 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
Mã này giả định rằng đầu vào để làm sạch đã được phân tích cú pháp và thậm chí được chèn vào cây tài liệu. Nếu đúng như vậy, các tập lệnh độc hại đã được thực thi. Đầu vào phải là một chuỗi.
phihag

Sau đó, gửi một đoạn DOM tới nó, chỉ vì nó nằm trong DOM ở một hình dạng hoặc hình thức nhất định không thực sự ngụ ý rằng nó đã được thực thi. Giả sử anh ta đang tải nó qua AJAX, anh ta có thể sử dụng nó kết hợp với importNode.
John
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.