django下的xss攻击原理分析和防御实战

原理分析: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>

执行后如下图:

image.png

此时我往输入框中输入script代码:<script>alert("hello world");</script>
点击提交后结果如图:
image.png

点击确定后:
-2.png

此时数据库中:
-1.png

如果此时我再往输入框中输入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>
同样提交后:
image.png

xss攻击防御原理:我们只要转义掉前段传送过来的html标签即可,比如大于小于号,或者判断script字符是否存在,注意,这里不建议判断,因为随便加几个tab或者空格即可让你的判断失效!所以转义script前后的大于小于号才是重点,利用django很容易做到这件事,比如在html中去掉加入safe标签(默认情况下django是不加safe的,也就是会默认过滤转义掉几乎一切的违规字符),当然你也可以直接在view中利用django.template.defaultfilters.escape()方法,来直接对传送进来的字符串进行手动转义,这样当存进去数据库的时候就不会有"<",">",取而代之的是“<”和“&gt”,其他字符同理。。。下面给出了escape()默认转义的字符,它是django内定的:

_html_escapes = {
    ord('&'): '&amp;',
    ord('<'): '&lt;',
    ord('>'): '&gt;',
    ord('"'): '&quot;',
    ord("'"): '&#39;',
}

下面给出了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代码后的数据库:
0.png

如果你对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默认允许的标签:

image.png

attributes默认允许的属性:
image.png

  1. github地址: https://github.com/mozilla/bleach
  2. 文档地址: 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代码,执行后结果:


0.5.png

数据库中:


1.png

上面因为转义掉了大于小于号,所以就无法输出图片,接下来我们继续输入这段js代码:<img src='http://img.haote.com/upload/news/image/20170605/20170605144101_12960.jpg' />,然后结果如下:

2.png

由上面可知,script是bleach默认不允许的,被bleach库转义了,而我们手动放松了img标签的通过,这样就
达到了我们随心所欲的控制html标签的转义与否!!
此时数据库中:

3.png

本文参考多方面的文章,不可一一给出源地址,请见谅

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容