在Web开发中很多地方需要用户输入富文本但又要确保输入的这些内容绝对安全不会引发XSS漏洞,那么最常用的技术就是白名单技术。
白名单的通常做法都是构建一个允许使用的标签及对应属性的列表,然后对用户输入的HTML文本进行解析,解析出的tag及属性去白名单中进行查找,如果对应上了,那么就保留下来,没有对应上就进行移除。白名单的结构都是这样一个层次: 允许的tag->允许的属性->允许的属性值。拿img标签来举例,我们允许在博客中插入图片标签,那么img标签就被允许,img标签有很多的属性,比如src、alt、onerror、onload等等,其中src是必须的,alt属性无法执行任何javascript代码,但onerror和onload是可以执行javascript代码,因此只保留src和alt属性,src属性允许很多类型的值,比如以http开头的链接,以javascript:开头的bookmarklet还有以data:开头的data url, javascript:和data:在某些类型的浏览器里面都有引发XSS的风险,因此不能允许这两种类型的值,只允许http://开头的图片链接,这就需要继续对属性值进行匹配。每个标签完成这三个层次的匹配才能说是安全的,我们在设计白名单的时候这三个层次任何一个层次也都不能漏掉。
代码实现如下:
regex_cache = {}
def search(text, regex):
regexcmp = regex_cache.get(regex)
if not regexcmp:
regexcmp = re.compile(regex)
regex_cache[regex] = regexcmp
return regexcmp.search(text)
# XSS白名单
VALID_TAGS = {'h1':{}, 'h2':{}, 'h3':{}, 'h4':{}, 'strong':{}, 'em':{},
'p':{}, 'ul':{}, 'li':{}, 'br':{}, 'a':{'href':'^http://', 'title':'.*'},
'img':{'src':'^http://', 'alt':'.*'}}
def parsehtml(html):
soup = BeautifulSoup(html)
for tag in soup.findAll(True):
if tag.name not in VALID_TAGS:
tag.hidden = True
else:
attr_rules = VALID_TAGS[tag.name]
for attr_name, attr_value in tag.attrs:
#检查属性类型
if attr_name not in attr_rules:
del tag[attr_name]
continue
#检查属性值格式
if not search(attr_value, attr_rules[attr_name]):
del tag[attr_name]
return soup.renderContents()
下面拿一段html来做测试:
if __name__ == '__main__':
text = '''
Hello!
Hello
alert(1);' title='sddasdsadsd'/>
'''
print parsehtml(text) 过滤的结果:
Hello!
Hello
alert(1);' title='sddasdsadsd'/>
效果还不错~