Django基础开发与攻防测试[入门篇]


真香~


大约有2年左右的时间没再碰过django了,依稀记得上次用django + semantic ui + mongodb的博客搭建还是大一的时候…

最近包括在培训以及在一些比赛中,python框架方面的攻防需求出现的越来越多。

虽然python框架相对于Java、php等的广泛度还略低一点(现在的流行程度已经越来越高了),但是我们并不能够因此而忽视其的安全性。比如在一些赛事中就常用flask来出题等等,所以花时间学习python框架的开发与攻防对于web选手来说还是一件比较重要的事情,所以撰写这篇文章,希望可以给一些想要快速入门的同学一点帮助,同时也共同进步。

如有错误的地方,还请各位师兄与同学斧正。

基础开发流程

下载安装与启动

使用的版本是django 1.8.2 ,以前搭博客就是用1.8.2,还啃了一段时间django文档,但是现在回想当初的学习与吸收能力还是比较差,不扯废话了…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 下载django
pip install django==1.8.2 -i https://pypi.mirrors.ustc.edu.cn/simple/
# 创建文件夹并启动虚拟环境
virtualenv django_demo
cd django_demo
source bin/activate
# 创建存放django文件的文件夹
mkdir learn_django
cd learn_django
# 创建项目
python django-admin.py startproject django_web
# 创建应用
python manage.py startapp django_app
# 编辑django_web中的settings.py文件,将django_app加入到apps中

这样就完成了最基础的搭建

运行服务看一下
python manage.py runserver,默认在8000端口

Django框架中的MVC与MTV

MVC是众所周知的模式:model(模型)、view(视图)、controller(控制器)
用户在页面输入url,转交给url控制器,然后根据url匹配相应的视图函数,viwe会去到models取数据,然后models在数据库中取得数据后返回给视图,视图把要展示的数据返回给模版,然后就输出到页面上。


Django也是一个MVC框架,但是在Django中,控制器接受用户输入的部分由框架自行处理,所以django更加关注的是 模型(model)、view(视图)、templates(模版),也就是MTV模型。

请求一个url后,匹配相应的view区,view去models(一个托管数据的层级)查找我们要的数据,然后将数据装载到templates层,然后呈献给我们。

可以说两者很像,MTV基于MVC。

静态Web开发

创建模版

在learn_django中创建templates文件夹(如果是IDE创建的django项目会自动创建),这就是我们的模版文件夹,来添加一个可视化的模版index.html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<title>Django Learning</title>
</head>
<body>
<h1>Hellow,Django!</h1>>
</body>
</html>

创建view层

vim django_app/views.py

1
2
3
4
5
6
from django.shortcuts import render

# Create your views here.
# 对于传入的请求,使用render函数进行渲染,返回index页面
def index(request):
return render(request,'index.html')

创建url层

创建url层,根据传入的url来找到我们的视图函数,从而将渲染的模版返回
vim django_web/urls.py

1
2
3
4
5
6
7
8
9
# 引入我们刚才创建的视图函数
from django.conf.urls import include, url
from django.contrib import admin
from django_app.views import index

urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
# 用正则去对url进行匹配,然后将视图函数匹配给相应的url
url(r'^index/',index),

到这一步有一部分同学会有一些小问题,那就是并不能返回模版,Windows和linux情况可能各不相同,linux需要把templates目录放在app目录下才可以找到

原因就在于settings.py中模版路径设置问题,如果templates目录是放在项目根目录,在settings中将templates路径加入就可以了


动态Web开发

前边是说静态页面,如果需要实现动态,那就不得不说与数据库存储的交互问题,需要对models进行对应的编写来取得数据。

mysql + django

安装对应的数据库接口驱动,这里大致有三种:mysqldb、pymysql、mysqlclient

在settings.py中修改DATABASES项
默认使用web根目录下的sqlite3数据库

将其修改为相应的mysql信息

创建mysql数据库,指定字符集为UTF-8

models层

程序 数据库
class类型 table表格
attr属性 field字段
object对象 record记录

创建模型的对象和数据库字段的对应关系

字段定义中的特殊属性

1
2
3
4
5
6
from django.db import models

# Create your models here.

class DjangoTest(models.Model):
text = models.CharField(max_length=20)

生成迁移文件并执行迁移
python manage.py makemigrations
python manage.py migrate


查看创建的信息

建立一些测试数据

在去创建views层之前,我们先对models层进行测试,看是否提取出了数据

可以用两种方法:
1、直接在models中填写提取数据

也可以在view层,这个无所谓

运行后可能会出现,因为在项目中单独运行python文件,需要搜索环境变量,而并没有指定,所以需要进行设置

django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS…..

pycharm解决方法

