我亲手抓到了自己的账号密码:一次完整的 HTTP 登录抓包实验
在学习 Web 安全时,我们常听到一句话:
"HTTP 是明文的,不安全。"但很多人(包括我以前)并不知道:
👉 明文到底是怎么"跑在网络里"的?
👉 抓包时一堆 ACK 到底在干嘛?
于是我做了一个实验:
亲手部署一个 HTTP 登录服务,用 Wireshark 抓包,看账号密码是如何被发送的。
这篇文章,就是对这次实验的完整复盘。
一、实验环境说明(非常重要)
为了避免任何安全和法律问题,这次实验 全部在我自己的设备和服务器上完成:
- 一台云服务器(部署 HTTP 登录 Demo)
- 一台 Windows 电脑(负责抓包)
- 浏览器提交 虚拟账号密码
- 使用 Wireshark 抓取 TCP / HTTP 流量
实验目的只有一个:
用最直观的方式,理解 HTTP 登录在网络中是如何传输的
二、实验场景:一个"故意不安全"的 HTTP 登录页
我在服务器上部署了一个极简的 HTTP 服务(端口 8080),页面只有一个表单:直接运行python server.py
python
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs
HTML = """<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>HTTP Login Demo (Insecure)</title>
<style>
body { font-family: Arial, sans-serif; max-width: 520px; margin: 40px auto; padding: 0 16px; }
input { width: 100%; padding: 10px; margin: 8px 0; box-sizing: border-box; }
button { padding: 10px 14px; }
.hint { color: #666; font-size: 14px; }
</style>
</head>
<body>
<h2>HTTP 登录 Demo(故意不安全)</h2>
<p class="hint">用于抓包实验:请只填"虚拟账号密码"。</p>
<form method="POST" action="/login">
<label>用户名</label>
<input name="username" autocomplete="off" />
<label>密码</label>
<input name="password" type="password" autocomplete="off" />
<button type="submit">登录</button>
</form>
</body>
</html>
"""
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/" or self.path.startswith("/?"):
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(HTML.encode("utf-8"))
return
self.send_response(404)
self.end_headers()
self.wfile.write(b"Not Found")
def do_POST(self):
if self.path != "/login":
self.send_response(404)
self.end_headers()
self.wfile.write(b"Not Found")
return
length = int(self.headers.get("Content-Length", "0"))
body = self.rfile.read(length).decode("utf-8", errors="replace")
# 解析表单
data = parse_qs(body)
username = data.get("username", [""])[0]
password = data.get("password", [""])[0]
# 关键:把收到的内容打印出来(你也能在服务端日志看到)
print(f"[LOGIN] raw_body={body}")
print(f"[LOGIN] username={username} password={password}")
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write("OK (This is intentionally insecure)\n".encode("utf-8"))
def main():
host = "0.0.0.0"
port = 8080
httpd = HTTPServer((host, port), Handler)
print(f"HTTP demo server listening on http://0.0.0.0:{port}")
httpd.serve_forever()
if __name__ == "__main__":
main()
注意几个关键点:
- 使用 HTTP,而不是 HTTPS
- 表单提交方式是
application/x-www-form-urlencoded - 没有任何加密、没有任何防护
这是一个"用来被抓包"的登录页面。
三、抓包前的关键认知:不要"找包",要"看会话"
刚开始抓包时,Wireshark 里会看到大量这样的包:
bash
ACK
ACK
PSH, ACK
ACK
很多人会卡在这里:
"这么多 ACK,我该看哪一个?"
答案是:
不要试图理解单个包,而要看一整条 TCP 会话。
Wireshark 已经帮我们做好了这件事:
👉 Follow → TCP Stream
四、一条完整 HTTP 登录 TCP 会话解析
下面是我抓到的一条完整会话。

HTTP POST:账号密码真正出现的地方
接下来这一行,是整组包里最重要的部分:
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

在 TCP Stream 中,可以清楚看到:
username=frank&password=frank
⚠️ 关键结论:
HTTP 登录时,账号密码就是普通文本,
直接作为 TCP Payload 在网络中传输。
没有加密、没有混淆、没有"保护"。
五、为什么会看到这么多 ACK?
这是很多初学者的疑问。
一句话解释 TCP:
TCP 的核心职责只有一件事:
"我发的数据,你收到了没有?"
所以:
- 发数据 → 对方 ACK
- 再发数据 → 再 ACK
- HTTP 只是"搭在 TCP 上的内容"
ACK 多,不是异常,恰恰说明连接非常健康。
六、这次实验我真正理解的 5 件事
通过这次"亲手抓包",我对 Web 和网络安全有了完全不一样的理解:
- HTTP 登录一定是明文的
- TCP 不懂账号密码,只负责传字节
- 抓包要看"连接",不是单个包
- 服务器日志里的 IP,和抓包看到的 IP,来自不同视角
- HTTPS 的价值,在这一刻变得"肉眼可见"
七、为什么 HTTPS 能解决这个问题?
如果把同样的登录页面换成 HTTPS:
- Wireshark 里仍然能看到 TCP 三次握手
- 仍然能看到数据在传输
- 但 HTTP 内容会变成:
bash
TLS Application Data
👉 账号密码彻底消失
不是因为"看不见包",
而是因为 内容在进入 TCP 之前就被加密了。