SNETCracker弱口令审计:范围、批量检测与复核避坑

发布时间:2026/10/26 18:28:38
SNETCracker弱口令审计:范围、批量检测与复核避坑

简介:SNETCracker 是面向 Windows 平台的弱口令审计工具源码包,适合安全运维、渗透测试学习者及需批量核查账号风险的技术人员。工具基于 C# 与 .NET Framework 4.0 开发,覆盖 SSH、RDP、SMB、MySQL、FTP、Redis 等常见服务,支持用户名与密码组合尝试、自定义端口和字典,并可按 IP 段多线程检查,自带端口扫描以减少第三方工具依赖。压缩包共 129 个文件、约 11.47MB,以 62 个 cs 源码、54 个 dll 依赖及 resx、config、csproj、sln 等工程文件为主,便于在 Visual Studio 中梳理项目结构与线程池实现。已有 1163 人学习下载,读者可了解弱口令审计流程、多线程调度、批量目标管理和字典策略,也可作为内部安全自查工具二次开发的参考样本。

1. 弱口令审计不是"跑一遍就完事":三个前置条件决定结果有没有价值

第一次拿 SNETCracker 这类弱口令检查工具做审计,多数人的开局是这样的:把手上所有 IP 一股脑贴进去,字典挑最大的那个,线程拉到顶,点开始,然后盯着进度条刷。跑完导出一张表,几十条"弱口令账号",看着挺有成就感。结果这份表交上去,复核环节一条都过不了——要么目标根本不在授权范围内,要么同一个账号在三次尝试后就被锁定策略锁死,剩下的记录全是认证失败的假阳性。工具本身没问题,问题出在开局那三件事没定:测谁、测什么、测到什么程度停。这篇就把这三件事拆开,顺带把批量多线程、用户名密码结合检查、自定义端口和自定义字典这几个能力讲透,让第一次上手的人能照着配,也让已经跑过几轮的人回头看看自己的流程漏在哪。

2. 划清可测范围:从资产台账到端口清单的三层筛选

弱口令审计最容易翻车的地方不在工具里,在打开工具之前。我见过太多人拿到一份 IP 段就直接开跑,跑到一半发现里面混着业务核心库、混着第三方托管的主机、混着早就下线的测试机,最后报告没法交,还得回头解释为什么碰了不该碰的机器。范围这件事必须做成可追溯的清单,而不是脑子里的一团印象。下面两层筛选,第一层决定"哪些能测",第二层决定"测哪个门"。

2.1 从台账到可测目标:把授权落到每一行记录上

资产台账和可测目标是两回事。台账是"我有什么",可测目标是"我这次被允许测什么、测到什么时间、用什么强度"。这两者之间的差集,就是审计事故的高发区。我一般会要求清单里至少有这么几列,缺一列就不进队列:

字段作用缺失后果
资产编号和目标 IP 一一绑定,报告里回指报告无法定位,复核时对不上号
归属系统/负责人出问题第一时间能找到人触发告警后无人认领
授权单据号证明本次测试有据可依被当成异常扫描处理
测试窗口允许执行的时间段撞上业务高峰,影响生产
账号锁定策略连续失败几次锁定、锁多久批量跑直接把运维账号锁死
是否允许口令尝试有些主机只允许做配置核查越界操作

最后两列最容易被忽略,也最容易出事。账号锁定策略这一项,决定了你后面线程数和字典规模的上限——一个"5 次失败锁定 30 分钟"的域环境,你拿 500 条字典去打,第一条记录之后全是无效数据,还会把正常运维挡在门外。所以台账整理阶段就要把这类信息问清楚,问不到的就默认按最严格处理:单账号尝试次数不超过 2 次,跑完立刻停。

清单整理完还要做一次"反向确认":把最终队列发给资产负责人过一遍,让他确认这里面每一台都在授权范围内。这一步看着繁琐,但它是唯一能在事后拿出来的证据。我吃过一次亏,清单里有一台是某同事临时挂上来的测试机,没人认领,跑完第二天就被安全团队找上门。从那以后这份反向确认我一次都没省过。

2.2 端口不是越多越好:默认端口与自定义端口的取舍

弱口令审计本质上是"对认证入口做尝试",所以第一步是把认证入口找出来。很多服务跑在默认端口上,但生产环境里改端口的比例远比想象中高——改端口不算什么高深的安全措施,但它确实能把一大批只会扫默认端口的脚本挡在门外。SNETCracker 支持自定义服务端口,这个能力在真实环境里的价值,比多跑几本字典还大。

常见的默认端口先列一份底表,作为扫描阶段的起点:

服务类型默认端口审计时的注意点
RDP3389失败次数阈值低,容易触发锁定
SSH22多数有 fail2ban 类防护,注意封禁
SMB445域环境下可能触发账号锁定
FTP21匿名登录要单独确认,不算弱口令
Telnet23明文传输,测试前确认不影响业务
MySQL3306注意 host 限制,可能只允许内网
SQL Server1433与 Windows 认证模式相关
Oracle1521监听器与实例名分开确认
PostgreSQL5432默认账号权限范围要提前确认
Redis6379无认证和有密码是两种状态
MongoDB27017老版本默认无认证
VNC5900部分实现无锁定机制,风险高

这张表只是起点,不是终点。真正要做的是"端口开放"和"服务指纹"两步走:先做端口扫描拿到开放列表,再对每个开放端口做一次协议识别,确认它跑的到底是不是你以为的那个服务。跳过指纹识别直接按端口号去尝试,是误报的主要来源之一——把 3389 上的一个自定义 Web 服务当成 RDP 去打,跑出来的"失败"毫无意义,跑出来的"成功"更可怕,说明工具把返回内容误判成了认证通过。

自定义端口的写法上有个细节:如果目标服务改到了非常规端口,要在清单里显式标注"服务类型 + 端口",而不是只给一个端口号。因为同一个端口号在不同主机上可能对应完全不同的服务,只给端口号,后续配置阶段就得靠猜。我习惯在清单里多加一列"协议假设",跑之前先小样本验证 3 到 5 台,确认假设成立再全量铺开。

还有一类目标要单独拎出来:只对内网开放、但通过某种方式暴露到更大范围的管理后台。这类入口往往是最该审计的,也是最容易在台账里被漏掉的,因为它们不在标准的资产扫描结果里。整理阶段多问一句"有没有对外映射的管理端口",往往能捞出几条真正有价值的目标。

3. 工具能力拆开看:多线程、用户名密码组合、自定义字典各自解决什么问题

SNETCracker 在 Windows 平台上跑,核心能力是三块:批量多线程、密码与用户名结合检查、自定义端口和字典。很多人把这三块当成"参数越多越强",实际上它们各自对应一个具体问题,配错了不但不提效,还会制造假数据。这一章把三块拆开,讲清楚每块在什么场景下该开到什么程度。

3.1 多线程批量的收益曲线:不是线程越高越快

多线程的价值在于把"等待网络往返"的时间重叠起来。单线程跑一个目标,绝大部分时间花在建立连接、等待服务响应上,CPU 几乎是闲的;开到 20 线程,吞吐量能上去一个数量级。但这条曲线很快就平了,再往上加,收益递减得非常快,原因有三个:

第一是目标侧的限制。很多服务对并发连接数有上限,超过之后新连接直接被拒,工具记录下来就是"连接失败",你得回头看日志才知道是被限流了,不是口令错了。第二是中间设备的限制。防火墙、入侵防御这类设备对短时间内的大量认证请求有阈值,超了就把源 IP 拉黑,后面的目标全部连不上,一轮跑下来有效数据可能不到三成。第三是账号锁定。并发高意味着同一账号在极短时间内收到多次尝试,锁定策略一触发,这个账号在锁定窗口内就彻底没法测了。

我的经验值是:面向单台主机、账号数量少的场景,10 到 20 线程足够;面向大量主机、每台只做少量尝试的横向批量场景,可以开到 50 左右,但要配合"每台主机串行、主机之间并行"的策略。这个策略的意思是,同一台机器上的多次尝试排队走,不同机器之间并行跑,这样既压住了单机的失败频率,又吃到了批量的吞吐。很多人反过来配——单机高并发、多机串行——正好踩在锁定策略和限流的两条红线上。

判断线程数合不合适,有个很土但管用的办法:先拿 5 台目标试跑,看三个指标——连接失败率、平均响应时间、有效认证响应占比。连接失败率超过 10%,说明线程或频率超了;平均响应时间随线程数上升明显变长,说明对端在排队;有效认证响应占比低于 80%,说明大量请求根本没到认证环节。这三个指标任意一个不对,先把线程砍一半再跑。

3.2 用户名和密码结合检查:命中率差距就出在这一步

只跑密码字典的做法,命中率低得可怜。原因很简单:绝大多数弱口令不是随机撞上的,而是有规律的——用户名和密码相同、用户名加年份、用户名加 123、用户名首字母加生日。这类规律用纯密码字典覆盖不到,但用"用户名 + 密码"组合的方式生成候选,命中率会高出一个量级。这也是 SNETCracker 强调"密码支持和用户名结合进行检查"的实际意义。