2、使用django shell

1
2
3
4
5
6
7
8
9
10
11
# 打开django shell
python manage.py shell
import django
django.setup()

from django_app.models imoort DjangoTest as dj
# 添加数据,结果见下方图片
a = dj(text="django shell test")
a.save()
# 查询数据,get查询单个数据,filter查询多个模型,all查询
dj.objects.all()

django admin可以帮我们快速管理后台数据

1
2
# 创建管理员
python manage.py createsuperuser


将我们的模型注册到admin中,打开admin.py

1
2
3
4
5
6
7
from django.contrib import admin

# Register your models here.

from models import DjangoTest

admin.site.register(DjangoTest)


这样就可以在管理员界面管理模型

view与url层

当用户请求django站点上的某个页面时,django会使用路由解析模块来解析路由,默认是app目录下的urls.py

django加载该路由解析模块,并寻找可用的urlpatterns,这是一个python列表,然后django依次匹配列表中每个url模式,在遇到第一个与请求相匹配的模式时停下来,然后调用对应的视图,视图是一个python函数(或者是一个基于类的视图)。

一个简单的路由选择模块示例:

1
2
3
4
5
6
7
8
9
10
from django.conf.urls import url

from . import views

urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

与之对应的请求的例子:

  • 对/articles/2005/03/ 的请求将匹配列表中的第三个模式。Django 将调用函数views.month_archive(request, ‘2005’, ‘03’)。
  • /articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。
  • /articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。请像这样自由插入一些特殊的情况来探测匹配的次序。
  • /articles/2003 不匹配任何一个模式,因为每个模式要求URL 以一个斜线结尾。
  • /articles/2003/03/03/ 将匹配最后一个模式。Django 将调用函数views.article_detail(request, ‘2003’, ‘03’, ‘03’)。

根据url后边的数值,赋予相应的参数:id
urls.py

1
2
3
4
5
6
7
8
9
from django.conf.urls import include, url
from django.contrib import admin
from django_app.views import index

urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^index/(?P<id>[0-9])/$', index),

]

将对于的id参数代入查询获取数据列表中指定索引的值(忽略这的一点小细节错误)
views.py

1
2
3
4
5
6
7
8
9
10
11
from django.shortcuts import render
from django_app.models import DjangoTest

# Create your views here.

def index(request,id):
text = DjangoTest.objects.all()
iden = int(id)
context = {'text':text[iden]}

return render(request,'index.html',context)

模版层

模版语法参考

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Django test</title>
</head>
<body>
<h1>ID: {{ text.id }}</h1>
<h1>Hello:{{ text.text }}
</body>
</html>

实际效果


Django开发中的Web攻防

格式化字符串漏洞

修复前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from django.shortcuts import render
from django.contrib.auth import authenticate, login
import django
django.setup()

# Create your views here.

def index(request):
if request.method == 'GET':
return render(request,'index.html')
else:
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username,password=password)
if user is not None:
if user.is_active:
login(request, user)
template = 'Hello {user}! , You can set your email is:' + request.POST.get('email')
return render(request, 'index.html', {"value": template.format(user=request.user)})
else:
info = "The password is valid, but the account has been disabled!"
return render(request, 'index.html', {"value": info})
else:
info = "The username and password were incorrect."
return render(request, 'index.html', {"value": info})

在原来的代码上变动了一下,如果不增加认证选项,返回的用户永远是匿名用户。

在前边我已经创建了一个超级用户(admin admin),所以直接用这个用户来进行认证。

认证之后返回登录用户的用户名,我们可以自己通过post方法传入一个邮箱地址上去作为临时地址,如果用户名信息出现任何错误,返回相应的错误信息。

使用django认证系统

User对象是认证系统的核心,默认user的基本属性有username、password、email…..

代码中邮箱信息直接通过format拼接在了字符串中,然后展示在页面里,我们可以通过以下payload来获取敏感数据,将user变量中的password属性 作为变量信息进行拼接,从而进行获取

payload: {user.password}>

修复后

index.html

1
2
3
4
5
6
7
8
9
10
11
12
# 将其他的文本信息直接存放在模版中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Django test</title>
</head>
<body>
<h1>Hello, Django!</h1>
<h2>hello {{ user }}, You can set your email is:{{ value }}</h2>
</body>
</html>

views.py

1
2
email = request.POST.get('email')
return render(request, 'index.html', {"value": email})

XSS

测试与修复

只是简单的接收post参数值,然后让其显示在页面上
views.py

1
2
3
4
5
6
def index(request):
if request.method == 'GET':
return render(request,'index.html')
else:
info = request.POST.get('info')
return render(request,'index.html',{"value":info})

