简介:SNETCracker 是一款面向 Windows 平台的弱口令审计与安全自查工具,适用于企业内网安全巡检、渗透测试学习与教学演示场景,帮助安全运维人员快速发现账号体系中的弱密码、弱口令隐患。工具以 C# 开发,运行需安装 .NET Framework 4.0,覆盖 SSH、RDP、SMB、MySQL、SQLServer、Oracle、FTP、MongoDB、Redis、VNC、Telnet、SMTP 等二十余种常见服务的口令检查,支持用户名与密码组合尝试、批量导入 IP 或按网段扫描、自定义服务端口与口令字典,并自带端口扫描模块,无需依赖第三方工具。资源包共 129 个文件,以 62 个 .cs 源码文件与 54 个 .dll 依赖库为主体,另含 resx 资源、ico 图标、sln 解决方案与 csproj 工程配置等,压缩后约 11.47MB,可直接在 Visual Studio 中打开研读或二次编译。目前已有 1163 人学习,适合想了解弱口令检测原理、线程池调度与扫描器工程结构的安全从业者与开发者参考。
1. 给已授权资产做口令抽检,为什么大家都用 SNETCracker 起手
给一批已经拿到书面授权的内网资产做口令强度抽检,最耗时间的往往不是跑扫描,而是把几万条尝试结果整理成能交给运维看的东西。SNETCracker 这个超级弱口令检查工具处理的正是这一段:Windows 平台下批量、多线程地把「账号 + 口令」组合喂给各类服务,尽快筛出弱口令账号。它明确支持把用户名和密码结合起来尝试,也允许自定义服务端口和字典文件,这两个特性在实际环境里比「支持多少个协议」更影响成败。下面按能照着做的顺序拆:协议面怎么确认、字典怎么生成、并发怎么压、结果怎么验,最后是几条踩过的坑。附件源码和文章源码里带的那些配置样例,看的时候对照着调参数会更快。
2. 协议面与服务指纹:先确认对着谁打,再谈字典
口令审计里最常见的一种浪费,是对着一个根本没开放认证端口的服务跑了几十万次尝试,最后报告全是超时,谁也说不清是网络不通还是口令没错。所以在打开 SNETCracker 之前,我一般会花十分钟把目标的服务面先摸清楚——哪些端口开着、跑的是什么、认证入口在哪。这一步不做,后面所有参数都是瞎调。
2.1 判定成功的信号来自哪里
这类工具的判定逻辑本质上很朴素:连上去、发认证请求、看返回码。难点在于「成功」和「失败」在不同服务上的表现完全不一样,有些服务失败时返回的是明确的错误码,有些则直接断开连接,还有些在连续失败几次之后会静默延迟响应。
拿常见的几类认证入口举例。文本类协议(FTP、Telnet 这类)失败时通常回一个 5xx 或明确的拒绝字符串,判定很干脆;SSH 失败会走密钥交换后的认证失败流程,包长度和返回内容都比较稳定;SMB 和 RDP 的失败信号藏在协商阶段的返回结构里,网络抖动和真正的认证失败很容易混淆;数据库类(MySQL、SQL Server、Oracle)在认证失败时往往带上错误码和版本信息,反而最容易被准确识别;而 Redis、MongoDB 这类在未启用认证时是「直接可用」,压根不存在口令错误这一说,工具会把它记成成功,这属于协议特性,不是误报。
所以配置之前先想清楚:你要检查的是「口令是否为弱口令」,还是「这个服务有没有开认证」。两者在结果表里长得像,处置方式完全不同。
| 认证入口类型 | 失败信号特征 | 易混淆点 | 建议超时 |
|---|---|---|---|
| 文本类(FTP/Telnet) | 明确返回拒绝字符串 | 匿名登录被误判为成功 | 3~5 秒 |
| SSH | 认证失败包结构稳定 | 密钥交换慢导致误判超时 | 5~8 秒 |
| SMB / RDP | 返回结构内嵌错误码 | 网络抖动与认证失败难区分 | 8~10 秒 |
| 数据库类 | 带错误码与版本字符串 | 库名错误也会返回类似结构 | 5~8 秒 |
| 未授权访问类 | 无认证即可读写 | 协议本身无口令概念 | 3 秒 |
表里的超时是给内网千兆环境用的经验值,跨网段或者经过防火墙的设备要往上加,具体加多少在 4.1 节讲。
2.2 自定义端口怎么填才不出错
SNETCracker 支持自定义服务端口,这个功能的实际价值在于:很多设备为了「安全」把默认端口改掉了,但改端口从来不是安全措施,只是把扫描器默认配置挡在外面。对审计方来说,端口映射表必须自己维护。
我的做法是先在授权范围内做一次 TCP 连通性探测,把开放端口和对应服务拉成一张表,再回填到工具的端口配置里。不要直接拿默认端口表跑全量,那样既慢又容易漏。
# 授权范围内做端口连通性探测,输出「IP:端口 -> 是否开放」 # 仅用于已获书面授权的资产,探测本身不发送任何认证数据 import socket from concurrent.futures import ThreadPoolExecutor TARGETS = [("10.0.0.11", 21), ("10.0.0.11", 22), ("10.0.0.12", 3306), ("10.0.0.13", 3389), ("10.0.0.13", 445)] def probe(item, timeout=2.0): host, port = item s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout) try: # connect_ex 返回 0 表示三次握手成功,非 0 为错误码 code = s.connect_ex((host, port)) return f"{host}:{port}", code == 0 finally: s.close() # 无论成功失败都要释放句柄,否则并发一高就耗尽 with ThreadPoolExecutor(max_workers=32) as pool: for desc, ok in pool.map(probe, TARGETS): print(f"{desc}\t{'OPEN' if ok else 'closed/filtered'}")这段脚本只做三次握手,不发送任何协议数据,所以不会触发服务的失败计数,也不会在目标日志里留下认证失败记录。max_workers=32是内网环境下比较稳的取值,探测类的短连接开高一点问题不大;timeout=2.0是单次握手的上限,如果目标跨了网段,把它提到 3~5 秒,否则你会得到一堆假的「未开放」。
拿到这张开放端口表之后,回到工具里按「IP + 端口 + 协议类型」逐项配置。注意端口号只是入口,协议类型要自己指定——把 2222 端口当成 SSH 还是当成别的,取决于对面实际跑的是什么,这一点必须靠 banner 或者运维给的资产清单确认,不能靠猜。常见的做法是先手工连一次看 banner,再决定填哪个协议。
2.3 一次最小可用的检查流程
真正开始跑之前,建议先用一个目标、一个小字典做一次端到端验证,确认链路、确认判定、确认结果能正常导出,再放开到全量。顺序是这样的:
第一步,选单个目标 IP,只填一个确认存在的端口,协议类型填对。第二步,字典只放三五个确定会失败的条目,比如notexist_2024,跑一遍。第三步,看结果里这几条是不是都被判成失败——如果出现「成功」,说明判定逻辑和对面服务不匹配,先解决这个再往下走。第四步,放一条你已知的正确口令进去(授权环境里通常会有测试账号),确认它被判成成功。第五步,才是把目标列表和正式字典一起导进去。
这个「先失败、再成功」的双向验证只花十分钟,但能挡掉后面 90% 的返工。我见过太多次直接全量开跑,跑了两小时发现结果全是成功,实际上是协议类型选错了,工具把「连接建立」当成了「认证通过」。
注意:所有目标必须事先取得书面授权并明确检查时间窗口,未授权环境下不要执行任何认证尝试。
3. 字典与用户名组合:把命中率从个位数拉起来
口令审计的命中率,八成取决于字典,两成取决于并发和超时。很多人第一次跑完发现一条都没中,第一反应是工具不行,实际上是用了一份从网上直接下的通用弱口令表——那份表里的条目,早在几年前就被各种设备强制改掉了。
3.1 单纯跑通用弱口令表为什么几乎必然失败
通用弱口令表的问题在于它是「无主体」的。它假设所有系统都在用admin/123456、root/password这类组合,但真实环境里,口令往往和账号名、组织特征、年份、设备型号强相关。一个zhangsan的账号,口令更可能是zhangsan2023、Zhangsan@123或者zs@202401,而不是123456。
SNETCracker 支持把用户名和密码结合起来尝试,这个特性的价值就在这里:它让「针对每个账号生成候选口令」变成了可执行的流程,而不是给所有人喂同一份表。同样一份基础词根,加上用户名派生之后,候选空间从几千条变成几十万条,但命中概率的提升远大于尝试次数的增长。
需要提前说清楚边界:用户名派生的组合爆炸很快。一个账号名能派生出几十上百个变体,一千个账号就是十万级,再乘以协议数和端口数,很容易跑到百万次尝试。所以派生规则要有节制,靠的是「高频模式」而不是「穷举」。
3.2 企业账号的命名规律与派生规则
先看账号名本身的形态。常见的几类:全拼(zhangsan)、首字母(zs)、拼音加数字(zhangsan01)、英文名(jdoe)、工号(e12345)。账号名越长、越像真实姓名,派生出来的口令命中率越高,因为人倾向于用自己的名字做口令基础。
派生规则我一般按优先级分三档:
第一档是高命中率规则,量小但准。账号名本身、账号名首字母大写、账号名 + 当年年份、账号名 +123、账号名 +@123、账号名 +!。这几条加起来每个账号不到十条,但覆盖了相当一部分真实弱口令。
第二档是中等命中率,包括账号名 + 特殊年份(入职年、设备采购年)、账号名 + 常见弱口令词根(admin、root、test、guest)、账号名首字母 + 生日格式(zs19900101)。这一档每个账号能到五六十条。
第三档是兜底,把通用弱口令表和前面两档做交叉,量会到几百条一个账号,只在时间窗口充裕的时候开。
实际执行时,先只跑第一档。第一档跑完没命中的账号,再决定要不要上第二档。这样总尝试次数可控,目标服务也不容易因为高频失败触发锁定。
3.3 用脚本生成并去重字典
手工拼字典不现实,一般写个小脚本。下面这段按账号列表生成第一档和第二档候选,同时做去重和长度过滤。
# 按账号列表派生候选口令,输出为工具可直接导入的字典文件 # 输入:accounts.txt 每行一个账号名;输出:dict_stage1.txt import re from datetime import date YEAR = date.today().year # 第一档:命中率最高、数量最少,先跑这一档 STAGE1_SUFFIX = ["", "123", "@123", "!", "1", "2024", "2025"] # 第二档:常见词根交叉,量级上升一个数量级 STAGE2_ROOTS = ["admin", "root", "test", "guest", "password", "abc123", "qwerty"] def variants(name: str): base = name.strip() if not base: return low = base.lower() cap = low[:1].upper() + low[1:] # 首字母大写,覆盖 Zhansan 这类写法 ini = re.sub(r"[^a-z]", "", low)[:2] # 取前两位做首字母缩写,覆盖 zs 这类 pool = set() for prefix in (low, cap, ini): if not prefix: continue for suf in STAGE1_SUFFIX: pool.add(f"{prefix}{suf}") for root in STAGE2_ROOTS: pool.add(f"{prefix}{root}") pool.add(f"{root}{prefix}") # 长度过滤:绝大多数系统要求 6 位以上,低于 6 位的直接丢掉省尝试次数 for pwd in sorted(p for p in pool if 6 <= len(p) <= 20): yield pwd seen = set() with open("accounts.txt", encoding="utf-8") as fin, \ open("dict_stage1.txt", "w", encoding="utf-8") as fout: for line in fin: for pwd in variants(line): if pwd not in seen: # 全局去重,避免同一口令被反复尝试 seen.add(pwd) fout.write(pwd + "\n") print(f"生成候选 {len(seen)} 条")几个参数值得说明。STAGE1_SUFFIX里的2024、2025是硬编码年份,实际用的时候改成当前年和上一年,这两年跨度的口令覆盖率最高。STAGE2_ROOTS放的是跨行业通用的词根,如果你的目标属于某个特定行业,把行业词换进去,命中率会明显不同。长度过滤定在 6~20 位,是因为低于 6 位在绝大多数系统上根本设不进去,白跑;超过 20 位则容易触发某些服务的输入截断,导致「口令正确但长度被截」的假阴性。
全量去重这一步不能省。不做全局去重的话,账号名相近的两个账号会派生出大量重复口令,尝试次数翻倍但覆盖不变,而且重复失败会更快触发目标侧的失败计数。
生成完之后建议手工扫一眼前 50 条,看看有没有明显不符合目标系统口令策略的条目(比如目标强制要求含特殊字符,而你生成的绝大多数是纯字母数字),把这类提前剔掉,能省掉一大截无效尝试。
提示:字典文件编码用 UTF-8、每行一条、不要带 BOM、不要有空行,这三点任何一条不满足都可能导致工具导进去之后条目数对不上。
4. 多线程与超时:并发数不是越大越好
并发这块是整套流程里最容易出问题的地方,也是新手最容易踩的坑。很多人第一次配就把线程数拉到一百以上,觉得快,结果要么目标服务被打得响应变慢、大量超时被记成失败,要么直接触发账号锁定,把本来能查出来的口令锁死了。
4.1 线程数、超时、重试的三角关系
这三个参数是联动的,单独调任何一个都没意义。
线程数决定同一时刻有多少个认证请求在飞。超时决定一个请求等多久算失败。重试次数决定失败之后还要不要再试。关系是这样的:线程数越高,目标侧并发压力越大,单个请求的响应时间会被拉长;响应时间一旦逼近超时阈值,就会出现大量「假失败」;而如果开了重试,这些假失败会被重复尝试,进一步加剧压力,形成正反馈。
内网环境下我的起始配置是:线程数 20~30,超时 5 秒,重试 1 次。跑到一百个目标左右看一次结果,如果超时占比超过 5%,把线程数往下砍到 15,而不是去加超时——加超时只是把问题往后推。
跨网段或者经过防火墙的目标,超时要单独给。防火墙对半开连接的处理方式很粗暴,握手成功但认证数据被丢的情况很常见,这类目标超时给到 8~10 秒,线程数压到 10 以下。
还有一个容易被忽略的点:不同协议的合理并发完全不一样。文本类协议单连接开销小,二三十个线程很轻松;SMB、RDP 这类在协商阶段就要来回好几轮,线程开高了反而互相拖累,一般给 8~12 个。
| 场景 | 线程数 | 单次超时 | 重试 | 备注 |
|---|---|---|---|---|
| 同网段文本类协议 | 25~30 | 5 秒 | 1 | 起始配置 |
| 同网段数据库类 | 15~20 | 8 秒 | 1 | 认证握手较重 |
| 同网段 SMB / RDP | 8~12 | 10 秒 | 0 | 不开重试 |
| 跨网段任意协议 | ≤10 | 8~10 秒 | 0 | 防假失败 |
| 目标有锁定策略 | ≤5 | 10 秒 | 0 | 见 4.2 |
4.2 先探锁定阈值,再决定并发
账号锁定是口令审计里唯一不可逆的副作用。锁了就是锁了,有的系统十分钟自动解锁,有的要管理员手工解,还有的锁三次就永久停用。所以并发之前必须先搞清楚目标的锁定策略。
探测方式很直接:拿一个明确的测试账号(不是真实业务账号),用错误口令连续尝试,记录第几次开始返回锁定提示。三次以内就锁的,属于严格策略,这类目标必须把并发压到 5 以下,并且每个账号的尝试次数严格控制在阈值减一。十次以上才锁的,相对宽松,可以正常跑。
不知道阈值怎么办?按最保守的来,假设是三次。听起来很浪费,但比锁掉一个业务账号再去找人解锁要好得多。血泪经验是:宁可慢,不可锁。
另外,很多系统有「同一 IP 连续失败过多则临时封禁」的策略,这和账号锁定是两回事。表现是跑到一半突然所有请求都超时,过一阵又恢复。遇到这个现象,说明是 IP 级封禁,解决办法是降低整体速率、拉长尝试间隔,而不是换目标。
4.3 分批跑与断点续跑
全量目标一把梭的风险在于:跑到一半出问题,前面的结果可能没落盘,得从头再来。我一般的做法是按目标分片,每片跑完立刻把结果导出并落盘,同时记录已完成的片号。
# 把目标列表按批切分,配合工具的分批执行,每批结束后立即归档结果 import json, os BATCH_SIZE = 20 # 每批目标数,按锁定策略调整,严格策略降到 5 TARGET_FILE = "targets.txt" STATE_FILE = "progress.json" def load_state(): if os.path.exists(STATE_FILE): with open(STATE_FILE, encoding="utf-8") as f: return json.load(f) return {"done": []} # done 记录已完成的批次下标,用于断点续跑 def slice_targets(): with open(TARGET_FILE, encoding="utf-8") as f: targets = [l.strip() for l in f if l.strip()] return [targets[i:i + BATCH_SIZE] for i in range(0, len(targets), BATCH_SIZE)] state = load_state() for idx, batch in enumerate(slice_targets()): if idx in state["done"]: continue # 已完成的批次直接跳过 print(f"batch {idx}: {len(batch)} targets -> 交给工具执行") # 这里把 batch 写进工具的导入文件,执行完导出结果 state["done"].append(idx) with open(STATE_FILE, "w", encoding="utf-8") as f: json.dump(state, f) # 每批结束立刻落盘,中断后可续BATCH_SIZE是核心参数。目标有严格锁定策略时降到 5,宽松时可以到 20~30。批次越小,单次中断的损失越小,但人工介入的次数越多,按目标规模权衡。
progress.json这个状态文件看着简陋,但它是唯一的后悔药。跑到第七批的时候目标网络抖动、工具崩了,有了它你只需要重跑第七批,而不是从头开始。每次写完立刻 flush,不要攒到最后统一写。
还有一点,结果导出建议按批次命名(result_batch_00.csv这种),不要覆盖同一个文件。事后核对的时候,批次和时间戳能帮你把「哪一批是在网络抖动期间跑的」这类问题定位出来。
5. 避坑与排查:五条踩过的记录
下面这五条都是实际跑的时候撞上的,按「现象 → 原因 → 解决」写,遇到同类现象可以直接对号。
5.1 现象:全部目标显示超时或连接被拒,一条结果都没有
原因通常不在工具,在链路。三种可能:目标端口填错了(比如把 2222 填成了 22);中间有防火墙只放通了部分源地址;或者工具的运行主机和目标不在同一个可达网段。
解决顺序是先做连通性验证:用 2.2 节那段脚本单独探一次目标端口,确认返回 OPEN。如果探测通、工具不通,检查工具所在主机是否有出站限制,以及是否有安全软件拦截了工具的进程。如果探测也不通,问题在网络层,跟口令审计无关,先找网络侧确认。
5.2 现象:跑了几万次尝试,一条都没命中,但超时率很低
这种「跑得很顺但零结果」的情况,九成是字典和目标不匹配。要么字典全是通用弱口令、没有做用户名派生;要么目标的密码策略强制要求特殊字符和长度,而字典里大量条目在提交阶段就被策略挡掉了(表现为快速失败,不消耗失败计数)。
解决方法是先用 3.3 节的脚本生成针对性的第一档字典,只跑这一档,看命中情况。同时挑三五个账号,手工用一条确定符合策略的错误口令试一次,观察返回是不是「口令错误」而不是「不符合策略」——后者说明你的字典在格式层面就被拒了,得调整生成规则。
5.3 现象:结果里出现大量「成功」,但拿这些口令手工登录却登不上
这是判定逻辑和服务不匹配的典型表现。常见于:协议类型选错(对面跑的不是你指定的协议);目标服务在未认证状态下也会返回一个看似成功的响应;或者目标有前置的负载均衡,把请求转到了另一台不需要认证的实例上。
解决方法是回到 2.3 节的双向验证:先用必然失败的口令确认失败能被正确识别,再用已知正确的口令确认成功能被正确识别。两个方向都对上,结果才可信。如果失败方向识别不了,说明这个协议在当前目标上的判定不可靠,这类目标建议单独拎出来手工核。
5.4 现象:跑到中途目标开始大面积拒绝连接,过一阵又自行恢复
典型的 IP 级速率封禁,不是账号锁定。原因是你这台主机的整体失败速率超过了目标侧的安全策略阈值,触发了临时封禁。
解决办法是降速而不是换工具:把线程数砍到原来的一半,在批次之间加固定间隔(比如每批之间停 30 秒),并且关掉重试。如果目标策略特别严,改成单线程、每条之间加 200 毫秒间隔,速度慢但不会被打断。这类目标最好单独排一个时间窗口跑,不要和其他目标混在同一批。
5.5 现象:结果导出后条目数对不上,导入的字典明明有五万条,结果只有三千条
三个高频原因。一是字典文件带 BOM 或有空行,工具解析时把非法行跳过了;二是字典里有超出目标系统长度上限的条目,在提交阶段被截断,同一条被记成多次;三是并发写入结果文件时没有加锁,高并发下部分记录被覆盖。
解决方法是导入前先做一次清洗:去掉 BOM、去掉空行、按长度过滤、全局去重,用 3.3 节脚本产出的文件直接导入,不要手工编辑。结果文件这块,如果工具本身不支持并发安全写入,就把线程数压到结果不丢的水平,或者按批次分开导出再合并。
注意:这五条里只有 5.4 会留下目标侧的痕迹,排查时优先看目标的安全日志,比在工具侧猜要快得多。
6. 命中之后:怎么验证、怎么复测、怎么留证据
跑出一份命中列表只是开始,真正交付的是「可复核的结论」。我一般分三步走。
第一步是抽样复核。从命中列表里按协议分层抽 10%,用独立的方式再登一次——不是用工具,用系统自带的客户端或者命令行。FTP 用命令行、数据库用官方客户端、远程桌面用手工连。复核的重点不是「能不能登」,而是「登进去之后看到的东西是不是你以为的那个系统」。同一 IP 上跑多个实例的情况很常见,工具连上的可能是个测试实例,而你以为是生产。
第二步是确认口令的实际强度属性。同样是命中,Admin@2024和admin123的风险等级完全不同。前者可能是符合策略但仍在字典覆盖范围内,后者是彻底违背策略。处置建议要分开写,否则运维拿到一份混在一起的清单,只会全部当成误报处理。
第三步是复测。整改之后必须用同一份字典、同一套参数再跑一次,确认原来命中的条目全部变成失败。复测时最容易翻车的地方是参数变了——线程数、超时、字典版本任何一个不同,结果就不可比。我习惯在每份结果文件头部记一行元信息:字典的哈希、线程数、超时、执行时间窗口。
| 复测项 | 必须一致 | 说明 |
|---|---|---|
| 字典文件 | 哈希一致 | 换个字典等于换了个测试 |
| 线程数与超时 | 完全一致 | 影响假失败率 |
| 目标清单 | 一致 | 新增目标单独成批 |
| 执行时间窗口 | 尽量同段 | 避开业务高峰 |
最后说个习惯。早些年我图快,跑完直接导 CSV 交给运维,结果对方拿着清单来问「这条为什么是弱口令」,我翻遍记录也说不清当时用的是哪个字典、哪套参数。从那以后我每次跑之前都强制走一遍「字典哈希 + 参数快照 + 批次编号」三件套,写进结果文件名里。麻烦是麻烦,但真出事的时候,这是唯一能自证的东西。希望帮到你。
本文还有配套的精品资源,点击获取