Django安全

Internet并不安全。


现如今,每天都会出现新的安全问题。我们目睹过病毒飞速地蔓延,大量被控制的肉鸡作为武器来攻击其他人,与垃圾邮件的永无止境的军备竞赛,以及许许多多站点被黑的报告。

作为web开发人员,我们有责任来对抗这些黑暗的力量。每一个web开发者都应该把安全看成是web编程中的基础部分。不幸的是,要实现安全是困难的。攻击者只需要找到一个微小的薄弱环节,而防守方却要保护得面面俱到。

Django试图减轻这种难度。它被设计为自动帮你避免一些web开发新手(甚至是老手)经常会犯的错误。尽管如此,需要弄清楚,Django如何保护我们,以及我们可以采取哪些重要的方法来使得我们的代码更加安全。

首先,一个重要的前提:我们并不打算给出web安全的一个详尽的说明,因此我们也不会详细地解释每一个薄弱环节。在这里,我们会给出Django所面临的安全问题的一个大概。

Web安全现状

如果你从这章中只学到了一件事情,那么它会是:

在任何条件下都不要相信浏览器端提交的数据。

你从不会知道HTTP连接的另一端会是谁。可能是一个正常的用户,但是同样可能是一个寻找漏洞的邪恶的骇客。

从浏览器传过来的任何性质的数据,都需要近乎狂热地接受检查。这包括用户数据(比如web表单提交的内容)和带外数据(比如,HTTP头、cookies以及其他信息)。要修改那些浏览器自动添加的元数据,是一件很容易的事。

在这一章所提到的所有的安全隐患都直接源自对传入数据的信任,并且在使用前不加处理。你需要不断地问自己,这些数据从何而来。

SQL注入

SQL注入是一个很常见的形式,在SQL注入中,攻击者改变web网页的参数(例如GET/POST数据或者URL地址),加入一些其他的SQL片段。未加处理的网站会将这些信息在后台数据库直接运行。这也许是最危险的一种,然而不幸的是,也是最多的一种隐患。

这种危险通常在由用户输入构造SQL语句时产生。例如,假设我们要写一个函数,用来从通信录搜索页面收集一系列的联系信息。为防止垃圾邮件发送器阅读系统中的email,我们将在提供email地址以前,首先强制用户输入用户名。

def user_contacts(request):

user = request.GET['username']

sql = "SELECT * FROM user_contacts WHERE username = '%s';" % username

# execute the SQL here...

备注

在这个例子中,以及在以下所有的不要这样做的例子里,我们都去除了大量的代码,避免这些函数可以正常工作。我们可不想这些例子被拿出去使用。

尽管,一眼看上去,这一点都不危险,实际上却不尽然。

首先,我们对于保护email列表所采取的措施,遇到精心构造的查询语句就会失效。想象一下,如果攻击者在查询框中输入"' OR 'a'='a"。此时,查询的字符串会构造如下:

SELECT * FROM user_contacts WHERE username = '' OR 'a' = 'a';

由于我们允许不安全的SQL语句出现在字符串中,攻击者加入OR子句,使得每一行数据都被返回。

事实上,这是最温和的攻击方式。如果攻击者提交了"'; DELETE FROM user_contacts WHERE 'a' = 'a'",我们最终将得到这样的查询:

SELECT * FROM user_contacts WHERE username = ''; DELETE FROM user_contacts WHERE 'a' = 'a';

哦!我们整个通信录名单去哪儿了?

解决方案

尽管这个问题很阴险,并且有时很难发现,解决方法却很简单:绝不信任用户提交的数据,并且在传递给SQL语句时,总是转义它。

Django的数据库API帮你做了。它会根据你所使用的数据库服务器(例如PostgreSQL或者MySQL)的转换规则,自动转义特殊的SQL参数。

举个例子,在下面这个API调用中:

foo.get_list(bar__exact="' OR 1=1")

Django会自动进行转义,得到如下表达:

SELECT * FROM foos WHERE bar = '\' OR 1=1'

完全无害。

这被运用到了整个Django的数据库API中,只有一些例外:

§ 传给extra()方法的where参数(参见附录C)。这个参数接受原始的SQL语句。

§ 使用底层数据库API的查询。

以上列举的每一个示例都能够很容易的让您的应用得到保护。在每一个示例中,为了避免字符串被篡改而使用绑定参数来代替。也就是说,在本章中我们使用到的所有示例都应该写成如下所示:

from django.db import connection

def user_contacts(request):

user = request.GET['username']

sql = "SELECT * FROM user_contacts WHERE username = %s;"

cursor = connection.cursor()

cursor.execute(sql, [user])

# ... do something with the results

底层execute方法采用了一个SQL字符串作为其第二个参数,这个SQL字符串包含若干’%s’占位符,execute方法能够自动对传入列表中的参数进行转义和插入。

不幸的是,您并不是在SQL中能够处处都使用绑定参数,绑定参数不能够作为标识符(如表或列名等)。因此,如果您需要这样做我是说动态构建POST变量中的数据库表的列表的话,您需要在您的代码中来对这些数据库表的名字进行转义。Django提供了一个函数,django.db.backend.quote_name,这个函数能够根据当前数据库引用结构对这些标识符进行转义。

跨站点脚本 (XSS)

Web应用中,跨站点脚本(XSS)有时在被渲染成HTML之前,不能恰当地对用户提交的内容进行转义。这使得攻击者能够向你的网站页面插入通常以

你可能感兴趣的:(Django安全)