第一次做pickle反序列化
1.打开题目是这个页面
信息收集一下
目录扫描无可用信息、翻看源码,发现提示
感觉这个lv6就是提示,因为首页面下方对应的就是等级
寻找一下lv6
这里发现他的页数是可控的
然后lv等级数也是有规则的
那么只需要写个脚本,遍历他的页数,然后再搜索页面内容带有lv6.png字段的网页不就行了
脚本如下:
在181页面找到了lv6.png
查看一下
逻辑漏洞利用
打开到了一个结算页面,里面有优惠券,但是还是很贵买不起,抓包看看能不能进行一个修改
发现他的价钱还是1145……,然后后面打了一个8折,但是上面页面上出现的是91611……,那么他的优惠价是原价*0.8,那么这里将0.8变成0.0000001,那么价钱是不是就低了,实践一下
发现有一个路径/b1g_m4mber,点击跟随重定向看看到哪里了
JWT密钥爆破和伪造
这个页面两点注意,一是右边的只允许admin访问,左边是一个jwt编码,那么这个admin应该是通过jwt伪造的
解码发现里面有一个username字段,但是后面的 名字是我刚才注册的,后面还有一段加密的数据,这里使用c-jwt-cracher爆破一下密钥
密钥为1Kun,然后再伪造username为admin
进行一个替换,放回包到浏览器看看
这里有一个一键成为大会员,点击没啥反应,再看一下源码
2.分析代码
这里有一个www.zip,下载下来看看
在Admin.py下发现了是pickle反序列化
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)首先是导入一个Tornado web框架的主要模块,tornado.web,用来构建web应用
BaseHandler:这个类从sshop.base模块导入,通常用于定义处理HTTP请求的基本类
pickle:python的标准库,用于序列化和反序列化对象
urllib:python2的用于URL操作,unquote方法可以解码url编码的字符串
AdminHandler类:
这个类继承自BaseHandler,处理管理后台相关的请求。他有两个方法GET和POST请求
get方法:
这个方法会在用于通过浏览器请求一个页面时被调用。
@tornado.web.authenticated:这个装饰器表示用于必须已经通过身份认证才能访问此方法
self.current_user=="admin":表示检查当前用于是否是admin,如果是,则渲染form.html页面,并传递res='This is Black Technology!'和member=0给页面。如果不是,则渲染no_ass.html页面,表示没权限访问
post方法:
这个方法会在用户提交表单时被调用,通常用于处理表单数据。
self.get_argument('become'):获取表单种名为become的字段值。
然后首先使用urllib.unquote对become字段进行URL解码,然后使用pickle.loads将其反序列化为python对象。
如果反序列化成功,则渲染form.html页面,并将反序列化后的对象p和member=1作为参数传递给页面
简述一下就是
首先身份认证也就是JWT中的username的值为admin,那么就能传入become参数,这个参数可以用于传递恶意的序列化值,这个序列化值也没什么过滤,他会对序列化字段进行一个url解码,然后就将他进行进行反序列化操作
3.开始构造
这里也是构造链子,首先确认他的python环境是需要使用python2,其次是他没进行任何过滤,可以使用任意函数和模块,然后就是他会使用unquote进行一个解码,然后再反序列化,那么只需要将命令进行一个url编码,然后再序列化,从become传入,就能成功执行成功了
这个时候可以使用python魔术方法中的__reduce__,这是一个特殊的魔术方法,当使用pickle序列化对象时,__reduce__返回一个元组,告诉pickle如何恢复对象,那么让他返回的元组的值为我么们构造的命令执行语句,那么返回他就会让命令成功执行
可以这样写
import pickle import urllib class AdminHandler(object): def __reduce__(self): return (eval,("__import__('os').popen('cat /flag.txt').read()",)) b=pickle.dumps(AdminHandler()) b=urllib.quote(b) print(b)首先是导入pickle和urllib,让AdminHandler继承一个object基类,然后定义一个__reduce__方法,然后里面的参数是当前对象,返回值为(eval,("__import__('os').popen('cat /flag.txt').read()",)),然后在进行一个实例化类,再进行一个反序列化,随后再让他进行一个url编码,最后打印出来
所以这个脚本的目的就是
在反序列化的时候会触发那个__reduce__方法,然后调用eval,__import__导入os模块,在调用系统命令popen,执行cat /flag.txt,并使用read进行读取文件内容
然后使用python2运行脚本
随后传入上去,得到flag
4.知识点
这题考验了
信息收集能力,JWT爆破密钥并进行伪造,pickle反序列化
首先是JWT爆破密钥,使用的是c-jwt-cracher工具进行一个爆破
关键还是pickle反序列化
这是python的一个反序列化,是将类对象向字节流转化从而进行存储和传输,然后使用的时候再将字节流转化为原始的对象的一个过程。
python中序列化的一般有两种方式,pickle模块和json模块,前者是python特有的格式,后者是json通用的格式。
然后是python序列化和反序列化相关函数
pickle.dump(obj,file):将对象序列化后保存到文件
pickle.load(file):读取文件,将文件中的序列化内容反序列化为对象
pickle.dumps(obj):将对象序列化成字符串格式的字节流
pickle.loads(bytes_obj):将字符串格式的字节流反序列化为对象
魔术方法
__reduce__():反序列化时调用
必须返回一个元组,元素的第一个元素是一个可调用对象,第二个元素是要传递给该可调用对象的参数
__reduce_ex__():反序列化时调用
和__reduce__相似,返回一个可调用对象和构造该对象所需的参数
__getstate__():反序列化时调用
返回一个字典,该字典表示对象的状态。通过修改这个字典,可以控制对象被序列化的内容。
__setstate__():反序列化时调用
将反序列化的状态数据设置回对象的属性,他接收一个参数,这个参数是对象的状态数据(通常是字典形式),可以通过它恢复对象的状态
只列举了它的使用方法,具体利用原理因为牵扯到栈还有opcode指令集和PVM,还需要再进行了解
参考文章:
https://blog.csdn.net/weixin_62808713/article/details/130048382
https://xz.aliyun.com/news/13498
https://www.cnblogs.com/DSchenzi/p/19271665