原理分析:
XSS(Cross Site Script)攻击又叫做跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。他的原理是用户在使用具有XSS漏洞的网站的时候,向这个网站提交一些恶意的代码,当用户在访问这个网站的某个页面的时候,这个恶意的代码就会被执行,从而来破坏网页的结构,获取用户的隐私信息等。
攻击场景:
比如A网站有一个发布帖子的入口,如果用户在提交数据的时候,提交了一段js代码比如:<script>alert("hello world");</script>,然后A网站在渲染这个帖子的时候,直接把这个代码渲染了,那么这个代码就会执行,会在浏览器的窗口中弹出一个模态对话框来显示hello world!如果攻击者能成功的运行以上这么一段js代码,那他能做的事情就有很多很多了!
XSS攻击的危害包括:
1、盗取各类用户帐号,如机器登录帐号、用户网银帐号、各类管理员帐号
2、控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能力
3、盗窃企业重要的具有商业价值的资料
4、非法转账
5、强制发送电子邮件
6、网站挂马
7、控制受害者机器向其它网站发起攻击
XSS漏洞的分类
XSS漏洞按照攻击利用手法的不同,有以下三种类型:
类型A
,本地利用漏洞,这种漏洞存在于页面中客户端脚本自身。其攻击过程如下所示:
Alice给Bob发送一个恶意构造了Web的。
Bob点击并查看了这个URL。
恶意页面中的JavaScript打开一个具有漏洞的HTML页面并将其安装在Bob电脑上。
具有漏洞的HTML页面包含了在Bob电脑本地域执行的JavaScript。
Alice的恶意脚本可以在Bob的电脑上执行Bob所持有的权限下的命令。
类型B
,反射式漏洞,这种漏洞和类型A有些类似,不同的是Web客户端使用Server端脚本生成页面为用户提供数据时,如果未经验证的用户数据被包含在页面中而未经HTML实体编码,客户端代码便能够注入到动态页面中。其攻击过程如下:
Alice经常浏览某个网站,此网站为Bob所拥有。Bob的站点运行Alice使用用户名/密码进行登录,并存储敏感信息(比如银行帐户信息)。
Charly发现Bob的站点包含反射性的XSS漏洞。
Charly编写一个利用漏洞的URL,并将其冒充为来自Bob的邮件发送给Alice。
Alice在登录到Bob的站点后,浏览Charly提供的URL。
嵌入到URL中的恶意脚本在Alice的浏览器中执行,就像它直接来自Bob的服务器一样。此脚本盗窃敏感信息(授权、信用卡、帐号信息等)然后在Alice完全不知情的情况下将这些信息发送到Charly的Web站点。
类型C
,存储式漏洞,该类型是应用最为广泛而且有可能影响到Web服务器自身安全的漏洞,骇客将攻击脚本上传到Web服务器上,使得所有访问该页面的用户都面临信息泄漏的可能,其中也包括了Web服务器的管理员。其攻击过程如下:
Bob拥有一个Web站点,该站点允许用户发布信息/浏览已发布的信息。
Charly注意到Bob的站点具有类型C的XSS漏洞。
Charly发布一个热点信息,吸引其它用户纷纷阅读。
Bob或者是任何的其他人如Alice浏览该信息,其会话cookies或者其它信息将被Charly盗走。
类型总结:
类型A直接威胁用户个体,而类型B和类型C所威胁的对象都是企业级Web应用。
django下的小例子模拟xss攻击中的类型B:
废话不多说,直接上代码
models.py:
from django.db import models
# Create your models here.
class School(models.Model):
name = models.CharField('名字',max_length=50)
address = models.CharField('地址',max_length=100)
content = models.TextField('描述内容',max_length=50,default='')
def __str__(self):
return self.name
class Meta:
verbose_name='学校'
verbose_name_plural=verbose_name
class Person(models.Model):
username=models.CharField('用户名',max_length=20)
age = models.IntegerField('年龄')
school = models.ForeignKey(School,on_delete=models.CASCADE,verbose_name='学校')
def __str__(self):
return self.username
class Meta:
verbose_name='人物'
verbose_name_plural=verbose_name
myapp/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('index/', views.index,name='comment'),
]
myapp/views.py
from django.shortcuts import render,redirect,reverse
from .models import School
def index(request):
print(request.method)
if request.method=='GET':
context = {
'comments': School.objects.all()
}
return render(request, 'myapp/index.html', context=context)
if request.method == 'POST':
content = request.POST.get('content')
School.objects.create(content=content)
return redirect(reverse('comment'))
myapp/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>xss index page</h1>
<ul>
{% for comment in comments %}
<li>{{ comment.content|safe }}</li>{# 这里必须设置safe去掉django的防御 #}
{% endfor %}
</ul>
<form action="{% url 'comment' %}" method="post">
{% csrf_token %}
<textarea name="content" id="" cols="30" rows="3"></textarea>
<p>
<input type="submit" value="提交">
</p>
</form>
</body>
</html>
执行后如下图:
此时我往输入框中输入script代码:<script>alert("hello world");</script>
点击提交后结果如图:
点击确定后:
此时数据库中:
如果此时我再往输入框中输入script代码:<script> window.onload = function () { var imgTag = document.createElement("img"); imgTag.setAttribute('src','http://img.haote.com/upload/news/image/20170605/20170605144101_12960.jpg'); document.body.appendChild(imgTag); } </script>
同样提交后:
xss攻击防御原理:
我们只要转义掉前段传送过来的html标签即可,比如大于小于号,或者判断script
字符是否存在,注意,这里不建议判断,因为随便加几个tab或者空格即可让你的判断失效!所以转义script前后的大于小于号才是重点,利用django很容易做到这件事,比如在html中去掉加入safe标签(默认情况下django是不加safe的,也就是会默认过滤转义掉几乎一切的违规字符),当然你也可以直接在view中利用django.template.defaultfilters.escape()方法,来直接对传送进来的字符串进行手动转义,这样当存进去数据库的时候就不会有"<",">",取而代之的是“<”和“>”,其他字符同理。。。下面给出了escape()默认转义的字符,它是django内定的:
_html_escapes = {
ord('&'): '&',
ord('<'): '<',
ord('>'): '>',
ord('"'): '"',
ord("'"): ''',
}
下面给出了index.html代码来解释escape()用法:
from django.shortcuts import render,redirect,reverse
from .models import School
from django.template.defaultfilters import escape #导入escape
def index(request):
print(request.method)
if request.method=='GET':
context = {
'comments': School.objects.all()
}
return render(request, 'myapp/index.html', context=context)
if request.method == 'POST':
content = request.POST.get('content')
content=escape(content) #也加了这句
School.objects.create(content=content)
return redirect(reverse('comment'))
再次输入同样的script代码后的数据库:
如果你对django转义需要研究,我在网上找到这篇文章适合你,但是有些地方不妥,有空我会重新整理这篇文章进行更正!但是错误的也就一两处不伤大雅!
DJANGO基础学习之转义总结:escape,autoescape,safe,mark_safe
但是问题是:有时候我们需要允许一些html字符输出,而不是禁止掉一切字符,此时我们需要借助bleach库
bleach库:
bleach库是用来清理包含html格式字符串的库。他可以指定哪些标签需要保留,哪些标签是需要过滤掉的。也可以指定标签上哪些属性是可以保留,哪些属性是不需要的。想要使用这个库,可以通过以下命令进行安装:
pip install bleach
或者pip3 install bleach
这个库最重要的一个方法是bleach.clean方法,bleach.clean示例代码如下:
import bleach
from bleach.sanitizer import ALLOWED_TAGS,ALLOWED_ATTRIBUTES
@require_http_methods(['POST'])
def message(request):
# 从客户端中获取提交的数据
content = request.POST.get('content')
# 在默认的允许标签中添加img标签
tags = ALLOWED_TAGS + ['img']
# 在默认的允许属性中添加src属性
attributes = {**ALLOWED_ATTRIBUTES,'img':['src']}
# 对提交的数据进行过滤
cleaned_content=bleach.clean(content,tags=tags,attributes=attributes)
# 保存到数据库中
Message.objects.create(content=cleaned_content)
return redirect(reverse('index'))
相关介绍如下:
-tags:
表示允许哪些标签。
-attributes:
表示标签中允许哪些属性。
-ALLOWED_TAGS:
这个变量是bleach默认定义的一些标签。如果不符合要求,可以对其进行增加或者删除。有默认值,可选参数。
-ALLOWED_ATTRIBUTES:
这个变量是bleach默认定义的一些属性。如果不符合要求,可以对其进行增加或者删除。有默认值,可选参数。
bleach更多资料:
tags默认允许的标签:
attributes默认允许的属性:
- github地址: https://github.com/mozilla/bleach
- 文档地址: https://bleach.readthedocs.io/
在上面我们举的小例子中views.py代码只要这样写:
from django.shortcuts import render,redirect,reverse
from .models import School
import bleach
from bleach.sanitizer import ALLOWED_TAGS,ALLOWED_ATTRIBUTES
from django.template.defaultfilters import escape
# Create your views here.
def index(request):
print(request.method)
if request.method=='GET':
context = {
'comments': School.objects.all()
}
return render(request, 'myapp/index.html', context=context)
if request.method == 'POST':
content = request.POST.get('content')
# content=escape(content)
tags = ALLOWED_TAGS + ['img']
attributes = {**ALLOWED_ATTRIBUTES, 'img': ['src']}
cleaned_data = bleach.clean(content, tags=tags, attributes=attributes)
# cleaned_data = bleach.clean(content)
# School.objects.create(content=content)
School.objects.create(content=cleaned_data)
return redirect(reverse('comment'))
同样继续执行后输入script代码,执行后结果:
数据库中:
上面因为转义掉了大于小于号,所以就无法输出图片,接下来我们继续输入这段js代码:
<img src='http://img.haote.com/upload/news/image/20170605/20170605144101_12960.jpg' />
,然后结果如下:
由上面可知,script
是bleach默认不允许的,被bleach库转义了,而我们手动放松了img
标签的通过,这样就
达到了我们随心所欲的控制html标签的转义与否!!
此时数据库中: