78 votes

Suppression des balises HTML ne figurant pas sur une liste autorisée dans une chaîne Python

J'ai une chaîne de caractères contenant du texte et du HTML. Je veux supprimer ou désactiver d'une autre manière certaines balises HTML, telles que <script> tout en autorisant les autres, afin que je puisse les rendre sur une page web en toute sécurité. J'ai une liste de balises autorisées, comment puis-je traiter la chaîne pour supprimer toutes les autres balises ?

6 votes

Il devrait également supprimer tous les attributs pas sur la liste blanche... considérer <img src="heh.png" onload="(function(){/* do bad stuff */}());" />

0 votes

Et aussi les balises vides inutiles et peut-être consécutives br tags

1 votes

Notez que les deux premières réponses sont dangereuses, car il est très facile de cacher XSS à partir de BS/lxml.

71voto

nosklo Points 75862

Utilice lxml.html.clean ! C'est TRES facile !

from lxml.html.clean import clean_html
print clean_html(html)

Supposons le html suivant :

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>'''

Les résultats...

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

Vous pouvez personnaliser les éléments que vous voulez nettoyer et ainsi de suite.

1 votes

Voir la docstring pour lxml.html.clean.clean() méthode. Il y a beaucoup d'options !

2 votes

Notez que cette approche utilise une liste noire pour filtrer les mauvais bits, plutôt qu'une liste blanche, mais seule une approche de liste blanche peut garantir la sécurité.

6 votes

@SørenLøvborg : Le Nettoyeur supporte également une liste blanche, utilisant allow_tags .

50voto

bryan Points 1248

Voici une solution simple utilisant BelleSoupe :

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()

Si vous souhaitez également supprimer le contenu des balises non valides, remplacez le texte par tag.extract() pour tag.hidden .

Vous pouvez également envisager d'utiliser lxml y Tidy .

0 votes

Merci, je n'avais pas besoin de ce distributeur, mais je savais que j'aurais besoin de trouver quelque chose de ce genre à l'avenir.

1 votes

L'instruction d'importation devrait probablement être from BeautifulSoup import BeautifulSoup .

9 votes

Vous pouvez également souhaiter limiter l'utilisation des attributs. Pour cela, il suffit d'ajouter ceci à la solution ci-dessus : valid_attrs = 'href src'.split() for ... : ... tag.attrs = [(attr, val) for attr, val in tag.attrs if attr in valid_attrs] hth

41voto

Les solutions ci-dessus via Beautiful Soup ne fonctionneront pas. Il est possible de faire quelque chose avec Beautiful Soup en plus de ces solutions, car Beautiful Soup donne accès à l'arbre d'analyse. Dans quelque temps, je pense que je vais essayer de résoudre le problème correctement, mais c'est un projet d'une semaine environ, et je n'ai pas de semaine libre prochainement.

Pour être précis, non seulement Beautiful Soup lèvera des exceptions pour certaines erreurs d'analyse que le code ci-dessus n'attrape pas, mais il existe également de nombreuses vulnérabilités XSS bien réelles qui ne sont pas attrapées, comme par exemple :

<<script>script> alert("Haha, I hacked your page."); </</script>script>

La meilleure chose que vous puissiez faire est de retirer les éléments suivants < en tant qu'élément &lt; d'interdire todo HTML, puis utiliser un sous-ensemble restreint comme Markdown pour rendre le formatage correctement. En particulier, vous pouvez aussi revenir en arrière et réintroduire des éléments communs du HTML avec une regex. Voici à quoi ressemble le processus, en gros :

_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'&radic;<span style="text-decoration:overline;">', text)
    text = _endsqrt_.sub(r'</span>', text)
    return _tcre_.sub('&lt;', text)

Je n'ai pas encore testé ce code, il peut donc y avoir des bogues. Mais vous voyez l'idée générale : vous devez mettre sur liste noire tout le HTML en général avant de mettre sur liste blanche les choses correctes.

25voto

Jochen Ritzel Points 42916

Voici ce que j'utilise dans mon propre projet. Les éléments/attributs acceptables proviennent de analyseur d'alimentation et BeautifulSoup fait le travail.

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

Quelques petits tests pour s'assurer que cela se comporte correctement :

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

3 votes

Ce n'est pas sûr ! Voir la réponse de Chris Dost : stackoverflow.com/questions/699468/

1 votes

Thomas : Avez-vous quelque chose à l'appui de cette affirmation ? Le code "non sécurisé" de Chris Dost ne fait que lever une exception, donc je suppose que vous ne l'avez pas vraiment essayé.

3 votes

@THC4k : Désolé, j'ai oublié de préciser que j'ai dû modifier l'exemple. En voici un qui fonctionne : <<script></script>script> alert("Haha, I hacked your page."); <<script></script>script>

25voto

chuangbo Points 51

Eau de Javel fait mieux avec des options plus utiles. Il est construit sur html5lib et prêt pour la production. Consultez la documentation pour le bleach.clean fonction. Sa configuration par défaut échappe les balises non sécurisées comme <script> tout en autorisant des balises utiles comme <a> .

import bleach
bleach.clean("<script>evil</script> <a href='http://example.com'>example</a>")
# '&lt;script&gt;evil&lt;/script&gt; <a href="http://example.com">example</a>'

0 votes

Est-ce que bleach permet toujours les données : urls via html5lib par défaut ? On peut intégrer un data: url avec un type de contenu de html par exemple.

0 votes

2019, et j'ai du mal avec ça : stackoverflow.com/questions/7538600/ - pour moi, lxml.html.cleaner était plus solide, en supprimant complètement les balises de style, alors que bleach vous laisse avec votre css visible comme contenu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X