组合规则我一般分四层来做,从代价低到代价高:

第一层是恒等变形,用户名本身当密码,加上常见的后缀 123、1234、123456、!、@、#。第二层是大小写和分隔符变体,首字母大写、全大写、加下划线或点。第三层是组织特征词拼接,把系统名、部门缩写、年份拼进去,这一层需要提前收集信息,命中率最高但也最费工夫。第四层才是通用弱口令字典兜底。

用 Python 生成第一、二层候选的脚本大概长这样,参数都能改:

# 根据用户名列表生成组合候选,输出一行一个 "用户名:密码" # 用法:python gen_combo.py users.txt > combo.txt import sys SUFFIXES = ["", "123", "1234", "123456", "12345678", "!", "@", "#", "2023", "2024"] PREFIXES = ["", "!"] def variants(name): """生成单个用户名的常见弱口令变形""" base = name.strip() if not base: return seeds = {base, base.lower(), base.upper(), base.capitalize()} for s in list(seeds): # 首字母大写 + 常见后缀 for suf in SUFFIXES: yield s + suf yield s + suf[::-1] if suf.isdigit() else s + suf # 组织特征词拼接 for s in seeds: for org in ("admin", "test", "dev", "ops"): yield org + s yield s + org def main(): with open(sys.argv[1], encoding="utf-8") as f: for line in f: for cand in variants(line): if 6 <= len(cand) <= 20: # 过滤掉过短过长的 print(f"{line.strip()}:{cand}") if __name__ == "__main__": main()

这段脚本的作用是把"用户名"扩展成"用户名 + 候选密码"的成对列表。SUFFIXES和PREFIXES是要按目标环境改的,比如目标系统有明确的上线年份,就把年份加进去;如果是内部系统,组织缩写那一组权重最高。6 <= len(cand) <= 20这个长度过滤是按常见密码策略设的,实际环境里如果最短长度要求是 8,就改成 8,能省掉一大半无效尝试。脚本输出的格式是"用户名:密码",正好对应工具里用户名密码成对输入的场景,省去手工拼接。

这里要提醒一句:组合生成出来的候选数量会膨胀得很快。100 个用户名,按上面的规则大概能生成几千条,再叠加字典就是几万条。所以生成完必须先算一遍总量,再和"单账号允许尝试次数 × 账号数"对一下,超了就砍规则,别硬上。

3.3 字典裁剪:体积和时间之间要算一笔账

通用弱口令字典动辄几十万条,直接丢进去跑,一台机器可能要跑几个小时。真实审计里时间是有限的,所以字典要分层配:

第一层,Top 200 到 500 的高频弱口令,覆盖绝大多数"admin/admin""root/123456"这类;第二层,按服务类型定制的字典,数据库类和 Web 后台类的常见弱口令分布不一样;第三层,组织特征词,来自第二步收集的信息;第四层,才是通用大字典兜底。前两层跑完,命中率通常能到七成以上,后面两层是补漏。

字典文件本身还有两个工程细节。一是编码,Windows 平台上工具读字典时如果编码对不上,中文或者特殊字符会变成乱码,表现为"明明字典里有这条,就是不命中"。统一存成 UTF-8 无 BOM 是稳妥做法,改完拿记事本另存一次确认。二是空行和行尾空格,字典里混进空行会导致工具尝试空密码,这在某些服务上会被记成一次异常登录,日志里很难看。跑之前用一条命令清一遍:

# 去掉空行、行尾空格和 Windows 换行符,输出干净字典 sed -e 's/\r$//' -e 's/[[:space:]]*$//' raw.txt | grep -v '^$' | sort -u > clean.txt

sed的两条替换分别处理回车符和行尾空格,grep -v '^$'去掉空行,sort -u去重。去重这一步能砍掉不少体积,尤其是多个来源合并的字典。

4. 一次可复现的审计流程:从配置到可交付报告

前面讲的是"想清楚",这一章讲"跑明白"。一次能复现的审计,从头到尾要留下四样东西:目标清单、配置快照、原始结果、复核记录。缺任何一样,这份结果都没法被别人重跑,也就没法被信任。

4.1 配置阶段:先小样本试跑,再全量铺开

配置阶段的顺序我固定成这样:导入目标清单 → 按服务类型分组 → 每组挑 3 到 5 台做试跑 → 核对试跑结果 → 调整参数 → 全量执行。

