第4章构建安全的网站
4.1 密码的加密
在第2.3.2节中提醒过大家,前面的代码是明文存储密码的,其实这是很危险的,在这里将对密码进行M5加密,以保证信息的安全。在goods/util.py中定义加密方法md5()。
... import hashlib ... #MD5加密 def md5(self,mystr): if isinstance(mystr,str): m = hashlib.md5() m.update(mystr.encode('utf8')) return m.hexdigest() else: return "" ...
注意:加密字符串mystr必须转为bytes,才可以被加密。然后在注册和登录代码中分别调用该方法。
... #用户注册 def register(request): ... #获取密码信息 #加密password password = util.md5(password) ... #用户登录 def login_action(request): ... username = (request.POST.get('username')).strip() password = (request.POST.get('password')).strip() #加密password password = util.md5(password) ... # 修改用户密码 def change_password(request): ... if request.method == "POST": #获取旧密码 oldpassword=util.md5((request.POST.get("oldpassword", "")).strip()) #获取新密码 newpassword=util.md5((request.POST.get("newpassword", "")).strip()) #获取新密码的确认密码 checkpassword=util.md5((request.POST.get("checkpassword", "")).strip()) ...
由于使用MD5对密码进行加密了,同样也需要对测试程序interface/util.py进行下调整。
... #初始化信息 def inivalue(self,dataBase,ordertable,sign): ... #建立记录 if(sign!="0"):#sign=0,密码需要加密,否则需要加密 self.insertTable(dataBase,ordertable,values) #处理在用户注册的时候,需要将密码MD5处理 else: dom = minidom.parse("initInfo.xml") self.root = dom.documentElement password = self.root.getElementsByTagName('password') password = str(password[0].firstChild.data).strip() md5password = self.md5(password) newvalues = values.replace(password,md5password) self.insertTable(dataBase,ordertable,newvalues) return values ...
4.2 防止CSRF攻击
4.2.2 CSRF攻击介绍
跨站请求伪造(Cross-siterequest forgery: CSRF),也被称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。听起来有点像跨站脚本(在第4.4中进行介绍),但它与XSS是不同的,XSS利用的是站点内信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任网站。与XSS攻击相比,CSRF不是流行的攻击方式,对其进行防范的资源也相当稀少,且难以防范,所以业界认为其比XSS更具危险性。
用一个POST请求做个比方,黑客可以构建自己的网页form界面,form的action指向要攻击的网站,form中元素的name与攻击网站的值保持一致,从而达到CSRF攻击的目的。
比如被攻击的网站是http://www.a.com,页面提交网站是http://www.a.com/input.html,提交后处理的网站是http://www.a.com/display.jsp,input.html的网页内容如下。
... <form action="display.jsp" method="post" > 地址:<input type="text" name="address" id="id_address" size="20" maxlength="100" required /> 电话:<input type="text" name="phone" id="id_ phone" size="20" maxlength="100" required /> </form> ...
现在在本地构造一个界面来冒充input.html。
... <form action="http://www.a.com/display.jsp" method="post" > 地址:<input type="text" name="address" id="id_address" size="20" required /> 电话:<input type="text" name="phone" id="id_ phone" size="20"required /> </form> ...
这样黑客就可以用自己的页面向http://www.a.com/display.jsp发起攻击了。在作者著作《软件测试技术实战设计、工具及管理》一书中序言中曾经提及这么一件事情:
“2000年我所在的公司与CCTV‘开心辞典’目组合作开发网上答题的项目,这是一个智力娱乐性节目,我编写了前端的答题代码,考虑到可能有人用计算机程序来答题,比如编写一个死循环,一直选择B(或A或 C或 D),这可以使答题的速度很快,命中率也非常高,为此我选用JavaScript过滤了使用死循环的答题者。可是到了开心辞典’正式使用这个软件的时候,发现仍然有人使用死循环来答题,可我的程序是正确的。后来在一次聊天模块中,通过登录账号找到了这位‘达人’,他说我们前端的确没有漏洞,他是通过自己编写的程序绕过我们前端进入到系统后端的,而我们的后端并没有进行校验。当初如果有专业的测试人员,这个Bug是有可能避免的。”其实这就是一个很典型的CSRF攻击。
4.2.3 Django是如何防范CSRF攻击的
在第2.3.2节就介绍过Django是如何防范CSRF攻击机制的,而且Django默认是启动CSRF攻击机制的,在本书前几个章介绍的重点不在这里,所以把setting.py中的这个开关给关了。现在进入setting.py打开这个开关。
... MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]...
见粗体字部分。然后在所有模板有表单提交(<form>...</form>)的地方都加上一个{% csrf_token%}这个标记。最后把views.py中所有render_to_response()方法用render()方法替换(csrf不支持render_to_response()方法,正如2.4.2中所述render_to_response()逐步被render()取代)。现在以登录模块来分析Django是如何防范CSRF攻击的。在此之前,打开一个HTTP抓包工具,作者这里用的是Fiddle 4,然后进入登录界面,查看网页源代码会发现。
... <input type='hidden' name='csrfmiddlewaretoken' value='XltpK31i171tGLIH2leLWio0xM5TY8NC56oaU58CiIc5xLfqSiiehfJDSEnZesrX ' /> ...
也就是说{%csrf_token%}被一个名为csrfmiddlewaretoken的hidden类型给取代了。其值为XltpK31i171tGLIH2leLWio0xM5TY8NC56oaU58CiIc5xLfqSiiehfJDSEnZesrX一个一百位的字符串,然后查看Fiddle 4,会看见页面产生了一个名为csrftoken的cookie,其值也为XltpK31i171tGLIH2leLWio0xM5TY8NC56oaU58CiIc5xLfqSiiehfJDSEnZesrX,如图4-1所示。
图4-1 产生的cookie
如果刷新这个登录页面,发现这100个字符串会发生相应地变化,但是cookie的值与hidden中的值是永远保持一致的。后来作者查询了一些资料,发现不仅仅是Django是用这种方式处理CSRF注入的,其他大部分系统都是使用这种方法处理CSRF注入的。即在用户登录这个网站的时候产生一个叫做csrf token(csrf令牌)的随机字符串,即前面提到的100位会发生随机变化的字符串,然后把这csrftoken放入到cookie中(所以要是用CSRF防御机制,必须打开浏览器的cookie),并且放到页面的form表单中,产生一个类似于<input type='hidden' name='csrfmiddlewaretoken' value='csrf token'的表单,然后在提交表单的时候验证cookie中的值是不是与hidden的值保持一致,如果保持一致,则返回200代码,否则返回403拒绝访问代码。参见图4-2。
图4-2 CSRF防范示意图
星云测试
奇林软件
联合通测
顾翔凡言:
软件测试正在生病,而且病得不轻,自动化测试被要不吹得太火,要不一点都不会,自动化比不过开发、测试又找不到缺陷,丢了西瓜也捡不到芝麻。