|
| 1 | +--- |
| 2 | +title: 货拉拉面试官:JWT 被抓取了怎么办? |
| 3 | +shortTitle: JWT 攻击面试小结 |
| 4 | +description: JWT简介、JWT自身攻击面、JWT在业务场景的攻击面、JWT测试、相关工具 |
| 5 | +author: 雨九九 |
| 6 | +category: |
| 7 | + - 微信公众号 |
| 8 | +--- |
| 9 | + |
| 10 | +星球一位球友问:面试的时候遇到一个问题,如果有人通过抓包获取jwt,应该如何应对? |
| 11 | + |
| 12 | +本来想亲自写一篇,但刚好在货拉拉技术公众号上看到了这篇帖子,写得竟然很详细,于是就直接收录了进来,简单进行了一些细节调整,大家可以好好看一下,干货啊 |
| 13 | + |
| 14 | + |
| 15 | + |
| 16 | +## 一、JWT 简介 |
| 17 | + |
| 18 | +**_JSON Web Token (JWT)_** 是一种紧凑的、基于 JSON 的开放标准 (RFC 7519),常用于不同主体(客户端和服务器)之间安全地传递信息。JWT 通常由三部分组成: |
| 19 | + |
| 20 | +1. **Header(头部):** 定义令牌类型和加密算法,如`{"alg": "HS256", "typ": "JWT"}` |
| 21 | +2. **Payload(载荷):** 包含声明信息(claims),如用户身份、权限等。 |
| 22 | +3. **Signature(签名):** 用来验证令牌的真实性和完整性。 |
| 23 | + |
| 24 | +一个示例的 JWT: |
| 25 | + |
| 26 | +``` |
| 27 | +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. |
| 28 | +eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. |
| 29 | +SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
| 30 | +``` |
| 31 | + |
| 32 | +JWT 被广泛用于身份认证,授权场景。服务端验证 JWT 时,可以通过解密签名来判断消息是否被篡改。 |
| 33 | + |
| 34 | +## 二、JWT 自身攻击面 |
| 35 | + |
| 36 | +### 2.1 算法混淆攻击(Algorithm Confusion Attack)/"None"算法攻击 |
| 37 | + |
| 38 | +**技术要点:** |
| 39 | + |
| 40 | +- 攻击者通过篡改 JWT 的 Header 部分,将签名算法从安全算法(如 HS256)更改为不安全的算法(如 none),从而伪造合法 Token。 |
| 41 | +- 攻击者通过修改 JWT 的算法字段(如从 RSA 改为 HMAC),可以利用服务端验证机制的弱点来篡改令牌。例如,当服务端没有正确验证算法时,攻击者可能使用公钥重新签名令牌并将其发送回来,导致验证成功,即使令牌已经被篡改 |
| 42 | + |
| 43 | +**示例:** |
| 44 | + |
| 45 | +原始 Header: |
| 46 | + |
| 47 | +``` |
| 48 | +{"alg": "HS256","typ": "JWT"} |
| 49 | +``` |
| 50 | + |
| 51 | +攻击后被篡改为: |
| 52 | + |
| 53 | +``` |
| 54 | +{"alg": "none","typ": "JWT"} |
| 55 | +``` |
| 56 | + |
| 57 | +此时 JWT 将不再进行签名验证,攻击者可以伪造任意 Payload。例如: |
| 58 | + |
| 59 | +``` |
| 60 | +eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VySWQiOiIxMjMifQ. |
| 61 | +``` |
| 62 | + |
| 63 | +**解决方案:** |
| 64 | + |
| 65 | +- 在服务端严格校验 alg 字段,不允许使用 none 等不安全的算法。 |
| 66 | +- 在生成和解析 JWT 时,明确指定并验证安全算法,如 HS256 或 HS512。 |
| 67 | + |
| 68 | +``` |
| 69 | +payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS512"]) |
| 70 | +userId = payload['userId'] |
| 71 | +username = self.db_lookup(userId, "username") |
| 72 | +``` |
| 73 | + |
| 74 | +### 2.2 弱密钥导致 Token 可伪造 |
| 75 | + |
| 76 | +**技术要点:** |
| 77 | + |
| 78 | +- 如果服务端使用弱密钥或者密钥管理不当,攻击者可以通过暴力破解或暴露的密钥生成伪造的 JWT,绕过验证。 |
| 79 | +- 在对称加密(如 HMAC)中,JWT 的签名强度取决于密钥的复杂性。若使用弱密钥,攻击者可以通过暴力破解的方式获取密钥,生成伪造的 JWT。 |
| 80 | + |
| 81 | +**示例:** 如果密钥过于简单,攻击者可以使用工具如 jwtcrack 破解签名: |
| 82 | + |
| 83 | +``` |
| 84 | +jwtcrack your_jwt_token |
| 85 | +``` |
| 86 | + |
| 87 | +破解后,攻击者可以使用这个密钥生成伪造的 JWT。 |
| 88 | + |
| 89 | +**解决方案:** |
| 90 | + |
| 91 | +- 使用强加密算法,如 RS256(非对称加密),避免使用对称加密算法 HS256。 |
| 92 | + |
| 93 | +``` |
| 94 | +payload = jwt.decode(token, self.secret, algorithms="RS256") |
| 95 | +``` |
| 96 | + |
| 97 | +- 使用高强度的密钥,并妥善保管,避免泄露。 |
| 98 | + |
| 99 | +### 2.3 Token 重放攻击 |
| 100 | + |
| 101 | +**技术要点:** |
| 102 | + |
| 103 | +- 攻击者可以拦截合法用户的 JWT 并在其有效期内重复使用,造成重放攻击。 |
| 104 | +- 在验证 Token 签名之前,会计算 Token 的有效期,以确保 Token 尚未过期。通常是通过从 Token 中读取 exp (过期时间)声明并计算是否仍然有效来执行的。如果 exp 值设置得太大(或根本没有设置),Token 的有效时间就会太长,甚至可能永远不会过期。 |
| 105 | + |
| 106 | +**示例:** 假设攻击者捕获了一个合法的 JWT,在其有效期内不断重放该请求以执行未授权操作。 |
| 107 | + |
| 108 | +``` |
| 109 | +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
| 110 | +``` |
| 111 | + |
| 112 | +**解决方案:** |
| 113 | + |
| 114 | +- 设置较短的 exp 过期时间来限制 Token 的有效期。 |
| 115 | + |
| 116 | +``` |
| 117 | +lifetime = datetime.datetime.now() + datetime.timedelta(minutes=5) |
| 118 | +payload = { |
| 119 | + 'username' : username, |
| 120 | + 'admin' : 0, |
| 121 | + 'exp' : lifetime |
| 122 | +} |
| 123 | +access_token = jwt.encode(payload, self.secret, algorithm="HS256") |
| 124 | +``` |
| 125 | + |
| 126 | +- 在 Token 生成过程中使用唯一标识符(如 nonce)防止重放攻击。 |
| 127 | +- 在服务端维护 Token 使用历史,拒绝同一个 Token 被多次使用。 |
| 128 | + |
| 129 | +### 2.4 Token 泄露与窃取风险 |
| 130 | + |
| 131 | +**技术要点:** |
| 132 | + |
| 133 | +如果 JWT 在不安全的传输渠道中传递,如使用 HTTP 而非 HTTPS,攻击者可以通过中间人攻击拦截并获取 JWT。 |
| 134 | + |
| 135 | +**解决方案:** |
| 136 | + |
| 137 | +- 始终使用 HTTPS 传输 JWT,避免中间人攻击。 |
| 138 | +- 避免在 URL 中传递 JWT,因为 URL 可能被日志记录。 |
| 139 | + |
| 140 | +### 2.5 JWT Header 参数注入伪造自签名 |
| 141 | + |
| 142 | +JSON Web Signature (JWS) RFC 中定义的 Header 参数,最基本的 JWT header 是以下 JSON。 |
| 143 | + |
| 144 | +``` |
| 145 | +{ |
| 146 | + "typ": "JWT", |
| 147 | + "alg": "HS256" |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +其他在 RFC 中注册的 Header 参数包括:jwk、jku、kid 等: |
| 152 | + |
| 153 | +- `jwk(JSON Web Key)`:提供一个代表密钥的嵌入式 JSON 对象 |
| 154 | +- `jku(JSON Web Key Set URL)`:提供一个 URL,服务器可以从这个 URL 获取一组包含正确密钥的密钥 |
| 155 | +- `kid(Key ID)`:提供一个 ID,在有多个密钥可供选择的情况下服务器可以用它来识别正确的密钥,根据键的格式这可能有一个匹配的 kid 参数 |
| 156 | + |
| 157 | +这些用户可控制的 Header 参数每个都告诉服务端在验证签名时应该使用哪个密钥,如果服务端配置存在缺陷,通过这些参数的注入可伪造合法的自签名 JWT。 |
| 158 | + |
| 159 | +通过 jwk 参数注入自签名 JWT: |
| 160 | + |
| 161 | +**示例:** |
| 162 | + |
| 163 | +``` |
| 164 | +{ |
| 165 | + "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG", |
| 166 | + "typ": "JWT", |
| 167 | + "alg": "RS256", |
| 168 | + "jwk": { |
| 169 | + "kty": "RSA", |
| 170 | + "e": "AQAB", |
| 171 | + "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG", |
| 172 | + "n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m" |
| 173 | + } |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +**技术要点:** |
| 178 | + |
| 179 | +理想情况下,服务端应仅使用有限的公钥白名单来验证 JWT 签名。但是,配置错误的服务端有时会使用 jwk 参数中嵌入的任何密钥。使用自己的 RSA 私钥对修改后的 JWT 进行签名,然后在 jwk 中嵌入匹配的公钥,服务端会使用 jwk 中嵌入的公钥验证 JWT 签名,导致伪造的自签名 JWT 通过验证。 |
| 180 | + |
| 181 | +**解决方案:** |
| 182 | + |
| 183 | +服务端正确配置公钥白名单验证 JWT 签名 |
| 184 | + |
| 185 | +>通过 jku 参数注入自签名 JWT |
| 186 | +
|
| 187 | +**示例:** |
| 188 | + |
| 189 | +``` |
| 190 | +{"typ":"JWT","alg":"RS256", "jku":"https://hacker.com/jwks.json", "kid":"id_of_jwks"}. |
| 191 | +{"login":"admin"}. |
| 192 | +[Signed with new Private key; Public key exported] |
| 193 | +``` |
| 194 | + |
| 195 | +**技术要点:** |
| 196 | + |
| 197 | +与 jwk 不同的是,jku 参数是指向 jwk 集合文件的 URL。攻击者将 jku URL 替换为包含恶意公钥的 URL,再用配对的私钥对伪造的 Token 签名,服务端获取恶意公钥并验证伪造的 Token 为合法。 |
| 198 | + |
| 199 | +**解决方案:** |
| 200 | + |
| 201 | +服务端使用白名单验证 jku 参数值,仅允许指定来源的 URL |
| 202 | + |
| 203 | +>通过 kid 参数注入自签名 JWT |
| 204 | +
|
| 205 | +**示例:** |
| 206 | + |
| 207 | +``` |
| 208 | +{ |
| 209 | + "kid": "../../path/to/file", |
| 210 | + "typ": "JWT", |
| 211 | + "alg": "HS256", |
| 212 | + "k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc" |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +**技术要点:** |
| 217 | + |
| 218 | +服务端可能使用多个密钥对不同种类的数据签名,因此 JWT 的 Header 参数可能包含 kid(Key ID)参数,让服务端在验证签名时确定使用哪个密钥,验证的密钥通常存储为一个 JWK 集合,在这种情况下服务端可能简单地查找与 Token 具有相同 kid 的 JWK,然而 JWS 规范没有为 kid 定义具体的结构——它只是开发人员选择的任意字符串,例如:它们可能使用 kid 参数指向数据库中的特定条目,甚至是文件的名称,如果这个参数也存在目录遍历漏洞,则攻击者可能会强制服务端使用其文件系统中的任意文件作为验证密钥,例如 ../../../../../../../dev/null,由于这是一个空文件,读取它会返回一个空字符串。因此,使用空字符串对 Token 进行签名将得到能通过服务端验证的合法签名。 |
| 219 | + |
| 220 | +**解决方案:** |
| 221 | + |
| 222 | +服务端使用白名单验证 kid 参数值。 |
| 223 | + |
| 224 | +## 三、JWT 在业务场景的攻击面 |
| 225 | + |
| 226 | +### 3.1 敏感信息泄露 |
| 227 | + |
| 228 | +**技术要点:** |
| 229 | + |
| 230 | +JWT 的 Payload 是 Base64 编码,而非加密,因此内容可以被轻易解析。如果在 Payload 中包含敏感信息(如用户的身份、邮箱等),可能会造成信息泄露。 |
| 231 | + |
| 232 | +**示例:** |
| 233 | + |
| 234 | +``` |
| 235 | +{"SSN": "123-45-6789","email": "[email protected]","role": "admin"} |
| 236 | +``` |
| 237 | + |
| 238 | +攻击者可以轻易解码 Base64 部分,得到这些敏感数据。 |
| 239 | + |
| 240 | +**解决方案:** |
| 241 | + |
| 242 | +- 避免在 JWT 的 Payload 中存储敏感信息,使用标识符(如 userId),而非明文信息。 |
| 243 | + |
| 244 | +``` |
| 245 | +payload = jwt.decode(token, self.secret, algorithms="HS256") |
| 246 | +userId = payload['userId'] |
| 247 | +password = self.db_lookup(userId, "password") |
| 248 | +``` |
| 249 | + |
| 250 | +- 如果必须包含敏感信息,应使用加密机制对 Payload 进行加密。 |
| 251 | + |
| 252 | +### 3.2 身份验证逻辑错误导致 JWT 可混用 |
| 253 | + |
| 254 | +**技术要点:** |
| 255 | + |
| 256 | +在某些场景中,如果不同身份类型的 JWT(如管理员和普通用户的 Token)没有严格区分,可能导致权限提升等问题。 |
| 257 | + |
| 258 | +**示例:** 某服务允许用户使用普通用户的 JWT 访问管理员接口,攻击者可以利用此漏洞提升权限: |
| 259 | + |
| 260 | +``` |
| 261 | +POST /admin/manage/add HTTP/2 |
| 262 | +Authorization: Bearer user_token_with_low_permissions |
| 263 | +``` |
| 264 | + |
| 265 | +**解决方案:** |
| 266 | + |
| 267 | +- 在业务逻辑中增加对 Token 类型、权限的校验,确保不同身份的 Token 不能混用。 |
| 268 | +- 在 JWT Payload 中明确用户的角色,并在服务端验证其权限。 |
| 269 | +- 如果管理员和普通用户的 JWT 都是对称加密类型,则使用不同的密钥签名、验证。 |
| 270 | + |
| 271 | +### 3.3 跨服务中继攻击 |
| 272 | + |
| 273 | +**技术要点:** |
| 274 | + |
| 275 | +在多服务场景中,如果未指定 audience 限制 Token 仅能访问指定的应用程序,可能导致 A 应用程序的 Token 能在 B 应用程序中合法使用,可能导致权限提升。 |
| 276 | + |
| 277 | +**示例:** A 服务允许用户使用 B 服务生成的 JWT 访问,攻击者可以利用此漏洞扩展访问权限: |
| 278 | + |
| 279 | +``` |
| 280 | +Host:appA |
| 281 | +Authorization: Bearer user_token_made_by_appB |
| 282 | +``` |
| 283 | + |
| 284 | +**解决方案:** |
| 285 | + |
| 286 | +- 多服务场景中,各服务单独验证 audience 防止其他服务的 Token 访问 |
| 287 | + |
| 288 | +``` |
| 289 | +payload = jwt.decode(token, self.secret, audience=["appB"], algorithms="HS256") |
| 290 | +``` |
| 291 | + |
| 292 | +- 如果各服务的 JWT 都使用对称加密类型算法,则各服务使用不同的密钥签名、验证 |
| 293 | + |
| 294 | +### 3.4 注入与越权常规风险 |
| 295 | + |
| 296 | +**技术要点:** |
| 297 | + |
| 298 | +攻击者可能试图构造恶意的 JWT,以绕过权限检查或注入恶意数据,造成越权操作。 |
| 299 | + |
| 300 | +**示例:** 攻击者构造如下 Payload,试图绕过权限检查: |
| 301 | + |
| 302 | +``` |
| 303 | +{"userId": "123","role": "admin"} |
| 304 | +{"userId": "123","username": "admin' or 1=1#","password": "null"} |
| 305 | +``` |
| 306 | + |
| 307 | +如果服务端没有严格验证 Token 的合法性,攻击者可能获得管理员权限。 |
| 308 | + |
| 309 | +**解决方案:** |
| 310 | + |
| 311 | +- 严格验证 JWT 的签名、算法和格式,确保 Token 未被篡改。 |
| 312 | +- 在每个业务接口中,正确处理 Token 权限校验,避免越权操作;对 JWT 中提交的数据进行过滤处理,防止恶意数据影响业务。 |
| 313 | + |
| 314 | +## 四、JWT 测试 |
| 315 | + |
| 316 | +**针对 JWT 的完整测试流程** |
| 317 | + |
| 318 | +[https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology](https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology) |
| 319 | + |
| 320 | +**开始:** |
| 321 | + |
| 322 | +- 查找 JWT |
| 323 | +- 查找测试接口 |
| 324 | +- 重放请求检查 JWT 是否有效 |
| 325 | + |
| 326 | +**简单的检查:** |
| 327 | + |
| 328 | +- JWT 是否必须的? |
| 329 | +- JWT 是否校验签名? |
| 330 | +- JWT 能否持续使用? |
| 331 | +- JWT 是否在客户端生成? |
| 332 | +- 接口是否先校验 JWT 再处理 Payload? |
| 333 | +- 对称加密算法的 JWT 是否使用了弱密钥? |
| 334 | + |
| 335 | +**测试已知漏洞:** |
| 336 | + |
| 337 | +- 'none' Algorithm (CVE-2015-9235) |
| 338 | +- RSA Key Confusion (CVE-2016-5431) |
| 339 | +- JWKS Injection (CVE-2018-0114) |
| 340 | +- null signature (CVE-2020-28042) |
| 341 | + |
| 342 | +**测试其他漏洞:** |
| 343 | + |
| 344 | +- "kid" issues - reveal key & path traversal |
| 345 | +- URL 篡改攻击 |
| 346 | +- JWKS 欺骗 |
| 347 | + |
| 348 | +**额外检查:** |
| 349 | + |
| 350 | +- 跨服务中继攻击/同服务身份验证逻辑错误 |
| 351 | +- JWT 是否校验有效期 exp |
| 352 | + |
| 353 | +**更进一步:** |
| 354 | + |
| 355 | +- 注入、越权等常规漏洞 |
| 356 | +- 模糊测试 |
| 357 | + |
| 358 | +## 五、相关工具 |
| 359 | + |
| 360 | +**JWT.io** |
| 361 | + |
| 362 | +- [https://jwt.io/](https://jwt.io/) |
| 363 | +- 一个在线工具,可以解析和调试 JWT,帮助开发者查看 JWT 的内容和签名算法,识别常见问题。 |
| 364 | + |
| 365 | +**JWT Tool** |
| 366 | + |
| 367 | +- [https://github.com/ticarpi/jwt_tool](https://github.com/ticarpi/jwt_tool) |
| 368 | +- 用于分析、生成和攻击 JWT 的工具,支持算法混淆攻击等多种手段。 |
| 369 | + |
| 370 | +**jwtcrack** |
| 371 | + |
| 372 | +- [https://github.com/Sjord/jwtcrack](https://github.com/Sjord/jwtcrack) |
| 373 | +- 字典枚举破解 HS256, HS384 或 HS512 加密算法的 JWT |
| 374 | + |
| 375 | +**CyberChef** |
| 376 | + |
| 377 | +- [https://gchq.github.io/CyberChef/](https://gchq.github.io/CyberChef/) |
| 378 | +- 用于 JWT 验证、解码、签名的在线工具 |
| 379 | + |
| 380 | +**JWS 库** |
| 381 | + |
| 382 | +- 提供生成和验证 JWT 的库,支持多种签名算法,可用于实现安全的 Token 处理逻辑。 |
| 383 | + |
| 384 | +**Burp Suite 插件** |
| 385 | + |
| 386 | +- Burp Suite 插件市场 |
| 387 | +- sign-saboteur:[https://github.com/d0ge/sign-saboteur](https://github.com/d0ge/sign-saboteur):用于编辑、签名、验证各种签名的 Web Token |
| 388 | + |
| 389 | +> 参考链接:[https://mp.weixin.qq.com/s/O8Er8UmlFrER2LAkdcXCjA](https://mp.weixin.qq.com/s/O8Er8UmlFrER2LAkdcXCjA),整理:沉默王二 |
0 commit comments