JWT伪造&Pickle反序列化

文章发布时间:

最后更新时间:

文章总字数:
1.8k

预计阅读时间:
6 分钟

CISCN2019 WEB2 ikun

本篇文章将通过深入解析这道2019的国赛题目来学习两个非常重要的知识点,JWT伪造和Python的反序列化。

首先看到页面提示:一定要买到lv6,而下面出现的账号没有lv6的,点下一页发现url上出现了page=1,故使用python脚本找到有lv6的page,对lv的图片右键查看发现图片的名字就是lv几.png。

1
2
3
4
5
6
7
8
9
10
11
import requests
import time
for i in range(1,400):
url = "http://c5ed3491-3f54-45fa-8909-1108bb026d2c.node4.buuoj.cn:81/shop?page=%d"
res = requests.get(url % i)
if res.status_code != 200:
time.sleep(0.3)
res = requests.get(url % i)
print(res.status_code,i)
if "lv6.png" in res.text:
break
image-20230708101030083

页面访问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/

image-20230708104037158

成功爆破出密钥之后就可以在jwt官方网站伪造token了,

image-20230708104428477

把原来的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
2
def __reduce__(self):
return (os.system,("ls /"))

其底层的编码方法就是利用了R指令码,要么返回一个字符串,要么返回一个元组,后者对我们而言更有用。

Reduce函数bypass

有一种过滤方式,没有进制R指令码,但是对R执行的函数有黑名单限制,典型的是2018-XCTF-HITB-WEB。

面对这种题,一种思路是找到黑名单的漏网之鱼,另一种思路是利用map来逃避黑名单限制,与命令执行的利用GET逃避限制异曲同工。

1
2
3
class Exploit(object):
def __reduce__(self):
return map,(os.system,["ls"])

map函数通常不会被黑名单禁止,成功绕过,而对函数有黑名单限制,并没有对参数进行黑名单限制,从而成功执行命令。

顺便一提,map函数的用法是,第一个参数为function,第二个参数是一个可迭代对象,列表,元组,字典都可以,以可迭代对象中的每一个成员作为参数调用function。

而关于不用reduce RCE的高级应用,目前有点难以理解,以后再来研究学习,详情可参考贴出的文章。

继续回到题目,题目中对reduce没有任何限制,直接最正常构造命令即可,

1
2
3
4
5
6
7
8
9
10
11
12
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)

注意这个urllib.quote是python2里面的,需要使用Python2解释器运行,运行后把a传入become可以得到flag。