Python HTML дезинфицирующее средство / скруббер / фильтр [закрыто]
Я ищу модуль, который будет удалять любые HTML-теги из строки, которые не найдены в белом списке.
9 ответов:
вот простое решение с помощью BeautifulSoup:
from bs4 import BeautifulSoup VALID_TAGS = ['strong', 'em', 'p', 'ul', 'li', 'br'] def sanitize_html(value): soup = BeautifulSoup(value) for tag in soup.findAll(True): if tag.name not in VALID_TAGS: tag.hidden = True return soup.renderContents()
Если вы также хотите удалить содержимое недопустимых тегов, замените
tag.extract()
наtag.hidden
.
использовать
lxml.html.clean
! Это очень просто!from lxml.html.clean import clean_html print clean_html(html)
предположим следующий html:
html = '''\ <html> <head> <script type="text/javascript" src="evil-site"></script> <link rel="alternate" type="text/rss" src="evil-rss"> <style> body {background-image: url(javascript:do_evil)}; div {color: expression(evil)}; </style> </head> <body onload="evil_function()"> <!-- I am interpreted for EVIL! --> <a href="javascript:evil_function()">a link</a> <a href="#" onclick="evil_function()">another link</a> <p onclick="evil_function()">a paragraph</p> <div style="display: none">secret EVIL!</div> <object> of EVIL! </object> <iframe src="evil-site"></iframe> <form action="evil-site"> Password: <input type="password" name="password"> </form> <blink>annoying EVIL!</blink> <a href="evil-site">spam spam SPAM!</a> <image src="evil!"> </body> </html>'''
результаты...
<html> <body> <div> <style>/* deleted */</style> <a href="">a link</a> <a href="#">another link</a> <p>a paragraph</p> <div>secret EVIL!</div> of EVIL! Password: annoying EVIL! <a href="evil-site">spam spam SPAM!</a> <img src="evil!"> </div> </body> </html>
вы можете настроить элементы, которые вы хотите очистить и все такое.
вышеуказанные решения через красивый суп не получится. Вы могли бы взломать что-то с красивым супом выше и за их пределами, потому что красивый суп обеспечивает доступ к дереву разбора. Через некоторое время, я думаю, что попытаюсь решить проблему должным образом, но это недельный проект или около того, и у меня нет свободной недели в ближайшее время.
просто чтобы быть конкретным, не только красивый суп бросает исключения для некоторых ошибок синтаксического анализа, которые приведенный выше код не ловит; но также есть множество очень реальных уязвимостей XSS, которые не пойманы, например:
<<script>script> alert("Haha, I hacked your page."); </</script>script>
пожалуй, лучшее, что вы можете сделать, это вместо того, чтобы вырезать
<
элемент<
запретить все HTML, а затем используйте ограниченное подмножество, такое как Markdown, для правильного форматирования. В частности, вы также можете вернуться и повторно ввести общие биты HTML с регулярным выражением. Вот как выглядит процесс, примерно:_lt_ = re.compile('<') _tc_ = '~(lt)~' # or whatever, so long as markdown doesn't mangle it. _ok_ = re.compile(_tc_ + '(/?(?:u|b|i|em|strong|sup|sub|p|br|q|blockquote|code))>', re.I) _sqrt_ = re.compile(_tc_ + 'sqrt>', re.I) #just to give an example of extending _endsqrt_ = re.compile(_tc_ + '/sqrt>', re.I) #html syntax with your own elements. _tcre_ = re.compile(_tc_) def sanitize(text): text = _lt_.sub(_tc_, text) text = markdown(text) text = _ok_.sub(r'<>', text) text = _sqrt_.sub(r'√<span style="text-decoration:overline;">', text) text = _endsqrt_.sub(r'</span>', text) return _tcre_.sub('<', text)
Я еще не тестировал этот код, так что могут быть ошибки. Но вы видите общую идею: вы должны внести в черный список весь HTML в целом, прежде чем белый список ОК вещи.
вот что я использую в своем проекте. Acceptable_elements / атрибуты приходят из feedparser и BeautifulSoup делает работу.
from BeautifulSoup import BeautifulSoup acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area', 'b', 'big', 'blockquote', 'br', 'button', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'label', 'legend', 'li', 'map', 'menu', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var'] acceptable_attributes = ['abbr', 'accept', 'accept-charset', 'accesskey', 'action', 'align', 'alt', 'axis', 'border', 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'checked', 'cite', 'clear', 'cols', 'colspan', 'color', 'compact', 'coords', 'datetime', 'dir', 'enctype', 'for', 'headers', 'height', 'href', 'hreflang', 'hspace', 'id', 'ismap', 'label', 'lang', 'longdesc', 'maxlength', 'method', 'multiple', 'name', 'nohref', 'noshade', 'nowrap', 'prompt', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope', 'shape', 'size', 'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title', 'type', 'usemap', 'valign', 'value', 'vspace', 'width'] def clean_html( fragment ): while True: soup = BeautifulSoup( fragment ) removed = False for tag in soup.findAll(True): # find all tags if tag.name not in acceptable_elements: tag.extract() # remove the bad ones removed = True else: # it might have bad attributes # a better way to get all attributes? for attr in tag._getAttrMap().keys(): if attr not in acceptable_attributes: del tag[attr] # turn it back to html fragment = unicode(soup) if removed: # we removed tags and tricky can could exploit that! # we need to reparse the html until it stops changing continue # next round return fragment
некоторые небольшие тесты, чтобы убедиться, что это ведет себя правильно:
tests = [ #text should work ('<p>this is text</p>but this too', '<p>this is text</p>but this too'), # make sure we cant exploit removal of tags ('<<script></script>script> alert("Haha, I hacked your page."); <<script></script>/script>', ''), # try the same trick with attributes, gives an Exception ('<div on<script></script>load="alert("Haha, I hacked your page.");">1</div>', Exception), # no tags should be skipped ('<script>bad</script><script>bad</script><script>bad</script>', ''), # leave valid tags but remove bad attributes ('<a href="good" onload="bad" onclick="bad" alt="good">1</div>', '<a href="good" alt="good">1</a>'), ] for text, out in tests: try: res = clean_html(text) assert res == out, "%s => %s != %s" % (text, res, out) except out, e: assert isinstance(e, out), "Wrong exception %r" % e
отбеливатель лучше более полезные варианты. Он построен на html5lib и готов к производству.
проверить это документы.
Я изменил Брайан ' s решение с BeautifulSoup в адрес проблема, поднятая Крисом Дростом. Немного грубо, но делает свою работу:
from BeautifulSoup import BeautifulSoup, Comment VALID_TAGS = {'strong': [], 'em': [], 'p': [], 'ol': [], 'ul': [], 'li': [], 'br': [], 'a': ['href', 'title'] } def sanitize_html(value, valid_tags=VALID_TAGS): soup = BeautifulSoup(value) comments = soup.findAll(text=lambda text:isinstance(text, Comment)) [comment.extract() for comment in comments] # Some markup can be crafted to slip through BeautifulSoup's parser, so # we run this repeatedly until it generates the same output twice. newoutput = soup.renderContents() while 1: oldoutput = newoutput soup = BeautifulSoup(newoutput) for tag in soup.findAll(True): if tag.name not in valid_tags: tag.hidden = True else: tag.attrs = [(attr, value) for attr, value in tag.attrs if attr in valid_tags[tag.name]] newoutput = soup.renderContents() if oldoutput == newoutput: break return newoutput
Edit: обновлено для поддержки допустимых атрибутов.
Я использую этот:
Это просто и позволяет определить хорошо контролируемый белый список, очищает URL-адреса и даже сопоставляет значения атрибутов с регулярным выражением или имеет пользовательские функции фильтрации для каждого атрибута.
при тщательном использовании это может быть безопасным решением.
вы могли бы использовать html5lib, который использует белый список, чтобы санировать.
пример:
import html5lib from html5lib import sanitizer, treebuilders, treewalkers, serializer def clean_html(buf): """Cleans HTML of dangerous tags and content.""" buf = buf.strip() if not buf: return buf p = html5lib.HTMLParser(tree=treebuilders.getTreeBuilder("dom"), tokenizer=sanitizer.HTMLSanitizer) dom_tree = p.parseFragment(buf) walker = treewalkers.getTreeWalker("dom") stream = walker(dom_tree) s = serializer.htmlserializer.HTMLSerializer( omit_optional_tags=False, quote_attr_values=True) return s.render(stream)
Я предпочитаю
lxml.html.clean
решение, как noskloуказывает. Вот чтобы также удалить некоторые пустые теги:from lxml import etree from lxml.html import clean, fromstring, tostring remove_attrs = ['class'] remove_tags = ['table', 'tr', 'td'] nonempty_tags = ['a', 'p', 'span', 'div'] cleaner = clean.Cleaner(remove_tags=remove_tags) def squeaky_clean(html): clean_html = cleaner.clean_html(html) # now remove the useless empty tags root = fromstring(clean_html) context = etree.iterwalk(root) # just the end tag event for action, elem in context: clean_text = elem.text and elem.text.strip(' \t\r\n') if elem.tag in nonempty_tags and \ not (len(elem) or clean_text): # no children nor text elem.getparent().remove(elem) continue elem.text = clean_text # if you want # and if you also wanna remove some attrs: for badattr in remove_attrs: if elem.attrib.has_key(badattr): del elem.attrib[badattr] return tostring(root)