很多人第一次看到这条漏洞时都会觉得莫名其妙:
网站已经强制 HTTPS,证书也正常,登录请求还是 POST,报告为什么还写着“密码明文传输”?
更麻烦的是,不同测试人员口中的“明文”经常不是同一回事。有人指浏览器开发者工具里能看到密码,有人指登录接口仍能通过 HTTP 访问,还有人发现反向代理解密后,把请求原样转发给了另一台服务器。
这几种情况看起来相似,风险差得很远。报告里只有一句“密码以明文形式提交”,没有请求地址、网络位置和复现过程,基本无法直接判断严重程度。
先弄清楚:密码到底在哪里“明文”
浏览器提交登录表单时,密码在应用层本来就是一段可读字符串。JavaScript 要读取它,浏览器要把它组装进 HTTP 请求,服务端也要在内存中拿到它,才能校验密码。
HTTPS 做的事情,是在 HTTP 数据离开浏览器、进入网络之前,用 TLS 加密整个传输过程。到达 TLS 终点后,数据会被解密,再交给 Web 服务器或应用处理。
因此,在 Chrome 开发者工具的 Request Payload 里看到:
{
"username": "alice",
"password": "example-password"
}
不能证明密码正在网络中裸奔。开发者工具展示的是浏览器准备发送的 HTTP 内容,不是网卡上已经加密后的 TLS 数据包。
真正需要查的是下面几种情况。
| 现象 | 通常如何判断 |
|---|---|
| 开发者工具能看到请求体中的密码 | 正常现象,单凭这一点不能定漏洞 |
登录接口可以直接使用 http:// 提交 |
明确风险,需要立即修复 |
| 先用 HTTP 提交,再由服务器跳转到 HTTPS | 跳转已经晚了,第一次请求可能泄露 |
| 浏览器到 CDN 是 HTTPS,CDN 到源站是 HTTP | 要看第二段链路经过哪里,跨公网时风险很高 |
Nginx 到同机 127.0.0.1 使用 HTTP |
需要结合主机隔离、权限和威胁模型评估 |
| 请求体、异常信息或访问日志记录了密码 | 严重问题,泄露面往往比抓包更大 |
会话 Cookie 没有 Secure 或 HttpOnly |
即使登录请求加密,会话仍可能暴露 |
HTTPS 只保护两个 TLS 端点之间的那段路
部署稍微复杂一点,请求一般不会直接进入业务进程。
浏览器
│ HTTPS
▼
CDN / WAF / Nginx
│ HTTP 或 HTTPS?
▼
后端应用
│
├── 日志系统
├── 链路追踪
├── 错误上报
└── 数据库
浏览器地址栏的小锁,只能说明浏览器与当前 TLS 终点之间建立了加密连接。它不会自动保证代理到后端、应用到日志系统、服务到服务之间也使用了 TLS。
图:TLS termination proxy,Galgalesh,CC BY-SA 4.0。
这也是反向代理环境最容易出问题的地方。常见配置如下:
location / {
proxy_pass http://app:8080;
}
用户访问的是 HTTPS,但 Nginx 与 app:8080 之间走的是 HTTP。
这不一定马上等于高危漏洞。如果 Nginx 和应用位于同一台机器,后端只监听 127.0.0.1 或 Unix Socket,普通外部攻击者很难接触这段流量。但如果后端在另一台主机、另一套云网络,或者请求经过了不受控制的公网,风险就完全不同。
Cloudflare 的 Flexible 模式就是很直观的例子:访客到 Cloudflare 使用 HTTPS,Cloudflare 到源站仍是未加密 HTTP。Cloudflare 当前文档建议在条件允许时使用 Full (strict),这样第二段连接也会加密,并验证源站证书。
参考:Cloudflare SSL/TLS 加密模式 · Full (strict)
前端先做一次 SHA-256,并没有解决问题
有些整改建议会让前端在提交前执行:
const value = sha256(password);
然后服务器拿这个哈希值登录。看起来网络里没有出现原始密码,实际上这个哈希值已经成了新的密码。攻击者只要截获它,很多实现里就能直接重放,不需要恢复原文。
客户端哈希还会破坏服务端使用随机盐、工作因子和密码算法升级的能力。普通账号密码登录没有必要自己发明一套“二次加密协议”。先把 TLS、会话和服务端密码存储做好,通常更可靠。
密码进入服务器后,也不能用普通 SHA-256 直接存数据库。OWASP 当前建议优先使用 Argon2id;无法使用时再考虑 scrypt,bcrypt 更适合遗留系统。每个密码都要有独立随机盐,参数也应根据服务器性能调整。
参考:OWASP Password Storage Cheat Sheet
一套能落地的 HTTPS 配置
下面这份 Nginx 配置不追求覆盖所有场景,适合作为检查起点:
server {
listen 80;
server_name example.com;
return 308 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/tls/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# 先用较短时间验证,确认所有子域名都支持 HTTPS 后再逐步增加。
add_header Strict-Transport-Security "max-age=86400" always;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
HTTP 跳转和 HSTS 作用不同。跳转需要浏览器先发出一次 HTTP 请求,服务器才能返回 301 或 308;HSTS 生效后,浏览器会在本地把后续 HTTP 访问直接升级为 HTTPS。OWASP 也提醒,includeSubDomains 和 preload 不适合未经检查就复制粘贴。一旦某个子域名仍依赖 HTTP,贸然开启可能直接导致它无法访问。
如果反向代理和业务服务跨主机,建议继续加密代理后的链路,并验证后端证书:
location / {
proxy_pass https://app.internal.example:8443;
proxy_ssl_server_name on;
proxy_ssl_name app.internal.example;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/ca/internal-ca.pem;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
}
安全要求更高时,可以给内部服务部署 mTLS。这样后端不仅验证代理连接是否加密,还会验证请求方是否持有受信任的客户端证书。
Cookie 经常比登录请求本身更容易出事
账号密码只在登录时发送一次,会话 Cookie 却会跟随大量请求。如果 Cookie 配置松散,HTTPS 做得再漂亮也保不住登录状态。
推荐从类似下面的配置开始:
Set-Cookie: __Host-session=<随机会话标识>;
Path=/;
Secure;
HttpOnly;
SameSite=Lax
几个属性分别解决不同问题:
Secure:只通过 HTTPS 发送 Cookie。HttpOnly:阻止普通前端 JavaScript 读取会话 Cookie,降低 XSS 窃取风险。SameSite=Lax或Strict:减少跨站请求自动携带 Cookie 的场景。__Host-:要求 Cookie 使用Secure、Path=/,并且不能设置Domain,尽量把作用域限制在当前主机。
MDN 的安全 Cookie 指南也建议对会话标识使用 Secure 和 HttpOnly,并尽量缩小 Domain、Path 和有效期。
参考:MDN Secure cookie configuration
别让日志替攻击者保存密码
我见过最离谱的情况,不是登录接口没上 HTTPS,而是开发时为了排查 400 错误,把整个请求体打进了日志:
log.Printf("login request: %+v", req)
之后这些日志又被采集到 Loki、Elasticsearch、Sentry 或第三方 APM。原本只在内存里短暂停留的密码,变成了一份可搜索、可备份、保存几个月的历史记录。
下面这些地方都要检查:
- Web 服务器是否记录完整查询字符串;
- 应用是否输出请求体、表单对象或 DTO;
- 异常上报是否自动附带请求参数;
- API 网关和 WAF 是否记录敏感字段;
- 链路追踪 Span 是否带有 Authorization、Cookie 或密码;
- 测试环境是否把真实账号数据复制进了低权限日志系统。
OWASP 的日志指南明确把认证密码、访问令牌、会话标识、数据库连接串和加密密钥列为通常不应直接记录的数据。
收到“密码明文传输”报告后怎么复核
先不要急着和测试人员争论,也别立刻安排前端加密。把证据补齐:
- 确认完整请求地址:到底是
http://还是https://。 - 检查表单目标和接口地址:页面是 HTTPS,不代表
form action、Ajax 接口或 WebSocket 也是 HTTPS。 - 检查第一次请求:用户输入
http://后,是否在提交敏感数据前完成跳转。 - 画出完整链路:浏览器、CDN、WAF、负载均衡、Nginx、应用分别在哪里终止 TLS。
- 对关键链路抓包:客户端外部链路和代理到后端链路要分开看。
- 检查 Cookie:至少确认
Secure、HttpOnly和合理的SameSite。 - 搜索日志与追踪系统:用测试账号提交一个唯一标记,确认它没有进入日志。
- 检查密码存储:数据库里应保存带盐的慢哈希,不应保存明文或可逆密文。
一份合格的漏洞报告,至少应该写清请求、传输位置、攻击前提和可观察证据。只截一张浏览器开发者工具中的 Request Payload,就给出“高危密码明文传输”,证据是不够的。
反过来,地址栏有锁也不能成为关闭漏洞的理由。代理后的 HTTP 链路、未设置安全属性的 Cookie、请求日志和错误上报,都可能让密码或会话绕开 TLS 的保护。
严重程度怎么定
这类问题没有固定等级,可以按攻击者能否接触敏感数据来判断:
- 登录接口直接允许 HTTP 提交,攻击者可在用户网络中窃听:通常需要高优先级处理。
- CDN 到源站跨公网使用 HTTP:风险明显,应该尽快改为端到端 TLS。
- 跨主机内网使用 HTTP:要看网络隔离、租户边界、旁路监听能力和数据敏感度。
- 同机回环地址或 Unix Socket:风险相对可控,仍要防止本机高权限进程和错误暴露。
- 仅仅因为浏览器开发者工具显示请求体:不能据此认定存在传输漏洞。
- 密码进入日志、APM 或错误上报:这已经超出“传输”问题,往往需要立即清理历史数据、轮换凭据并追查访问记录。
最后
HTTPS 很重要,但它的边界必须说清楚。它保护的是某两个端点之间的数据传输,不负责替你清理日志、保护 Cookie、隔离后端网络,也不会自动把数据库里的密码变成安全哈希。
下次再看到“密码明文传输”,先问一句:在哪一段链路、由谁能够看到、需要什么条件?
这三个问题答不出来,报告无法定级;这三个问题答清楚,整改方案通常也就出来了。
延伸阅读
- 你扫描的是代码,攻击者盯的是发布链路:软件供应链安全到底在防什么
- 为什么你的 Docker Compose 项目越写越乱?从“能跑”到“可维护”的 12 条工程实践
- Windows 11 + WSL2 开发环境搭建全教程
资料来源
上了 HTTPS,渗透测试为什么还会报“密码明文传输”
https://wangling.hauchet.cn/archives/https-password-plaintext-transmission-finding
评论