分组这一步很关键。不同服务的认证行为差异很大,同一个线程数在 SSH 上没问题,在 RDP 上可能直接触发锁定。所以线程数、超时时间、重试次数这些参数要按服务类型分开设,不能一套参数打天下。

试跑阶段要看的东西比结果本身更重要:认证失败的返回内容对不对、连接超时比例高不高、有没有出现"连接成功但认证阶段无响应"的情况。最后这种最麻烦,说明协议握手有问题,可能是端口对了但服务类型判断错了,也可能是目标有前置的认证网关。试跑不通过就调参数,别急着全量。

配置快照我一般存成一个文本文件,内容包括:工具版本、目标清单文件名、字典文件名和条数、线程数、超时时间、重试次数、执行时间窗口。这份东西在报告附录里放一页,后面谁来复核都能照着复现。

4.2 执行与监控:中断了怎么接上

全量执行阶段最怕两件事:跑一半断了,和跑完了发现结果被覆盖。

断的情况很常见,网络抖动、目标重启、工具卡死都有可能。所以执行前要把目标按批次切开,比如每 50 台一批,一批跑完立刻导出结果,再跑下一批。这样即使中途断了,已完成的部分不会丢。SNETCracker 支持批量导入,分批导入就行,不需要什么特殊操作。

结果被覆盖的问题出在导出命名上。默认文件名重复,第二次导出就把第一次的覆盖了,而很多人是跑完才想起来检查。我的做法是导出文件名里带上批次号和时间戳,比如result_batch03_20240612_1430.csv,跑完统一放一个目录,最后合并。

合并和去重用一个短脚本就能搞定,比手工粘贴可靠得多:

# 合并多批结果并去重,保留每条记录首次出现的时间 import csv, glob, sys seen = {} for path in sorted(glob.glob("result_batch*.csv")): with open(path, encoding="utf-8-sig") as f: for row in csv.DictReader(f): # 以 目标+账号+端口 作为唯一键,防止同一账号被重复统计 key = (row["host"], row["username"], row["port"]) if key not in seen: seen[key] = row with open("merged.csv", "w", newline="", encoding="utf-8-sig") as f: w = csv.DictWriter(f, fieldnames=next(iter(seen.values())).keys()) w.writeheader() w.writerows(seen.values())

utf-8-sig是为了兼容 Excel 打开时的中文显示,DictReader按表头读列,唯一键取主机、账号、端口三元组。如果结果表里没有端口列,就把 key 改成主机加账号。去重之后的数量才是真实命中数,合并前的总数没有意义,因为同一账号可能在多批里都被记录。

监控方面,执行期间要盯的是失败率曲线,不是进度条。失败率突然抬升,通常是触发了目标侧的限流或封禁,这时候应该暂停而不是加速。我一般会把批次间隔设成 5 到 10 分钟,让对端的防护机制有时间复位。

4.3 结果复核与报告产出:每条记录都要能验证

跑出来的结果不能直接进报告,必须抽检复核。抽检比例我按命中数定:少于 20 条全查,20 到 100 条抽 20%,超过 100 条抽 10% 且不少于 20 条。复核的方式是拿单条记录去独立验证一次,确认这个账号确实能用这个密码登录,而不是工具的误判。

复核用一段小脚本比手工点更靠谱,比如验证一个 SSH 账号:

# 单账号复核:确认该账号确实能用该密码登录(仅限授权范围内使用) import paramiko def verify(host, port, user, pwd, timeout=8): client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: client.connect(host, port=port, username=user, password=pwd, timeout=timeout, allow_agent=False, look_for_keys=False) return True except paramiko.AuthenticationException: return False # 认证失败,说明是误报或密码已变更 except Exception as e: return f"ERROR: {e}" # 连接问题,不代表账号无效 finally: client.close()

这段的关键是把"认证失败"和"连接异常"分开处理。复核时最怕把连接超时当成"账号无效"直接划掉,结果把真实命中漏了。allow_agent=False, look_for_keys=False是为了强制走密码认证,避免脚本误用本机的密钥登录成功,产生假阳性。

报告结构我固定四段:范围与授权、方法与参数、命中清单(含账号、服务、端口、验证状态)、修复建议。命中清单里每一条都要标"已验证/待验证",没验证的不能写成结论。修复建议按账号类型分开写——运维账号、应用账号、设备默认账号的处理方式不一样,混在一起写,接收方没法执行。

5. 排查与避坑:五类让结果失真的常见问题

