Skip to content

fix(core): 单 host 并发限流,修复弱口令爆破结果不稳定#51

Open
wuchulonly wants to merge 1 commit into
chainreactors:masterfrom
wuchulonly:fix/host-threads-rate-limit
Open

fix(core): 单 host 并发限流,修复弱口令爆破结果不稳定#51
wuchulonly wants to merge 1 commit into
chainreactors:masterfrom
wuchulonly:fix/host-threads-rate-limit

Conversation

@wuchulonly

Copy link
Copy Markdown

问题

爆破结果不确定:同一条命令,有时能爆出口令,有时爆不出

根因

缺少「单 host 并发上限」。默认 -t 100 时会对同一个 SSH 目标同时发起最多 100 个未认证连接,而 sshd 的 MaxStartups(默认 10:30:100)在超过软上限后会随机丢弃预认证连接。zombie 把这种网络层丢弃当成「口令错误」直接丢弃、不重试——于是携带正确口令的那次尝试一旦被随机丢掉,就被静默漏报;而哪一次被丢是随机的,所以同样的命令时灵时不灵。

修复

新增按 ip:port 计数的单 host 信号量 hostLimiter,限制每个目标的在飞连接数:

  • acquire 感知 context:等额度期间若目标已被取消(例如其他 worker 已爆出口令),立即返回且不建连——避免命中后排空剩余队列时连接数瞬间暴涨、冲破服务端限速。
  • worker 顶部短路已取消的任务,同样不再建连。
  • 闸放在外层 worker、且在超时计时之前:排队等额度的时间不计入单任务超时(否则重竞争下会误判 goroutine timeout);通过 defer 释放,保证 Brute panic 时也不漏额度。
  • 新增 --host-threads(默认 8,0 = 不限)。8 落在 sshd 默认的「必收」区内(MaxStartups 从 10 才开始丢),而 10 恰好踩在拐点上。

验证

SSH 目标(sshd 默认 MaxStartups),40 条口令字典、正确口令在中间,重复多轮:

--host-threads 漏报率
0(不限) 46%
10 13%
8(新默认) 0%
2 0%

修复后,单 host 在飞连接数严格等于 --host-threads


附:测试中发现一个与本 PR 无关的预存问题——CLI 不带 -f 输出文件时 OutputHandler 不会启动(Runif r.OutFunc != nil 守卫),而 Output() 仍无条件向 OutputCh 发送,导致首条结果阻塞、整体挂起。本 PR 未改动该处,供 maintainer 参考。

Brute results were non-deterministic: the same command would find the
password on one run and miss it on another.

Root cause: there was no per-host concurrency cap, so with the default
-t 100 the brute fired up to 100 simultaneous pre-auth connections at a
single SSH target. sshd MaxStartups (default 10:30:100) then randomly
dropped connections above the soft limit. zombie treats such network
drops as "wrong password", so whenever the attempt carrying the correct
password happened to be dropped, it was silently missed — and which
attempt got dropped was random, hence the run-to-run flakiness.

Fix: add a per-host semaphore (hostLimiter) keyed by ip:port that bounds
in-flight connections per target.
- acquire is context-aware: if the target is cancelled while waiting for
  a slot (e.g. another worker already found the password), it returns
  without dialing, so the post-first-success queue drain does not burst
  connections.
- Skip already-cancelled tasks at the top of the worker.
- The gate sits in the outer worker before the timeout select, so time
  spent queuing for a slot is not counted against the per-task timeout;
  released via defer for panic safety.
- New flag --host-threads (default 8) controls the cap; 0 = unlimited.
  8 stays inside sshd's default always-accept zone (MaxStartups starts
  dropping at 10), while 10 sits exactly on the knee.

Verified on an SSH target with default MaxStartups, 40-password dict with
the correct password mid-list, repeated runs:
  unlimited: 46% miss | --host-threads 10: 13% | 8: 0% | 2: 0%
In-flight dials now strictly equal --host-threads.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant