前面实现了一个漏洞百出的网站登录认证机制,这里在前儿的基础上加上一点安全机制
前言
攻击者对网站的登录认证机制发起攻击,通常会选择一个可正常访问网站的用户,利用漏洞非法获取该用户的使用权
一旦攻击者窃取了某个用户的身份登录网站,就可以获取该用户的一些个人信息和私密数据,甚至还能以该用户的身份发起一些恶意请求
而我们前面实现的认证机制,就存在一个可被利用的漏洞,攻击者能够借以找到一个网站的合法用户作为攻击目标
用户枚举
什么是用户枚举?
通常发生在用户登陆界面,攻击者可以根据服务器反馈的错误提示信息猜测用户的存在性
一般有两种攻击情况:
第一种是猜测具体用户的存在性,攻击者输入待猜测的用户名和随机口令,提交登陆,观察错误提示信息。若提示“密码输入错误”,则表明该用户存在;若提示“用户不存在”,表明该用户名未被注册(基于前篇实现的认证机制)
如下图,从错误提示信息可以推测出,用户 123 不存在,而用户 12345 存在
第二种情况是攻击者通过自动化脚本工具生成大量用户名并进行提交尝试,通过反馈的错误信息进行判断,猜测可能存在的用户
如下图,可以发现对用户 12345 ,服务器响应包的长度明显跟其他是不一样的,这是不是可以说明这个特殊的就是一个真实存在的用户
如此,成功找到一个正常的用户,就可以进行后续的攻击(口令爆破或认证绕过),非法盗用该用户的身份
防护手段
这个漏洞的本质体现就是,针对不同的异常,服务器反馈不同的错误提示信息,攻击者就可以根据错误信息确定服务器所发生的异常,从而判断出用户的存在信息
防范的手段很简单,如果服务器反馈的错误信息都一样,攻击者无法从错误信息中提取额外的信息,自然就不能由此判断用户的存在性
对代码进行如下修改
# views.py
from django.contrib.auth.hashers import check_password
from django.shortcuts import render, redirect
from Blog.models import User
def login(request):
if request.session.get('is_login', None): # 检查是否已登录
return redirect('/index/')
if request.method == 'POST': # 如果有提交表单
username = request.POST.get('username', '').strip()
password = request.POST.get('password', '').strip()
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return render(request, 'login.html', {'mess': '用户名或密码输入错误'})
else:
if check_password(password, user.password):
request.session['is_login'] = 'T' # 标记已登录
request.session['user_id'] = user.id
request.session['username'] = username
return redirect('/index/')
else:
return render(request, 'login.html', {'mess': '用户名或密码输入错误'})
return render(request, 'login.html')
现在再看看修改后的效果
错误提示信息一样,无法猜测用户存在性
响应包的长度一样,攻击者无法猜测真实用户名
后语
然而事情还没有结束,存在用户枚举的不仅是在登录界面,在另外一个出现用户名输入的位置,注册界面,也存在用户枚举的漏洞
注册界面的用户枚举略微特殊,描述的篇幅较长,放在了后一篇