跑出来的数据不对劲,八成不是工具的问题,是配置或者环境的问题。下面这几条是我这些年反复遇到的,按"现象 → 原因 → 解决"整理,遇到对不上的先对照排查。

5.1 认证侧的三类坑

账号被锁,结果全废。现象是第一批跑完后,后续批次的失败率突然变成 100%,日志里全是认证拒绝。原因通常是没确认目标的锁定策略,或者确认了但线程开太高,同一账号在几秒内被尝试了十几次。解决办法是执行前把单账号尝试次数压到锁定阈值的三分之一以下,并把同账号的尝试串行化;已经锁了的,等锁定窗口过去再跑,或者联系管理员临时解锁,别硬试。

服务指纹不匹配,跑了个寂寞。现象是某个端口的失败率异常高,且失败返回内容和其它同类型目标不一样。原因是端口上跑的不是你以为的服务,比如 3389 上挂了个远程管理网关,协议握手能过但认证流程完全不同。解决办法是先做一次协议识别,确认服务类型再配置;识别不出来的目标单独拎出来,用单条手工验证,别混在批量里。

字典编码不对,明明有就是不中。现象是字典里肉眼可见的弱口令没被命中,而同类目标在别的机器上命中了。原因是字典文件编码不是 UTF-8,或者混了 BOM 头、Windows 换行符。解决办法是统一转成 UTF-8 无 BOM、LF 换行,跑之前用前面那条sed命令清一遍,并且抽样确认头几行内容正常。

5.2 网络与并发侧的两类坑

网络抖动被记成认证失败。现象是同一批目标里,某几台的失败率明显偏高,重跑一次结果又不一样。原因是超时时间设得太短,跨机房或者链路上有丢包时,认证请求还没返回就被判超时。解决办法是把超时时间按链路质量分档,跨区域的目标单独设一档更长的超时,并且开启重试——但重试次数要计入单账号尝试次数,别把锁定阈值算漏了。

并发冲高把源 IP 拉黑。现象是跑到中途所有目标都连不上,本地网络正常,但目标侧完全无响应。原因是短时间内的认证请求触发了中间设备的阈值。解决办法是降并发、加批次间隔,跑之前和网络团队打个招呼把测试源加进观察名单,别让防护设备把审计流量当成攻击处理。这条也是最容易在事后解释不清的,所以执行前的沟通记录一定要留。

6. 收在一个习惯上:把一次审计变成可重复的回归项

弱口令这件事有个特点:修完不等于修完。今天改掉的密码,三个月后可能因为人员变动、系统上线、临时账号又退回去了。所以单次审计的价值有限,真正有价值的是把它变成一个有固定节奏的回归动作。

我现在的做法是把整条链路固化成四份东西,缺一份就不算完成:一份稳定的目标台账(带授权和锁定策略)、一套按服务分层的字典和组合规则、一份固定的参数模板(按服务类型分组)、一份可复现的结果合并与复核脚本。这四份东西版本化管理,每次跑只改台账和批次号,其余不动。这样两次审计之间的结果才有可比性——你能说清楚"这次比上次少了 12 个弱口令",而不是"这次扫出来 30 个,上次好像 40 多个吧"。

这里有个小技巧值得单独说:把每次的命中清单和上一次做差集,只看新增和未修复两类。新增的往往对应最近上线的系统或新开的账号,未修复的则是推动不力的部分,这两类的处理优先级完全不同。差集用脚本做,几行代码的事,比人工比对可靠。

# 对比两次审计结果,输出新增命中与未修复命中 import csv def load(path): with open(path, encoding="utf-8-sig") as f: return {(r["host"], r["username"]) for r in csv.DictReader(f)} old = load("audit_202403.csv") new = load("audit_202406.csv") print("新增(上次没有、这次有):") for item in sorted(new - old): print(" ", item) print("未修复(两次都有):") for item in sorted(new & old): print(" ", item)

唯一键取主机加账号,输出的两类正好对应"推动新增系统整改"和"追旧账"两个动作。这段脚本每次审计后跑一遍,五分钟的事,但能让整改推动有据可依。

回过头看,我踩过最贵的一个坑不是参数配错,是没留配置快照。有一次跑完发现命中数比预期高很多,想复现却怎么也想不起当时的线程数和字典版本,最后只能全部重跑,浪费了一整天。从那以后我每次开跑前都强制走一遍"台账双签 + 配置快照 + 小样本试跑"这三步,一步不省。工具再强,也强不过流程上的那点笨功夫。希望帮到你。

本文还有配套的精品资源,点击获取