JWT伪造&Pickle反序列化
最后更新时间:
文章总字数:
预计阅读时间:
CISCN2019 WEB2 ikun
本篇文章将通过深入解析这道2019的国赛题目来学习两个非常重要的知识点,JWT伪造和Python的反序列化。
首先看到页面提示:一定要买到lv6,而下面出现的账号没有lv6的,点下一页发现url上出现了page=1,故使用python脚本找到有lv6的page,对lv的图片右键查看发现图片的名字就是lv几.png。
1 | import requests |
页面访问page=181,看到lv6账号,点击购买出现注册和登录界面,随便注册一个账号登录发现显示该账号只有1000元,而购买lv6即使八折也不够钱,抓包修改折扣改成0.00000001,成功购买,并返回了一个地址/b1g_m4mber
页面访问/b1g_m4mber时发现提示不是admin,无法查看页面,抓包查看admin的验证方式,发现传输了JWT,推测使用JWT来验证是否为admin,接下来的步骤就是JWT伪造成admin然后查看页面。
JWT伪造
https://blog.csdn.net/qq_45521281/article/details/106073624
了解JWT
首先我们要知道什么是JWT,JWT就是json web token,简单来说就是一种登录凭证,基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息(不像传统的session认证)。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
JWT鉴权的流程是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里
JWT构成
JWT有三段信息构成,将这三段信息的文本用.
连接在一起就成了JWT字符串。
另外JWT事实上就是经过base64加密的json,通过base64解密可以直观看到前两段的具体内容,不过还是更推荐官方网站,支持更多操作(https://jwt.io)
第一部分被称为头部header,头部承载了两部分信息:声明类型,这是JWT,并且声明加密的算法
1
2
3
4{
'typ': 'JWT',
'alg': 'HS256'
}如果alg是none,则第三段不会存在,可以伪造token随意访问
第二部分被称为载荷payload,载荷是存放有效信息的地方,放了很多声明,包括用户名,是否为管理员之类的,我们伪造token就是通过修改payload来访问管理员的账号。
第三部分被称为签证signature,检测JWT前两段的有效性,这个部分需要base64加密后的header和base64加密后的payload使用
.
连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分。也就是说我们伪造JWT的重中之重就是要破解secret,因为我们现在已经有了header和payload。
JWT伪造
jwt伪造的核心就是要获得secret,一般有两种方法获得,一种是信息泄露,就是在源码或者在信息收集的时候发现了secret密钥,另一种就是用工具爆破密钥(只能爆破简单的)。
推荐工具 c-jwt-cracker
https://sxz-oi.github.io/2022/06/07/c-jwt-cracker/
成功爆破出密钥之后就可以在jwt官方网站伪造token了,
把原来的username改为admin,这时再用这个token修改请求头里的jwt就可以登录管理员的账号。
继续解题,登录之后提示源码文件位置,获得源码之后发现是python代码审计,查看与当前页面最有关联的python文件,Admin.py,发现了关键模块pickle且调用了pickle.loads且loads的参数是用户传入的可控变量become,判断为Python反序列化。
Python反序列化
从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势
常规利用Reduce
pickle用来序列化和反序列化,pickle.dumps()可以将对象序列化字符串,而pickle.loads()可以将字符串序列化成对象,pickle不仅可以读写字符串,也可以读写文件:只需要采用pickle.dump()
和pickle.load()
。
简单利用反序列化漏洞是通过class的__reduce__
方法,这个方法在pickle反序列化的时候会被自动执行,reduce干了这样一件事情:
- 取当前栈的栈顶记为
args
,然后把它弹掉。 - 取当前栈的栈顶记为
f
,然后把它弹掉。 - 以
args
为参数,执行函数f
,把结果压进当前栈。
简单说来就是reduce把返回的元组的第一个元素为函数,第二个元素为参数来函数执行。一种非常流行的攻击方式就是返回一个恶意元组。
1 | def __reduce__(self): |
其底层的编码方法就是利用了R指令码,要么返回一个字符串,要么返回一个元组,后者对我们而言更有用。
Reduce函数bypass
有一种过滤方式,没有进制R指令码,但是对R执行的函数有黑名单限制,典型的是2018-XCTF-HITB-WEB。
面对这种题,一种思路是找到黑名单的漏网之鱼,另一种思路是利用map来逃避黑名单限制,与命令执行的利用GET逃避限制异曲同工。
1 | class Exploit(object): |
map函数通常不会被黑名单禁止,成功绕过,而对函数有黑名单限制,并没有对参数进行黑名单限制,从而成功执行命令。
顺便一提,map函数的用法是,第一个参数为function,第二个参数是一个可迭代对象,列表,元组,字典都可以,以可迭代对象中的每一个成员作为参数调用function。
而关于不用reduce RCE的高级应用,目前有点难以理解,以后再来研究学习,详情可参考贴出的文章。
继续回到题目,题目中对reduce没有任何限制,直接最正常构造命令即可,
1 | import pickle |
注意这个urllib.quote是python2里面的,需要使用Python2解释器运行,运行后把a传入become可以得到flag。