在使用Python中的request模块的post请求时,由于网站开启了csrf跨站请求攻击,会出现403错误,因为我们在使用post的时候没有携带csrf数据去验证,网站会不认可我们,因此我们需要第一次的时候使用get请求,然后使用re正则匹配到这个csrf-token命令,取出来这个命令,然后在使用post发送请求,在请求中的数据中添加csrf的键值对,然后就可以使用post访问到网上了,并且也可以post请求携带数据。
就是先访问一次登录页,然后从登录页中查找一个隐藏的authenticity_token字段
#登录源码:
def login(login_url = 'http://****.com/users/sign_in', username, password):
#请求头
my_headers = {
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36',
'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding' : 'gzip',
'Accept-Language' : 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4'
}
#获取token
sss = requests.Session()
r = sss.get(login_url, headers = my_headers)
reg = r''
pattern = re.compile(reg)
result = pattern.findall(r.content)
token = result[0]
#postdata
my_data = {
'commit' : '登录',
'utf8' : '%E2%9C%93',
'authenticity_token' : token,
'user[email]': username,
'user[password]':password
}
#登录后
r = sss.post(login_url, headers = my_headers, data = my_data)
return sss
其实,该token的作用就是防御CSRF攻击,关于什么是CSRF,还得先了解下Session id。
关于Session id的机制
HTTP请求的一大特点就是无状态,这也就导致服务端无法区分请求来自哪个客户端。为了记录每个用户的状态,跟踪用户的整个会话,web程序普遍采用了cookie与session技术。(由于cookie与session的内容过多,在此不表,详细原理可以参考一片文章:Cookie与Session机制)
关于cookie与session,最需要了解的几点是:
session机制运行依赖于session id,用于服务端跟踪每个会话,而session id存在于本地的cookie当中;
session id会随浏览器进程关闭的关闭而清除,也就表示一次完整的会话结束了。当下次再次访问该网站还需要登录,重新建立一个会话;
现在绝大多数浏览器都支持子窗体,子窗体能共享父窗体的session id,而另起的浏览器进程无法访问该session。这也是为什么当我们在某网站登录后,在新的页签下打开该网站依然是登录状态,而另起一个浏览器进程访问却是非登录状态。
根据session机制以上特点,就引申出了一个问题:CSRF攻击。
什么是跨站请求伪造(CSRF)攻击?
用户每次点击一个链接、提交一个表单,其本质就是对服务端发起一次请求。而CSRF攻击的原理就是:攻击者诱导用户点击一个链接,用户在不知情的情况下提交了一次表单请求。而表单的内容则是攻击者事先准备好的。
简单举个栗子:
用户小明登录了论坛A,同时也打开了一个危险网站B(同一个浏览器中);
网站B上有一个链接,该链接的实质内容是针对论坛A的一个发帖请求(比如广告贴)。
小明处于好奇点击了该链接,造成的结果就是:小明在完全不知情的情况下在论坛A成功发表了一篇帖子。
备注: 以上攻击成功实施的关键在于,小明已经登录论坛A,并且点击跳转后的浏览器子窗体是可以访问父窗体的session id的。
假如小明复制该链接,然后手动打开一个新的浏览器粘贴访问该链接,则会提示用户处于非登录状态,该发帖请求会被拒绝。原因是新打开的浏览器无法获取前一个浏览器中的session id,服务端会将该请求当成一个新的会话,需要重新登录后才能成功执行发帖请求。
CRSF攻击防御
既然大家都了解CRSF攻击,自然有相应的防御措施,其中比较常用的就是采用token验证。
工作机制就是:用户在发送表单时还需要携带一个token值。该token一般是填写表单页中的一个隐藏字段,每次访问都不同。通过该token的验证,服务端就能知道用户的表单请求是否从表单填写页面跳转而来了。
简单举例:
当小明主动发帖时,必定要先点击发帖编辑页面A,当填写完帖子内容后再点击【发帖】按钮。此时会将小明填写的表单内容连带页面A中隐藏的一个token发送给服务端。服务端验证token通过后才表示发帖成功。
当危险网站诱导小明点击危险链接时,由于该链接实质就是一个发帖的post请求,跳过了访问发帖编辑页面A的过程,自然也就无法获取有效token,最终服务端会认为该发帖请求不合法。
简单来说,服务端每次通过请求数据中的token来验证表单请求是否由用户主动发送的,从而有效防御了CRSF攻击。
至此,也就明白了为什么登录页面时需要携带一个authenticity_token参数了,同时也理解了为什么需要访问登录页面获取该token。