防止CSRF攻击与针对CSRF方法接口测试的调整

发表于:2017-9-11 10:59

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:顾翔    来源:51Testing软件测试网原创

  一、CSRF攻击介绍
  CSRF(Cross-site request forgery)跨站请求伪造,也被称为"One Click Attack"或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与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攻击。
  二、Django是如何防范CSRF攻击的
  首先我们确认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%}这个标记。现在我们以登录模块来分析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位会发生变化的字符串,然后把这csrf token放入到cookie中(所以要是用CSRF防御机制,必须打开浏览器的cookie),并且放到页面的form表单中,产生一个类似于"<input type='hidden' name='csrfmiddlewaretoken' value='csrf token'"的表单,然后在提交表单的时候验证cookie中的值是不是与hidden的值保持一致,如果保持一致,则返回200代码,否则返回403拒绝访问代码。
  三、接口测试代码的调整
  为了适应CSRF的加入,我们必须对我们的测试代码进行调整,由于我们对代码进行了很好的封装,所以在这里只需要调整测试代码util.py中Util类中的run_test()方法。我先把改造后的代码展示给大家。
#运行测试接口
# mylist测试数据
# values登录数据
def run_test(self,mylist,values,sign):
#获取测试URL
Login_url = self.url+"/login_action/" #login_Url为登录的URL
run_url = mylist["Url"] #run_url为运行测试用例所需的URL
#获取csrf_token
data = self.s.get(Login_url)
csrf_token = data.cookies["csrftoken"]
#初始化登录变量
#获取登录数据
username = values.split(',')[1].strip("\"")
password = values.split(',')[2].strip("\"")
#判断当前测试是否需要登录
if sign:
#使用当前用户登录系统
payload ={"username":username,"password":password,"csrfmiddlewaretoken":csrf_token}
try:
data = self.s.post(Login_url,data=payload)
except Exception as e:
print(e)
#运行测试接口
try:
#为POST请求,由于post请求参数肯定是存在的,所以这里不判断有无参数
if mylist["Method"] == "post":
#有请求参数
payload = eval(mylist["InptArg"])
payload["csrfmiddlewaretoken"] = csrf_token
data = self.s.post(run_url,data=payload)
#为GET请求
elif mylist["Method"] == "get":
if mylist["InptArg"].strip()=="":
#没有请求参数
data = self.s.get(run_url)
else:
#有请求参数
payload = eval(mylist["InptArg"])
data = self.s.get(run_url,params=payload)
except Exception as e:
print(e)
else:
return data
  首先我们通过代码:"data = self.s.get(Login_url)"访问登录页面,然后通过代码"csrf_token = data.cookies["csrftoken"]"获取产生的CSRF令牌cookie。我们在初始化登录操作与执行post操作的时候把令牌参数csrf_token加入到post参数中。在初始化登录操作中代码为:
#使用当前用户登录系统
payload ={"username":username,"password":password,"csrfmiddlewaretoken":csrf_token}
try:
data = self.s.post(Login_url,data=payload)
  执行post操作中代码为:
#为POST请求,由于post请求参数肯定是存在的,所以这里不判断有无参数
if mylist["Method"] == "post":
#有请求参数
payload = eval(mylist["InptArg"])
payload["csrfmiddlewaretoken"] = csrf_token
data = self.s.post(run_url,data=payload)
  查看了网站上许多资料,都是要求在接口测试前利用requests.get(url,cookies=cookies)设置一个cookie,我采用了这个方法是根本行不通的,因为访问页面的时候,这个cookie是已经产生的,所以我们首先需要获取这个cookie值,然后把它放到post方法的变量中(也不是向有些网站说的再把这个cookie设置进别的页面)。
版权声明:51Testing软件测试网及内容提供者拥有本文全部版权,未经明确的书面许可,任何人或单位不得对本文进行复制、转载或镜像,否则将追究法律责任。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号