72

Django开发与攻防测试(入门篇)

 5 years ago
source link: http://www.freebuf.com/vuls/176505.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

依稀记得以前用django + semantic ui + mongodb搭博客还是大学一年级的时候,时光匆匆啊….

一、Django基础开发

以前搭博客用的是1.8.2,还在机子上装着没卸,顺便拿过来用,当然新版本会修复很多bug,尽可能还是要去学习新一些的版本,此篇权当入门篇。

1、下载安装与启动

# 下载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中

vUvuQnR.jpg!web 这样就完成了最基础的搭建

nuuaY3n.jpg!web

运行服务看一下

v6fU32j.jpg!web 2、Django框架中的MVC与MTV

MVC是众所周知的模式:model(模型)、view(视图)、controller(控制器)

用户在页面输入url,转交给url控制器,然后根据url匹配相应的视图函数,viwe会去到models取数据,然后models在数据库中取得数据后返回给视图,视图把要展示的数据返回给模版,然后就输出到页面上。

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

请求一个url后,匹配相应的view区,view去models(一个托管数据的层级)查找我们要的数据,然后将数据装载到templates层,然后呈献给我们。 26BZ3aB.jpg!web 两者很像, 可以说 MTV基于MVC。

3、静态Web开发

创建模版层

当然,要是只想让简单的数据显示在Web页面中,不需要创建模版,直接在views函数中相应回去就可以了,但是还是正规化一点。

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

<!DOCTYPE html>
<html>
<head>
    <title>Django Learning</title>
</head>
<body>
    <h1>Hellow,Django!</h1>>
</body>
</html>

创建视图层

视图层通常来说是一个视图函数,与url进行匹配返回传入对应的Web页面

from django.shortcuts import render
# Create your views here.
def index(request):
    return render(request,'index.html')

创建url层

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

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),

运行服务,默认在8000端口

python manage.py runserver

jyyea2I.jpg!web

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

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

RnERze7.jpg!web

IJzaqub.jpg!web

4、动态web开发

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

mysql + django

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

默认使用web根目录下的sqlite3数据库

nANvY3J.jpg!web 将其修改为相应的mysql信息

y6fAn2U.jpg!web 创建mysql数据库,指定字符集为UTF-8

Yve6ZnY.jpg!webmodels层

JjUjeey.jpg!web 创建模型的对象和数据库字段的对应关系

UV3UryA.jpg!web 字段定义中的特殊属性

iMNJveu.jpg!web

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

uYFRvuj.jpg!web

查看创建的信息 7RbUji3.jpg!web

建立一些测试数据

A3miqqr.jpg!web

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

可以用两种方法:

1、直接在models中填写提取数据

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

MfaieyQ.jpg!web 运行后可能会出现,因为在项目中单独运行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解决方法: https://blog.csdn.net/u011013781/article/details/52155761

2、使用django shell

# 打开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()

VvY3q2q.jpg!web

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

# 创建管理员
python manage.py createsuperuser
```
![](http://oxrfjovwk.bkt.clouddn.com/18-6-29/76119583.jpg)

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

ZVrqaqN.jpg!web

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

from django.contrib import admin
# Register your models here.
from models import DjangoTest
admin.site.register(DjangoTest)

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

BveyyuI.jpg!webview与url层

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

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

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

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

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

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)

模版层

N36V3eY.jpg!web

<!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>

实际运行效果

nqU7juj.jpg!web2uiYz2f.jpg!web二、Django开发中的Web攻防(部分)

1、格式化字符串漏洞

修复前

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})

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

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

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

使用django认证系统

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

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

payload:  {user.password}

6Rn6n2e.jpg!web修复后

index.html

将其他的文本信息直接存放在模版中

<!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
.....
email = request.POST.get('email')
return render(request, 'index.html', {"value": email})
...

2、XSS

测试与修复前

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

views.py

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


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

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

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

autoescape测试

    {% autoescape off %}
    <h2>{{ value }}</h2>
    {% endautoescape %}

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

MJrAZnB.jpg!websafe测试

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

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

Jbm2mqU.jpg!web 还有几种情况也可能存在XSS:

1、var mystr = “\{ \{ value | escapejs \} \}”
2、safe、make_safe、autoescape
3、DOM型XSS
4、HttpResponse返回动态内容

修复后

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})

uqiuqyj.jpg!web 使用cgi模块需要注意:

ri2qMjq.jpg!web 设为True,让其转义尽可能多的导致逃逸的字符。

3、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; 

qMfAri7.jpg!webextra实现别名、条件、排序等

以select为例:

tag = dj.objects.all().extra(select={“tag_id”:’id’})

imeIb2v.jpg!web 以where为例:

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

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

mEZzuq3.jpg!webraw方法实现原生的SQL语句查询

a = dj.objects.raw(‘SELECT id,text FROM django_app_djangotest ‘)

raw()方法支持索引访问( a[0]

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

vM7VvuV.jpg!webyYJviyy.jpg!web 直接利用API来查询数据

django.connection

qiMV3qa.jpg!webMySQL API

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

修复前

views.py

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})

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

viENRzf.jpg!web

测试篇

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语句是什么

fYBNjeZ.jpg!web 更改后的payload

ZJVvymb.jpg!webZjqa2au.jpg!web 后边又构造测试了几个,SQL语句是正确,但是django传入SQL语句时会提示里边的语法问题,并且就算语法正确,也返回不了数据。(这其实有点问题,后边做完一想,没拿到mysql shell里边去测,终端里边测对了,再拿过来,这里有点懒没再弄)

又因为id的值,从而在页面中显示不出来,所以这时候想到了延时注入

imIbUzJ.jpg!web 在这里调用a的时候会延时3秒

ue6bmuN.jpg!web 我们在Web页面中进行测试

FNvmeum.jpg!webZfi2Afm.jpg!web

关于这里的秒数是成倍关系,以前看到过一篇帖子,说是当时间满足出现成倍的关系时,应该是查询出了多条数据,每一个row执行一次延时。

接下来就好办了

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

uiUZriQ.jpg!web 后边的步骤跟着盲注的流程走就OJBK了。

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

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

修复后

views.py

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})

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

现在测试就不会了

6veuM3V.jpg!web 4、待更新

这两天要考试了,得花几个小时复习一下了。完了还有一部分事情得处理完毕,接着就去实习了。django攻防方面的知识自己也会去好好研究,更多的漏洞方面的知识扩展完了会补充在自己的博客上( www.addon.pub   嘿嘿嘿)。

同时在自己探索的同时也能和方向相同的小伙伴共同探讨学习,期间也会把自己学到的以及在其中遇到的问题分享出来,让想学习的同学可以更快速的入门。

虽然文章简单,但是还是希望可以对想要入门的同学提供一些帮助,共同成长。

最后就祝大家学业有成,工作顺利~

*本文作者:Yokeen,转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK