- 网络安全
- 应用安全
- CLI
- 漏洞扫描
【免费下载链接】testssl.sh
Testing TLS/SSL encryption anywhere on any port
testssl.sh 是一款用于在任意端口上检测 TLS/SSL 加密配置的开源工具,其 FAQ.md 集中回答了用户在实际使用中最常遇到的三大类问题:运行时行为(误报排查、CDN 测试不一致、Docker 下 IPv6 扫描)、评分/评级(STARTTLS 为何得分低、为何不检测 DNSSEC/MTA-STS),以及项目内部架构(为什么坚持用 Bash、Bash Sockets 是什么、OpenSSL-bad 二进制的定位)。本文以该 FAQ 为骨架,结合仓库 testssl.sh 源码、bin/Readme.md 与 Dockerfile.md 等一手资料,逐条给出可复现的命令、底层实现证据与设计哲学,帮助你排查测试中的疑难现象,并理解这些现象背后的原理。
一、运行时问题解析
1.1 误报排查:为什么openssl s_client测不出的问题,testssl.sh 却能报出来
FAQ 中最常见的疑问是:testssl.sh 报出某个发现<XYZ>,但自己用openssl s_client -connect <host:port> <MoreParameters> </dev/null却连不上、测不出同样的结果,于是怀疑是误报。
根因在于测试目标不同。现代操作系统出于安全考虑,在编译或默认配置层面禁用了大量不安全特性;而 testssl.sh 的使命恰恰相反——它必须有能力去测试那些不安全的密码学配置。为此它采用两条路径:
向 OpenSSL 传入正确的选项。对当前发行版自带的 OpenSSL,你可以临时重新启用部分不安全算法,例如:
openssl s_client -connect <host:port> -cipher 'DEFAULT@SECLEVEL=0' <MoreParameters> </dev/null使用
-cipher 'DEFAULT@SECLEVEL=0'后可以测出的"坏密码学"包括:NULL-MD5 这类空加密套件、RSA+SHA1 这类旧签名算法,以及 TLSv1 / TLSv1.1 协议。Bash 套接字(bash sockets)编程。当 OpenSSL 无论如何都无法完成某种连接时,testssl.sh 会退回到自实现的套接字通信(详见本文 3.2 节)。
从源码看,testssl.sh 会在启动时自动探测当前 OpenSSL 是否支持 SECLEVEL 语法(testssl.sh:$OPENSSL ciphers @SECLEVEL=0:ALL > /dev/null成功则置HAS_SECLEVEL=true),并在构造各种 cipher 参数时自动注入@SECLEVEL=0:前缀(见 testssl.sh 与 testssl.sh),从而透明地完成上述"坏密码学"探测。
但有一类坏密码学你无法用这个开关测到:古老的 SSL 协议。现代发行版提供的 OpenSSL 二进制要么在源码层面、要么在编译时就把 SSLv2 / SSLv3 禁用了,运行时无法重新启用。此时可以借助 testssl.sh 项目提供的OpenSSL-bad 版本二进制:
OPENSSL_CONF='' ./bin/openssl.Linux.x86_64 s_client -connect <host:port>这个二进制启用了 SSLv2、SSLv3 以及更多"坏东西";但代价是不支持 TLS 1.3,也不支持现代椭圆曲线。对于这些能力缺口,testssl.sh 会透明地补偿:要么用 Bash 实现,要么在合适场景自动切换到发行版自带的现代 OpenSSL(源码中对应OPENSSL2变量与OSSL_SHORTCUT自动切换机制,见 testssl.sh)。
关于这套预编译二进制的更多细节,仓库 bin/Readme.md 做了权威说明:它们来自 openssl-1.0.2.bad 分支的编译产物,扩展支持 40/56 位密钥、export/ANON 类弱套件、弱 DH 参数、弱 EC 曲线、SSLv2 等常规 OpenSSL/LibreSSL 不具备的"脏特性";严禁用于生产环境(服务端或客户端都不行),它们只服务于安全测试。该文档还特别说明:随着 Bash Sockets 对弱密码学覆盖能力的增强,这些二进制在多数场景已不再是必需品。
1.2 透过 CDN / 负载均衡器测试,结果为什么不稳定
FAQ 指出:testssl.sh 总体上是确定性的、可复现的,但其测试本质决定了它会打开相当数量的连接。当目标位于 Cloudflare 或各类 CDN、OnPrem 负载均衡器之后时,大量连接很容易触发服务端的速率限制(rate limit),于是不同轮次的测试结果可能不一致——具体表现取决于你是用终端交互测试还是自动化脚本测试,是否能看到连接错误也不一定。
FAQ 给出的务实建议是:
- 如果无法把你测试所用的 IP 加入服务端的白名单(allow list),可以只运行受限测试,例如:
testssl.sh -P testssl.sh -S或连续运行一系列此类受限测试,以降低并发连接数、减少被限流的概率。
这两个选项在源码中的含义非常明确(testssl.sh):
-S, --server-defaults:只检测服务器默认配置;-P, --server-preference:只检测服务器的密码套件/协议偏好。
相比全量扫描,这类单项检查建立的连接数少得多,更适合在高限流策略的目标前使用。
1.3 用 Docker 镜像扫描 IPv6 / 双栈主机时 IPv6 不通
FAQ 明确说明:这不是 testssl.sh 的问题,而是 Docker 的"特性"——Docker 在宿主机上默认不会给容器分配 IPv6 地址,而且宿主机的路由也可能需要额外配置。
最快的修复方式是使用host 网络模式,让容器直接复用宿主机的网络栈:
docker run --rm -ti --net=host drwetter/testssl.sh -6 ipv6.google.com其中-6是 testssl.sh 强制使用 IPv6 的开关(IPv6 地址或双栈主机都可用)。仓库提供了完整的镜像使用说明(Dockerfile.md)以及常规(非 host 网络)的 Docker 运行方式(Readme.md),例如:
docker run --rm -ti drwetter/testssl.sh <your_cmd_line>如果你的 IPv6 扫描场景受限于 Docker 默认网络,优先尝试--net=host,这是 FAQ 推荐的最短路径。
二、评分与评级问题解析
2.1 为什么测试 STARTTLS 服务时评分总是偏低
FAQ 指出:SSLlabs 的评分体系原本就不包含 STARTTLS,而 testssl.sh 的评级目标是尽量 1:1 对齐该体系。问题的关键在于 STARTTLS 的工作方式——同一端口上先以明文对话,随后由客户端请求升级到 TLS。这种"先明文、后升级"的模式天然存在嗅探(snooping)与中间人攻击(MitM)风险,因此项目选择将这类服务标记为不安全,并强调:只要可能就应该尽量避免使用 STARTTLS。
从源码可以印证 testssl.sh 对评级处理的严肃性:它实现了完整的评分封顶机制——set_grade_cap()负责把评级封顶到某个等级(A/B/C/D/E/F/M/T),GRADE_CAP_REASONS数组记录所有封顶原因,set_grade_warning()记录评分警告(见 testssl.sh 与 testssl.sh)。例如源码中有一条硬规则:[[ $size -lt 112 || $size == None ]] && set_grade_cap "F" "Using cipher suites weaker than 112 bits"(testssl.sh),即密钥强度低于 112 位的套件直接把总分封顶到 F。这种"宁可保守、不给虚假安全感"的设计取向,与 STARTTLS 评分偏低的处理逻辑一脉相承。
2.2 你们不做 DNSSEC 和 MTA-STS 检测,但这些标准我已经实现了啊
FAQ 的回答很直接:DNSSEC、MTA-STS 这类机制主要只是为 SMTP 25 端口提供"创可贴"式补救。具体而言:
- MTA-STS 已有一个待合并的 PR("there is a PR pending"),DNSSEC 则尚未列入计划;
- 即便服务端配置了这些标准,也不能把服务端标为安全——因为每个客户端都需要自行去验证这些标准,只要有一个客户端不验证,MitM 之门就依然敞开。
FAQ 用 SMTP 邮件服务器间通信举例:即使服务器证书校验失败,邮件仍然会被投递,因为"邮件送达"在默认优先级上高于"安全性"。换句话说:即便收件服务器配置良好、持有有效证书,我们也无法判断发件服务器是否在意证书校验——若将其标为安全,只会给用户造成虚假的安全感。
2.3 那 IMAPS 这类"纯 TLS 端口"呢?
FAQ 承认大多数客户端如今确实会做正确的证书校验,但强调"从明文升级"这一机制的缺陷依然存在:STARTTLS 注入攻击(该缺陷可追溯到 2011 年发现的同类问题,Postfix 相关公告编号 CVE-2011-0411)以及 Opossum 攻击都属于这类隐患,未来还可能有更多衍生问题。因此不能因为"多数客户端会校验证书"就放松对升级型协议的警惕。
补充一个可操作的细节:testssl.sh 内置了对大量 STARTTLS 协议族的支持。源码中fd_socket()的对话框分发逻辑覆盖 ftp/smtp/lmtp/pop3/nntp/imap/sieve/ldap/xmpp/postgres/mysql 等协议(testssl.sh),命令行解析同样接受这些协议名(testssl.sh)。测试时只需:
testssl.sh --starttls=smtp <host:port>另外,仓库提供了若干与 STARTTLS 测试调优相关的环境变量,供高频测试场景使用(testssl.sh):MAX_STARTTLS_FAIL(明文阶段 STARTTLS 握手最大失败次数,默认 2)、STARTTLS_SLEEP(套接字上等待 STARTTLS 的最大秒数,默认 10)、FAST_STARTTLS(默认true,以牺牲部分可靠性换取更少的握手次数)。
三、代码与内部架构问题解析
3.1 为什么用 Bash?别人都用(Python|Golang|Java)……
FAQ 给出了完整的历史与工程理由:
- 历史沿革:项目始于 2007 年,最初就是一组用于渗透测试的、由 OpenSSL 命令组成的 Shell 脚本。彼时 OpenSSL 是所有连接检查的基础操作所必需的(至今部分场景仍然如此),用其他语言实现这些操作反而更繁琐。
- 迁移成本:随着项目规模膨胀,再改写成 Python/Golang/Java 等语言在资源投入上已不现实。
- 调试便利:Bash 相比编译型二进制更容易调试。
- 能力被低估:testssl.sh 甚至原生包含了对 chacha20、gcm/ccm 等加解密函数的编解码实现(见 testssl.sh 中
enc-/dec-functions相关代码)。
FAQ 还强调了一个重要的架构事实:如今任意版本(支持相关特性)的 OpenSSL 或 LibreSSL 都能胜任大部分工作,凡是某个特定版本测不了的,testssl.sh 就用 Bash 完成(即上述编解码函数),而连接检查则交给 Bash Sockets。
3.2 Bash Sockets 到底是什么
Bash Sockets 是一种通过 Shell 接口进行网络编程的方法,核心就是 Bash 的虚拟设备文件:
/dev/tcp/$IPADDRESS/$PORTUDP 场景对应/dev/udp/...。它同样支持 IPv6。
在源码中,这一机制被封装为fd_socket()函数(testssl.sh),其核心动作是:
exec 5<>/dev/tcp/$nodeip/$PORT即通过文件描述符 5 建立双向 TCP 连接。几个值得注意的实现细节:
- 对 IPv6 地址,函数会先剥掉方括号(
tr -d '[]'),因为套接字写法不需要方括号; - 支持通过 HTTP 代理(CONNECT 方法)建连,并会解析代理返回的 HTTP 状态行判断是否成功(testssl.sh);
- 支持套接字超时控制:当设置了
SOCKET_TIMEOUT时,用timeout命令在子 Shell 中执行连接,超时计入NR_SOCKET_FAIL,超过MAX_SOCKET_FAIL(默认 2)则终止(testssl.sh); - 建连成功后按 STARTTLS 协议分发到
starttls_ftp_dialog、starttls_smtp_dialog、starttls_imap_dialog等各协议对话框函数。
仓库中还有多处独立的/dev/tcp用法(如 testssl.sh、testssl.sh),用于探测端口可达性等场景。
3.3 那为什么不用(Python|Perl|Golang|Java)单独实现某个更快的函数?
FAQ 点出了项目的核心哲学与卖点:
testssl.sh 的哲学与美在于它在任何地方都能运行(runs everywhere),与操作系统无关,且只依赖最小集合的典型 Unix 工具,加上任意 Bash 和任意 OpenSSL 版本。
这意味着用户不需要担心:某个版本的依赖库没装、二进制版本是 A.b、解释器版本是 C.d…… 这种"零额外依赖、随处可跑"的独立性,正是项目坚持 Bash 的重要原因。
3.4 关于 OpenSSL-bad:会不会回移 TLS 1.3 / QUIC?二进制的信息去哪看?
FAQ 给出两个明确的官方答复:
- 不会回移 TLS 1.3、QUIC 或其他现代密码学到 OpenSSL-bad 版本。因为更高效的做法是使用发行版自带的现代 OpenSSL,再用 OpenSSL-bad 或 Bash Sockets 按需补偿其缺失能力。同时,除非"天塌下来",否则不会再编译出另一套二进制。
- OpenSSL-bad 的源码、文档与许可证可在其独立的 openssl-1.0.2.bad 仓库获取(仓库 bin/Readme.md 亦说明这些二进制编译自该分支)。欢迎将其用于测试,但不要在生产环境的服务器或客户端中使用。
仓库 bin/ 目录当前实际提供的预编译二进制包括:
openssl.Linux.x86_64openssl.FreeBSD.amd64openssl.Darwin.x86_64
(另有许可证与版本信息文件OPENSSL-LICENSE.txt、openssl-Vall.txt。)bin/Readme.md 同时提醒:这些二进制不支持 TLS 1.3、缺少较新的 TLS 1.2 套件,且随着弱密码学场景逐渐由 Bash Sockets 覆盖,它们在未来版本中可能被退役。
四、FAQ 关键结论速查
| 问题类别 | 关键结论 | 可复现命令 / 源码依据 |
|---|---|---|
| 误报排查 | 现代 OpenSSL 默认禁用不安全特性,需SECLEVEL=0临时启用 | openssl s_client -connect <host:port> -cipher 'DEFAULT@SECLEVEL=0' </dev/null;testssl.sh |
| SSLv2/SSLv3 测试 | 发行版二进制编译时禁用,需 OpenSSL-bad | OPENSSL_CONF='' ./bin/openssl.Linux.x86_64 s_client -connect <host:port>;bin/Readme.md |
| CDN/负载均衡结果抖动 | 连接过多触发服务端限流,改用受限测试 | testssl.sh -P、testssl.sh -S;testssl.sh |
| Docker 下 IPv6 不通 | Docker 默认不给容器分配 IPv6,改用 host 网络 | docker run --rm -ti --net=host drwetter/testssl.sh -6 ipv6.google.com;Dockerfile.md |
| STARTTLS 评分低 | 明文先行升级天然不安全,评分体系未覆盖 STARTTLS | 评级封顶机制 testssl.sh |
| 为何不用其他语言 | 2007 年起源于 OpenSSL 命令脚本;随处运行、最小依赖是核心哲学 | FAQ.md |
| Bash Sockets | 通过/dev/tcp/$IP/$PORT实现网络编程,支持 IPv6 与代理 | testssl.sh |
| OpenSSL-bad 定位 | 不回移 TLS 1.3/QUIC;仅供测试,严禁生产使用 | bin/Readme.md |
以上答案均以仓库当前版本的源码、文档与二进制为事实依据;如果你遇到 FAQ 未覆盖的新现象,建议先对照 CHANGELOG.md 确认版本行为差异,再决定是否提交 issue。
- 网络安全
- 应用安全
- CLI
- 漏洞扫描
【免费下载链接】testssl.sh
Testing TLS/SSL encryption anywhere on any port
相关推荐
s3git完全指南:如何将Git版本控制带入S3云存储的终极方案
s3git完全指南:如何将Git版本控制带入S3云存储的终极方案 s3git是一款革命性的分布式版本控制系统,它将Git的强大版本管理能力与S3云存储的无限扩展
Falco架构深度剖析:内核监控与容器运行时集成原理
Falco架构深度剖析:内核监控与容器运行时集成原理 Falco作为CNCF毕业项目,通过内核级监控与容器运行时集成,为Kubernetes集群提供实时安全事件
云原生运行时防护IDS应用安全RivetKit 运行时边界解析:NAPI 原生与 WASM 双运行时的架构约束与实现原理
RivetKit 运行时边界解析:NAPI 原生与 WASM 双运行时的架构约束与实现原理 导读 RivetKit 是 Rivet Actors 的原生 SDK
后端AI Agent人工智能流程编排WebSocket
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考