Skip to content

feat(auth): support longer PINs and harden login throttling - #125

Merged
shaobeichen merged 1 commit into
shaobeichen:mainfrom
ShawnKung:feat/longer-custom-pin-pr
Sep 16, 2026
Merged

shaobeichen merged 1 commit into
shaobeichen:mainfrom
ShawnKung:feat/longer-custom-pin-pr

Conversation

@ShawnKung

Copy link
Copy Markdown

Summary

  • 将公网和 LAN 自定义 PIN 从固定 8 位扩展为 8–64 位字母数字,同时保持默认 8 位随机数字 PIN 的行为不变。
  • 修复同步并发登录请求可以批量穿透失败次数限制的问题。
  • 更新设置页、登录页、中英文文档以及边界和并发回归测试。

背景

我通过公网 IP 和 NAT 端口映射,将 DSH Pocket 的监听端口直接提供给外网访问。公网入口地址长期稳定,Pocket PIN 是该入口的身份验证凭据。

DSH 具备执行本机命令和读写文件的能力,因此成功通过 Pocket 认证后,攻击者可能获得接近远程代码执行的权限。对于直接暴露在互联网中的入口,认证凭据需要具备足够的抗猜测能力。

目前默认 PIN 是 8 位随机数字;自定义 PIN 则必须是恰好 8 位字母数字,可以混合使用:

  • 10^8 个可能值仅适用于默认的纯数字随机 PIN。
  • 8 位自定义 PIN 使用大小写字母和数字时,字符集大小为 62。
  • 在每一位完全均匀随机的理想情况下,搜索空间为 62^8,约 2.18 × 10^14 种组合。
  • 对应的理论最大熵为 log2(62^8) ≈ 47.63 bits。
  • 用户自行选择的固定 PIN 通常不满足均匀随机假设,实际抗猜测能力可能明显低于 47.63 bits。

Pocket 的自定义 PIN 是通过网络提交、由服务端重复验证的固定凭据。它不是本地设备解锁 PIN,也不是验证一次后立即失效的一次性验证码,因此在安全模型上更接近单因素网络密码。

本 PR 保持默认 8 位随机数字 PIN 的行为不变,只将自定义公网及 LAN PIN 扩展为 8–64 位字母数字。这样不会影响现有用户,同时允许公网暴露场景使用密码管理器生成的长随机凭据。

并发限速问题

原有认证流程为:

  1. 请求刚进入时检查当前 IP 和全局限速状态。
  2. 异步读取 /pocket-login 的 POST body。
  3. 读取完成后比较 PIN。
  4. PIN 错误时记录失败次数并触发锁定。

攻击者可以在限速尚未触发时同时建立大量 POST 请求,暂不结束请求体,然后同步提交这些请求。由于这些请求已经通过第一次限速检查,即使前几个失败已经触发锁定,其余请求仍会继续执行 PIN 比较。

回归测试将失败阈值设置为 3,同时建立并提交 20 个错误 PIN 请求:

  • 修复前:20 个请求全部进入 PIN 比较流程。
  • 修复后:只有阈值内的 3 个请求进入比较,其余 17 个请求返回 429 Too Many Requests。

原有限速能够限制后续新请求,但无法有效限制已经通过首次检查的同步并发请求。攻击者可以按并发批次执行大量猜测,实际尝试速率不再符合配置的失败阈值。

本 PR 在请求体读取完成后、执行 PIN 比较前再次检查限速状态,从而关闭该并发窗口。

修改内容

  • 自定义公网 PIN 支持 8–64 位字母数字。
  • 自定义 LAN PIN 支持 8–64 位字母数字。
  • 默认生成的 8 位随机数字 PIN 保持不变。
  • 现有 8 位自定义 PIN 继续有效,无需迁移。
  • 设置页输入框和 /pocket-login 登录页支持最长 64 位输入。
  • 更新中英文输入校验提示和使用文档。
  • 在读取登录请求体后、比较 PIN 前重新检查限速状态。
  • 增加长 PIN 边界测试和并发限速回归测试。

兼容性

  • 最小长度仍为 8 位。
  • 现有 PIN 文件格式不变。
  • 默认 PIN 的长度、字符集和轮换逻辑不变。
  • 自定义 PIN 仍然只允许大小写字母和数字。
  • Cookie、二维码、URL 参数和认证协议没有变化。
  • 未配置长 PIN 的用户不会观察到行为变化。

本 PR 允许最长 64 位,但没有宣称完整实现 NIST 密码规范。保留 8 位最小长度和字母数字字符集,是为了控制修改范围并兼容现有 Pocket PIN 行为。

Test plan

  • 执行 npm test,172 项测试全部通过。
  • 验证现有 8 位字母数字 PIN 继续可用。
  • 验证长度为 64 的字母数字 PIN 可设置、持久化并认证。
  • 验证少于 8 位、超过 64 位以及包含非字母数字字符的 PIN 被拒绝。
  • 验证设置页与 /pocket-login 输入框允许 8–64 位 PIN。
  • 验证自定义公网 PIN 不会被自动轮换。
  • 验证失败阈值为 3 时,20 个同步并发请求中只有 3 个进入 PIN 比较,其余 17 个返回 429。
  • 验证现有认证、Cookie、WebSocket 和代理测试继续通过。

安全参考

NIST SP 800-63B 将通过网络提交并由服务端验证的 PIN 纳入 password authenticator 的讨论范围,其附录明确说明文档中的 “password” 也包括 passphrase 和 PIN。对于单因素网络密码,NIST 要求至少 15 个字符,建议允许至少 64 个字符,并要求服务端有效限制失败认证尝试。

OWASP Authentication Cheat Sheet 同样建议:未使用 MFA 时将少于 15 位的密码视为弱密码、最大长度至少支持 64 位,并为登录端点实施可靠的 throttling。

CWE-307 将“未充分限制短时间内大量失败认证尝试”定义为认证安全缺陷。其并行连接示例特别指出,如果认证限制未覆盖并行连接,攻击者可以绕过预期的尝试速率限制。

References

🤖 Generated with Claude Code

Allow 8-64 character alphanumeric custom PINs while preserving the generated 8-digit default, and close the concurrent login rate-limit window.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
@shaobeichen
shaobeichen merged commit 230039f into shaobeichen:main Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants