会话认证-Session与JWT
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 | # 示例 |
Payload:
Payload包含了JWT的主要信息,通常使用JSON对象表示并使用Base64编码,Payload中包含三个类型的字段:注册声明、公共声明和私有声明
- 公共声明(Public Claims):是自定义的字段,用于传递非敏感信息,例如:用户ID、角色等
- 私有声明(Private Claims):是自定义的字段,用于传递敏感信息,例如密码、信用卡号等
- 注册声明(Registered Claims):预定义的标准字段,包含了一些JWT的元数据信息,例如:发行者、过期时间等
1 | # 示例 |
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 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. |
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 | payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS512"]) |
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 | lifetime = datatime.datetime.now() + datatime.timedelta(minutes=5) |
- 在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 | { |
其他在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 | payload = jwt.decode(token, self.secret, algorithms="HS256") |
- 如果必须包含敏感信息,应使用加密机制对Payload进行加密
2 身份验证逻辑错误导致JWT可混用
技术要点:
- 在某些场景中,如果不同身份类型的JWT(如管理员和普通用户的Token)没有严格区分,可能导致权限提升问题。
示例:
某服务允许用户使用普通用户的JWT访问管理员接口,攻击者可以利用此漏洞提升权限:
1 | POST /admin/manage/add |
解决方案:
- 在业务逻辑中增加对Token类型、权限的校验,确保不同身份的Token不能混用
- 在JWT Payload中明确用户的角色,并在服务端验证其权限
- 如果管理员和普通用户的JWT都是对称加密类型,则使用不同的密钥签名、验证
3 跨服务器中继攻击
技术要点:
- 在多服务场景中,如果未指定audience限制Token仅能访问指定的应用程序,可能导致A应用程序的Token能在B应用程序中合法使用,可能导致权限提升。
示例:
A服务允许用户使用B服务生成的JWT访问,攻击者可以利用次漏洞扩展访问权限
1 | HOST:appa |
解决方案:
- 多服务场景中,各服务单独验证audience防止其他服务的Token访问
1 | payload = jwt.decode(token, self.secret, audience=["appB"], algorithms="HS26") |
- 如果各服务的JWT都是用对称加密类型算法,则各服务使用不同的密钥签名、验证
4 注入与越权常规风险
技术要点:
- 攻击者可能试图构造恶意的JWT,以绕过权限检查或注入恶意数据,造成越权操作。
示例:
攻击者构造如下Payload,试图绕过权限检查:
1 |
如果服务端没有严格校验Token的合法性,攻击者可能获得管理员权限
解决方案:
- 严格验证JWT的签名、算法和格式,quebaoToken未被篡改
- 在每个业务接口中,正确处理Token权限校验 ,避免越权操作;对JWT中提交的数据进行过滤处理,防止恶意数据影响业务