JWT简介

JWT(JSON Web Token,JSON Web 令牌)

  • JWT是一个开放标准,它定义了一种紧凑的、自包含的方式,用于各方之间以JSON对象安全地传输信息。此信息可以验证和新人,因为它是数字签名的。JWT可以使用秘密(使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
  • 通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据的加密、签名等相关工作。

作用

  • 授权:一旦用户登录,后续的每个请求都将包含JWT,从而允许用户访问该令牌允许的路由、服务和资源。JWT的开销很小并且可以在不同域中使用。

  • 信息交换:在各方之间安全地传输信息。JWT可以进行签名(如使用公/私钥对),因此可以确保发件人。并且签名是使用标头和有效负载计算的,因此还可以验证内容是否被篡改

  • 统中的每一次HTTP请求都会把JWT携带在Header里面,这也可能增加请求的开销。

Session认证方式

特点

  • Session认证是一种传统的鉴权机制,通过在客户端和服务器之间建立会话来验证用户身份。
  • 客户端发送用户名和密码到服务器进行登录验证。服务器对用户提交的凭证进行验证,通常通过与存储在数据库中的用户信息进行比较。
  • 如果用户凭证有效,服务器会为该用户创建一个唯一的Session ID,并在服务器端会话中存储该ID。
  • 服务器将Session ID发送给客户端,通常通过Set-Cookie HTTP标头实现。客户端收到Session ID后,将其存储在Cookie中,并在后续请求中将该Cookie发送回服务器。
  • 服务器通过验证Cookie中的Session ID来验证客户端的身份。

优缺点

  • 优点:
    • 简单易用:Session认证基于Cookie实现,无需在每个请求中都传递用户凭证。
    • 会话管理简单:适用于短时间交互或页面刷新较少的场景。
  • 缺点:
    • 安全风险:Session ID在客户端存储,存在被盗用的风险。
    • 服务器压力:随着用户数量的增加,服务器需要维护大量的会话数据,可能面临性能压力。
    • 可扩展性差:在分布式部署环境下,Session需要将数据存储在数据库或Redis等共享存储中,以实现多机数据共享。

JWT认证方式

特点

  • JWT是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息。
  • 客户端发送用户名和密码到服务器进行登录验证。服务器对用户提交的凭证进行验证,并生成一个唯一的Token(即JWT)。
  • 服务器将Token发送给客户端,客户端将其存储在本地(如Local Storage或Cookies)。
  • 在后续请求中,客户端将Token发送回服务器进行身份验证。服务器验证收到的Token是否有效。如果有效,则认为客户端已通过身份验证。

优缺点

  • 优点:
    • 高安全性:Token基于令牌生成和验证机制,可以提供更高级别的安全性。
    • 减轻服务器压力:Token不需要存储在服务器内存或数据库中,减少了服务器的存储和计算负担。
    • 跨域支持:Token可以与第三方服务集成,支持跨域请求的身份验证。
    • 无状态:JWT不在服务器端存储任何状态,符合RESTful API的无状态原则。
    • 可扩展性好:在分布式部署环境下,JWT不需要进行多机数据共享。
  • 缺点:
    • 实现复杂度较高:与Session认证相比,Token认证需要更多的逻辑处理和安全措施。
    • 长生命周期:Token具有较长的生命周期,可能不适合短时间交互的场景。
    • 性能问题:由于JWT包含所有必要的信息,如果JWT过长,可能会增加HTTP请求的大小,影响性能。同时,用户在系统中的每一次HTTP请求都会把JWT携带在Header里面,这也可能增加请求的开销。

基本结构

JWT(JSON Web Token)的结构由三部分组成,分别是Header、Payload和Signature

Header:

Header包含了JWT使用的算法和类型等元数据信息,通常使用JSON对象表示并使用Base64编码,Header中包含两个字段:alg和typ

alg(algorithm):指定了使用的加密算法,常见的有HMAC、RSA和ECDSA等算法

typ(type):指定了JWT的类型,通常为JWT

1
2
3
4
5
# 示例
{
"alg": "HS256",
"typ": "JWT"
}

Payload:

Payload包含了JWT的主要信息,通常使用JSON对象表示并使用Base64编码,Payload中包含三个类型的字段:注册声明、公共声明和私有声明

  • 公共声明(Public Claims):是自定义的字段,用于传递非敏感信息,例如:用户ID、角色等
  • 私有声明(Private Claims):是自定义的字段,用于传递敏感信息,例如密码、信用卡号等
  • 注册声明(Registered Claims):预定义的标准字段,包含了一些JWT的元数据信息,例如:发行者、过期时间等
1
2
3
4
5
6
# 示例
{
"sub": "1234567890", // 主题
"name": "John Doe", // 名称
"iat": 1516239022 // JWT的签发时间
}

Signature:

Signature是使用指定算法对Header和Payload进行签名生成的,用于验证JWT的完整性和真实性,Signature的生成方式通常是将Header和Payload连接起来然后使用指定算法对其进行签名,最终将签名结果与Header和Payload一起组成JWT,Signature的生成和验证需要使用相同的密钥

1
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

其中HMACSHA256是使用HMAC SHA256算法进行签名,header和payload是经过Base64编码的Header和Payload,secret是用于签名和验证的密钥,最终将Header、Payload和Signature连接起来用句点(.)分隔就形成了一个完整的JWT,下面是一个示例JWT,其中第一部分是Header,第二部分是Payload,第三部分是Signature,注意JWT 中的每一部分都是经过Base64编码的,但并不是加密的,因此JWT中的信息是可以被解密的

1
2
3
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

0x02 JWT自身攻击面

1 算法混淆攻击(Algorithm Confusion Attack)/“None”算法攻击

技术要点

  • 攻击者通过篡改JWT的Header部分,将签名算法从安全算法(如HS256)更改为不安全的算法(如none),从而伪造合法Token
  • 攻击者通过修改JWT的算法字段(如从RSA改为HMAC),可以利用服务器端验证机制的弱点来篡改令牌。例如,当服务端没有正确验证算法时,攻击者可能使用公钥重新签名令牌并将其发送回来,导致 验证成功,即使令牌已经被篡改

示例:

原始Header:

1
{"alg": "HS256", "typ": "JWT"}

攻击后被篡改为:

1
{"alg": "none","typ": "JWT"}

此时JWT将不再进行签名验证,攻击者可以伪造任意Payload,例如:

1
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VySWQiOiIxMjMifQ.

解决方案:

  • 在服务端严格校验alg字段,不允许使用none等不安全算法
  • 在生成和解析JWT时,明确指定并验证安全算法,如HS256或HS512。
1
2
3
payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS512"])
userId = payload["userId"]
username = self.db_lookup(userId, "username")

2 弱密钥导致Token可伪造

技术要点:

  • 如果服务端使用弱密钥或者密钥管理不当,攻击者可以通过暴力破解或暴露的密钥生成伪造的JWT,绕过验证。
  • 在对称加密(如HMAC)中,JWT的签名强度取决于密钥的复杂行。若使用弱密钥,攻击者可以通过暴力破解的方式获取密钥,生成伪造的JWT。

如果密钥过于简单,攻击者可以使用工具如 jwtcrack 破解签名,示例:

1
jwtcrack your_jwt_token

破解后,攻击者可以使用这个密钥生成伪造的JWT。

解决方案:

  • 使用强加密算法,如RS256(非对称加密),避免使用对称加密算法HS256.
1
payload = jwt.decode(token, self.secret, algorithms="RS256")
  • 使用高强度的密钥,并妥善保管,避免泄露

3 Token重放攻击

技术要点:

  • 攻击者可以拦截合法用户的JWT并在其有效期内重复使用,造成重放攻击
  • 在验证Token签名之前,会计算Token的有效期,以确保Token尚未过期。通常是通过从Token中读取exp(过期时间)声明并计算是否仍然有效来执行的。如果exp值设置得太大,或者根本没有设置,Token的有效期就会太长,甚至可能永远不会过期。

假设攻击者捕获了一个合法的JWT,在其有效期内不断重放该请求以执行未授权操作,示例:

1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

解决方案:

  • 设置较短的exp过期时间来限制Token的有效期
1
2
3
4
5
6
7
lifetime = datatime.datetime.now() + datatime.timedelta(minutes=5)
payload = {
'username': username,
'admin': 0,
'exp': lifetime
}
access_token = jwt.encode(payload, self.secret, algorithm="HS25")
  • 在Token生成过程中使用唯一标识符(如nonce)防止重放攻击;
  • 在服务端维护Token使用历史,拒绝同一个Token被多次使用;

4 Token泄露与被窃取风险

技术要点:

  • 如果JWT在不安全的传输渠道中传递,如使用HTTP而非HTTPS,攻击者可以通过中间人攻击拦截并截获JWT

解决方案:

  • 始终使用HTTPS传输JWT,避免中间人攻击
  • 避免在URL中传递JWT,因为URL可能被日志记录

5 JWT Header参数注入伪造自签名

JSON Web Signature(JWS)RFC中定义的Header参数,最基本的JWT header是以下JSON

1
2
3
4
{
"typ": "JWT",
"alg": "HS256"
}

其他在RFC中注册的Header参数包括:jwk、jku、kid等:

  • jwk(JSON Web Key):提供一个代表密钥的嵌入式JSON对象
  • jku(JSON Web Key Set URL):提供一个URL,服务器可以从这个URL获取一组包含正确密钥的密钥
  • kid(Key ID):提供一个ID,在有多个密钥可供选择的情况下服务器可以用它来识别正确的密钥,根据键的格式这可能有一个匹配的kid参数

这些用户可控制的Header参数每个都会告诉服务端在验证签名时应该使用哪个密钥,如果服务端配置存在缺陷 ,通过这些参数的注入可伪造合法的自签名JWT

0x03 JWT在业务场景的攻击面

1 敏感信息泄露

技术要点:

  • JWT的Payload是Base64编码,而非加密,因此内容可以被轻易解析。如果在Payload中包含敏感信息(如用户的身份、邮箱等),可能会造成信息泄露。

示例:

1
{"SSN": "123-45-6789","email": "user@example.com","role": "admin"}

攻击者可以轻易解码Base64部分,得到这些敏感数据

解决方案:

  • 避免在JWT的Payload中存储敏感信息,使用标识符(如userId),而非明文信息。
1
2
3
payload = jwt.decode(token, self.secret, algorithms="HS256")
userId = payload['userId']
password = self.db_lookup(userId, "password")
  • 如果必须包含敏感信息,应使用加密机制对Payload进行加密

2 身份验证逻辑错误导致JWT可混用

技术要点:

  • 在某些场景中,如果不同身份类型的JWT(如管理员和普通用户的Token)没有严格区分,可能导致权限提升问题。

示例:

某服务允许用户使用普通用户的JWT访问管理员接口,攻击者可以利用此漏洞提升权限:

1
2
POST /admin/manage/add HTTP/2
Authorization: Bearer user_token_with_low_permissions

解决方案:

  • 在业务逻辑中增加对Token类型、权限的校验,确保不同身份的Token不能混用
  • 在JWT Payload中明确用户的角色,并在服务端验证其权限
  • 如果管理员和普通用户的JWT都是对称加密类型,则使用不同的密钥签名、验证

3 跨服务器中继攻击

技术要点:

  • 在多服务场景中,如果未指定audience限制Token仅能访问指定的应用程序,可能导致A应用程序的Token能在B应用程序中合法使用,可能导致权限提升。

示例:

A服务允许用户使用B服务生成的JWT访问,攻击者可以利用次漏洞扩展访问权限

1
2
HOST:appa
Authorization: Bearer user_token_made_by_appB

解决方案:

  • 多服务场景中,各服务单独验证audience防止其他服务的Token访问
1
payload = jwt.decode(token, self.secret, audience=["appB"], algorithms="HS26")
  • 如果各服务的JWT都是用对称加密类型算法,则各服务使用不同的密钥签名、验证

4 注入与越权常规风险

技术要点:

  • 攻击者可能试图构造恶意的JWT,以绕过权限检查或注入恶意数据,造成越权操作。

示例:

攻击者构造如下Payload,试图绕过权限检查:

1

如果服务端没有严格校验Token的合法性,攻击者可能获得管理员权限

解决方案:

  • 严格验证JWT的签名、算法和格式,quebaoToken未被篡改
  • 在每个业务接口中,正确处理Token权限校验 ,避免越权操作;对JWT中提交的数据进行过滤处理,防止恶意数据影响业务