index.html

1
2
<h1>Hello, Django!</h1>
<h2>{{ value }}</h2>


当键入payload时,并没有预想的弹窗,因为django自动为开发者提供了escape功能,让html代码在render之前先进行转义,然后再显示出来。

除了自动开启的escape,还有safe、autoescape、make_Safe

autoescape测试

1
2
3
{% autoescape off %}
<h2>{{ value }}</h2>
{% endautoescape %}

当其值为off时,即存在xss漏洞

safe测试

1
2
<h1>Hello, Django!</h1>
<h2>{{ value | safe }}</h2>

通过safe关闭了模版的安全机制,出现XSS漏洞

还有几种情况也可能存在XSS:
1、var mystr = “{ { value | escapejs } }“
2、safe、make_safe、autoescape
3、DOM型XSS
4、HttpResponse返回动态内容

修复后

1
2
3
4
5
6
7
8
9
10
import cgi
# Create your views here.

def index(request):
if request.method == 'GET':
return render(request,'index.html')
else:
info = request.POST.get('info')
info = cgi.escape(info)
return render(request,'index.html',{"value":info})

使用cgi模块需要注意:

转义尽可能多的可能导致逃逸的字符。

SQL注入

Django QuerySet

查看django queryset执行的SQL
from django_app.models import DjangoTest as dj
print dj.objects.all().query

SELECT django_app_djangotest.id, django_app_djangotest.text FROM django_app_djangotest
简化之后就是:SELECT id,text FROM django_app_djangotest;

extra实现别名、条件、排序等
以select为例:
tag = dj.objects.all().extra(select={“tag_id”:’id’})

extra里的允许当前的where参数可以使用原声的sql语句进行查询。

条件为id=1,结果即查询出了一条数据

raw方法实现原生的SQL语句查询
a = dj.objects.raw(‘SELECT id,text FROM django_app_djangotest ‘)
raw()方法支持索引访问
a[0]

也可以打印当前赋予的这个变量a都有哪些方法

直接利用API来查询数据

django.connection

MySQL API

诸如mysqldb、pymysql、mysqlclient,在views层写好sql语句,根据传入的参数值来查询,得出结果后返回给模版就可以了

修复前

views.py

1
2
3
4
5
6
7
8
9
10
11
from django.shortcuts import render
from django_app.models import DjangoTest as dj
# Create your views here.

def index(request):
if request.method == 'GET':
return render(request,'index.html')
else:
id = request.POST.get('id')
tag = dj.objects.extra(where={'id={}'.format(id)})[0].text
return render(request,'index.html',{"value":tag})

接下来就可以进行愉快的测试了

a = dj.objects.extra(where={‘id=1’})

SELECT django_app_djangotest.id, django_app_djangotest.text FROM django_app_djangotest WHERE (id=1asdasdad)

测试篇

先输入payload查看django传递回数据库的sql语句是什么

更改后的payload

后边又构造测试了几个,SQL语句是正确,但是django传入SQL语句时会提示里边的语法问题,并且就算语法正确,也返回不了数据,因为id的值,从而在页面中显示不出来,所以这时候想到了延时注入

在这里调用a的时候会延时3秒

我们在Web页面中进行测试

接下来就好办了

x = dj.objects.extra(where={“id=1 and if(substr((select user()),1,
1)=’r’,sleep(3),1)”})


后边的步骤跟着盲注的流程走就OJBK了。

有时候不要直接在django shell中执行,先去mysql命令行把命令敲对了,也确实可以执行payload时候再回来测试

刚才说了的只是extra中的where子句,其他类型的数据提取方法在前边也有说过了,具体案例具体分析

修复后

views.py

1
2
3
4
5
6
7
8
9
10
11
12
from django.shortcuts import render
from django_app.models import DjangoTest as dj
# Create your views here.

def index(request):
if request.method == 'GET':
return render(request,'index.html')
else:
id = request.POST.get('id')
tag = dj.objects.extra(where=['id=%s'],params=id)
info = tag[0].text
return render(request,'index.html',{"value":info})

用户发送的请求再匹配到视图函数进行处理时,视图函数的相关机制会对敏感信息进行处理,导致一些恶意语句被过滤

现在测试就不会了

待更新

最近这两天就到学期末了,有一部分事情处理完毕就准备去实习了,在django攻防方面的知识自己会去好好研究的,更多漏洞方面的知识拓展在花时间了解之后也会补充在这里,也就先这样,10点半了该回寝室了。下午打球来着,虽然又有一段时间没打球了,但是今天发挥的还是那么的C

参阅文章

理解django模式–MVC与MTV
django快速入门
Django1.8.2手册
Django框架开发中的Web漏洞案例解析