Ich habe eine Zeichenfolge, die Text und HTML enthält. Ich möchte einige HTML-Tags entfernen oder auf andere Weise deaktivieren, z. B. <script>
während andere zugelassen werden, damit ich sie sicher auf einer Webseite rendern kann. Ich habe eine Liste zulässiger Tags. Wie kann ich die Zeichenfolge verarbeiten, um andere Tags zu entfernen?
72
<img src="heh.png" onload="(function(){/* do bad stuff */}());" />
br
TagsAntworten:
Hier ist eine einfache Lösung mit 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()
Wenn Sie möchten , als auch, Ersatz , den Inhalt der ungültigen Tags entfernen
tag.extract()
fürtag.hidden
.Sie könnten auch die Verwendung von lxml und Tidy untersuchen .
quelle
from BeautifulSoup import BeautifulSoup
.Verwenden Sie
lxml.html.clean
! Es ist sehr leicht!from lxml.html.clean import clean_html print clean_html(html)
Angenommen, das folgende 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>'''
Die Ergebnisse...
<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>
Sie können die Elemente anpassen, die Sie reinigen möchten und so weiter.
quelle
lxml.html.clean.clean()
Methode finden Sie in der Dokumentzeichenfolge . Es hat viele Möglichkeiten!allow_tags
.Die oben genannten Lösungen über Beautiful Soup funktionieren nicht. Möglicherweise können Sie mit Beautiful Soup darüber hinaus etwas hacken, da Beautiful Soup den Zugriff auf den Analysebaum ermöglicht. In einer Weile werde ich versuchen, das Problem richtig zu lösen, aber es ist ein einwöchiges Projekt oder so, und ich habe bald keine freie Woche mehr.
Um genau zu sein, wird Beautiful Soup nicht nur Ausnahmen für einige Analysefehler auslösen, die der obige Code nicht abfängt. Es gibt aber auch viele sehr reale XSS-Schwachstellen, die nicht erkannt werden, wie zum Beispiel:
<<script>script> alert("Haha, I hacked your page."); </</script>script>
Wahrscheinlich ist das Beste, was Sie tun können, stattdessen das
<
Element als<
zu entfernen, um den gesamten HTML-Code zu verbieten , und dann eine eingeschränkte Teilmenge wie Markdown zu verwenden, um die Formatierung ordnungsgemäß zu rendern. Insbesondere können Sie auch zurückgehen und allgemeine HTML-Teile mit einem regulären Ausdruck wieder einführen. So sieht der Prozess ungefähr aus:_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'<\1>', text) text = _sqrt_.sub(r'√<span style="text-decoration:overline;">', text) text = _endsqrt_.sub(r'</span>', text) return _tcre_.sub('<', text)
Ich habe diesen Code noch nicht getestet, daher kann es zu Fehlern kommen. Aber Sie sehen die allgemeine Idee: Sie müssen den gesamten HTML-Code im Allgemeinen auf die schwarze Liste setzen, bevor Sie die OK-Liste auf die Whitelist setzen.
quelle
Folgendes verwende ich in meinem eigenen Projekt. Die akzeptablen_Elemente / Attribute stammen von feedparser und BeautifulSoup erledigt die Arbeit.
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
Einige kleine Tests, um sicherzustellen, dass sich dies korrekt verhält:
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
quelle
<<script></script>script> alert("Haha, I hacked your page."); <<script></script>script>
tag.extract()
ändert das eine Liste, über die wir iterieren. Das verwirrt die Schleife und führt dazu, dass das nächste Kind übersprungen wird.Bleichen macht es besser mit nützlicheren Optionen. Es basiert auf html5lib und ist produktionsbereit. Überprüfen Sie die Dokumentation für die
bleack.clean
Funktion. Die Standardkonfiguration entgeht unsicheren Tags wie<script>
und ermöglicht nützliche Tags wie<a>
.import bleach bleach.clean("<script>evil</script> <a href='http://example.com'>example</a>") # '<script>evil</script> <a href="http://example.com">example</a>'
quelle
data:
zum Beispiel eine URL mit dem Inhaltstyp HTML einbetten .Ich veränderte Bryan ‚s Lösung mit BeautifulSoup die Adresse Problem von Chris Drost angehoben . Ein bisschen grob, macht aber den Job:
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
Bearbeiten: Aktualisiert, um gültige Attribute zu unterstützen.
quelle
tag.attrs = [(attr, value) for attr, value in tag.attrs if attr in valid_tags[tag.name]]
- tag.attrs ist ein Diktat, daher sollte diestag.attrs = {attr: value for attr, value in tag.attrs.items() if attr in valid_tags[tag.name]}
die bs4Ich benutze FilterHTML . Es ist einfach und ermöglicht es Ihnen, eine gut kontrollierte Whitelist zu definieren, URLs zu bereinigen und sogar Attributwerte mit Regex abzugleichen oder benutzerdefinierte Filterfunktionen pro Attribut zu verwenden. Bei sorgfältiger Anwendung kann dies eine sichere Lösung sein. Hier ist ein vereinfachtes Beispiel aus der Readme-Datei:
quelle
Sie können html5lib verwenden , das zur Bereinigung eine Whitelist verwendet.
Ein Beispiel:
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)
quelle
sanitizer_factory
es? Sie solltenHTMLSanitizer
direkt passieren .Ich ziehe die
lxml.html.clean
Lösung, wie nosklo Punkte aus . Hier sind auch einige leere Tags zu entfernen: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)
quelle