Telnet 原理与报文 — NVT、IAC 命令与选项协商
Telnet 的网络虚拟终端模型、IAC 转义命令码、DO/DON'T/WILL/WON'T 协商与子协商 / 应用层 / TCP 23 / RFC 854
Table of Contents
先建立直觉
Telnet 可以想象成一台"对讲机":你说的话和你发出的指挥动作都走同一条无线电波,而且谁都能拿个收音机在旁边听个一字不漏——这就是它"明文"和"带内控制"的本质。为了不让指挥动作(比如"我要改回显规则了")被误当成普通说话内容,Telnet 规定一个特殊代号:凡是看到 0xFF 这个字节,后面跟的就不再是文字,而是"命令"。有了这条规则,一条纯字节流上就能同时跑"聊天内容"和"终端控制指令",而不需要开两条连接。
它解决什么问题
Telnet 诞生于终端机型五花八门的年代:这台机器回车是 CR,那台是 LF,还有机器退格键发的是 DEL。如果让每台客户端去适配所有远端终端,组合爆炸。Telnet 的解法见 1. 工作原理,核心是用一个"标准虚拟终端"做中转,再用"按需协商"叠加高级能力。
工作流程(简化版)
- 客户端与服务端先完成 TCP 三次握手(端口 23)。
- 双方用 IAC 命令互相"商量"能力:要不要远端回显、要不要传窗口大小、传什么终端类型。
- 协商出字符模式后,你每敲一个键,Telnet 就发一个字节过去,由服务端回显。
- 登录与后续所有命令、输出都以明文字节流传输。
- 想中断就发 IAC IP(Ctrl+C)并借 TCP 紧急指针抢插进去;想退出就发
exit。
报文 / 头部长什么样
看这张表前先记住:Telnet 没有固定头部。它是纯字节流协议,控制信息通过
IAC (Interpret As Command, 0xFF/255)转义嵌入数据流。所以下面"报文"其实是"命令字节表",而不是某个分层头部。
Telnet 建立在三个核心概念之上(RFC 854):
1.1 网络虚拟终端(NVT)
双方不直接理解对方的物理终端,而是各自与一台想象中的标准终端对话:
- 字符集:7 位 US-ASCII,装在 8 位字节里传输,高位置 0(启用 BINARY 选项后才允许 8 位)。
- 打印机模型:无限宽行、无限长页,收到不认识的控制码应忽略。
- 换行约定:
CR LF(0x0D 0x0A) 表示换行;单独的 CR 必须写成CR NUL(0x0D 0x00)——这条规则是 Telnet 与其他文本协议互操作时的经典坑。 - 必需控制码:
NUL、LF、CR、BEL(7)、BS(8)、HT(9)、VT(11)、FF(12)。
1.2 协商原则:“商定的选项”
RFC 854 的设计哲学:协议核心保持最小,能力通过选项按需叠加,且遵守两条防死循环规则:
- 只有在改变状态时才发送请求;已处于目标状态时收到请求不再回应。
- 收到请求必须回应(同意或拒绝),但同意后不得再重复请求。
1.3 对称性
客户端与服务端在协议层完全对等,任何一方都能发起协商。实际部署中的角色差异(谁提供 shell)由上层决定。
1.4 典型运行模式
| 模式 | 协商组合 | 行为 |
|---|---|---|
| 半双工行模式(原始默认) | 无 ECHO、无 SGA,用 GA 交替控制 | 逐行发送,效率高但交互差;现代几乎不用 |
| 字符模式(character-at-a-time,最常见) | 服务端 WILL ECHO + 双方 SUPPRESS-GO-AHEAD | 每敲一个键立即发一个 TCP 包,由服务端回显;vi、Tab 补全等交互功能可用;小包极多 |
| 行模式(LINEMODE,RFC 1184) | 双方协商 LINEMODE | 客户端本地编辑整行,回车后一次性发送;省包但需要客户端支持 |
| 本地回显行模式 | 客户端 ECHO,服务端不回显 | 老式设备/BBS 常见 |
抓包判断技巧:一次连接里出现大量 1 字节 payload 的 TCP 包 → 字符模式;出现整行文本的包 → 行模式。
2.1 IAC 命令码表(RFC 854)
| 十进制 | 十六进制 | 名称 | 含义 |
|---|---|---|---|
| 255 | 0xFF | IAC | 命令解释引导符;数据中的 0xFF 需写成 IAC IAC |
| 254 | 0xFE | DON’T | 要求对方停止使用某选项 |
| 253 | 0xFD | DO | 要求对方启用某选项 |
| 252 | 0xFC | WON’T | 声明自己不会使用某选项 |
| 251 | 0xFB | WILL | 声明自己将要使用某选项 |
| 250 | 0xFA | SB | 子协商开始(Subnegotiation Begin) |
| 249 | 0xF9 | GA | Go Ahead,半双工下交出发送权 |
| 248 | 0xF8 | EL | Erase Line,擦除当前行 |
| 247 | 0xF7 | EC | Erase Character,擦除前一字符 |
| 246 | 0xF6 | AYT | Are You There,探活(服务端通常回一行提示) |
| 245 | 0xF5 | AO | Abort Output,丢弃尚未送达的输出 |
| 244 | 0xF4 | IP | Interrupt Process,中断进程(对应 Ctrl+C) |
| 243 | 0xF3 | BRK | Break,映射到终端 BREAK 键 |
| 242 | 0xF2 | DM | Data Mark,Synch 的同步标记,配合 TCP 紧急指针 |
| 241 | 0xF1 | NOP | 无操作 |
| 240 | 0xF0 | SE | 子协商结束(Subnegotiation End) |
2.2 命令格式
| 形式 | 字节序列 | 说明 |
|---|---|---|
| 双字节命令 | IAC <cmd> | 如 FF F4 = 中断进程 |
| 三字节协商 | IAC <WILL/WONT/DO/DONT> <option> | 如 FF FD 18 = DO TERMINAL-TYPE |
| 子协商 | IAC SB <option> <参数...> IAC SE | 如 FF FA 18 00 78 74 65 72 6D FF F0 = 终端类型 “xterm” |
| 数据中的 0xFF | IAC IAC(FF FF) | 转义,表示一个字面量 255 |
2.3 常用选项码(RFC 855 及后续)
| 选项码 | 名称 | RFC | 作用 |
|---|---|---|---|
| 0 | BINARY | RFC 856 | 8 位二进制传输(关闭 NVT 7 位限制) |
| 1 | ECHO | RFC 857 | 谁负责回显。服务端 WILL ECHO = 远端回显 |
| 3 | SUPPRESS-GO-AHEAD (SGA) | RFC 858 | 抑制 GA 信号,进入全双工字符模式 |
| 5 | STATUS | RFC 859 | 查询选项状态 |
| 6 | TIMING-MARK | RFC 860 | 同步标记 |
| 24 | TERMINAL-TYPE | RFC 1091 | 子协商传递 xterm / vt100 / ansi |
| 31 | NAWS (Negotiate About Window Size) | RFC 1073 | 子协商传递列数/行数(16 位各一) |
| 32 | TERMINAL-SPEED | RFC 1079 | 终端速率 |
| 33 | TOGGLE-FLOW-CONTROL | RFC 1372 | 远端流控开关 |
| 34 | LINEMODE | RFC 1184 | 行模式,客户端本地行编辑 |
| 35 | X-DISPLAY-LOCATION | RFC 1096 | X 显示位置 |
| 36 / 39 | ENVIRON / NEW-ENVIRON | RFC 1408 / 1572 | 传递环境变量(曾被用于 USER 免认证攻击) |
| 37 | AUTHENTICATION | RFC 2941 | 认证扩展(Kerberos/SRP),实现率极低 |
| 38 | ENCRYPTION | RFC 2946 | 加密扩展,实现率极低且历史漏洞多 |
2.4 NAWS 子协商示例(窗口 80×24)
交互时序
一句话看懂这张图:从 TCP 建连 → 选项协商进入字符模式 → 明文登录(连口令都是明文)→ 逐字符交互 → 用 IAC IP + TCP 紧急指针中断 → 退出,全过程没有任何加密。
| |
关键机制 / 变体
4.1 协商防死循环规则(RFC 854 的精髓)
这是干什么的:防止双方就同一个选项无限来回确认,把连接变成"乒乓死循环"。
若无约束,A 发 WILL X、B 回 DO X、A 又回 WILL X……会无限循环。RFC 854 规定:
- 只在状态改变时发请求:若已启用某选项,不再重复发
WILL。 - 收到请求必须应答:
DO↔WILL/WON'T,WILL↔DO/DON'T。 - 应答不触发新请求:收到的是对自己请求的应答时,不再回应。
- 拒绝方向不可逆:一方
WON'T,另一方只能DON'T(无法强迫对方启用)。
四元组的语义要点:WILL/WON'T 说的是**“我”要不要做;DO/DON'T 说的是“你”**要不要做。
4.2 ECHO 的三种组合
这是干什么的:决定"你敲的字符由谁显示出来",直接决定输入口令时屏幕为什么是黑的。
| 组合 | 效果 | 场景 |
|---|---|---|
服务端 WILL ECHO | 远端回显,客户端关本地回显 | Unix 交互 shell(最常见) |
服务端 WON'T ECHO | 客户端本地回显 | 输入口令时服务端临时切到此状态(所以你看不到 *) |
| 双方都不回显 | 屏幕无显示 | 密码输入的实际状态 |
注意:口令"看不见"只是本地不显示,网络上依然是明文字节。
4.3 Synch 机制(IAC DM + TCP URG)
这是干什么的:当你按 Ctrl+C 想立刻停下程序时,服务端可能还有一屏幕没发完的输出堆在缓冲里——Synch 让"中断命令"能插队到这些数据前面被立刻处理。
用户按 Ctrl+C 时,服务端可能还有大量未发完的输出堆在缓冲里。Telnet 的解法:
- 客户端发
IAC IP(中断进程); - 紧接着发
IAC DM,并把 TCP 紧急指针(URG) 指向 DM 字节; - 服务端 TCP 收到紧急数据后进入"紧急模式",丢弃数据流直到 DM,只处理其中的 Telnet 命令;
- DM 之后恢复正常处理。
这是应用协议少见地直接依赖 TCP 紧急指针的例子。
4.4 为什么 Telnet 不安全(六条)
这是干什么的:说明为什么今天 Telnet 必须被 SSH 取代——它不是"不够好",而是从设计上就毫无保密与认证能力。
| 风险 | 说明 |
|---|---|
| 明文口令 | 同网段 ARP 欺骗 / 交换机镜像 / 上游任意节点均可抓取;Wireshark 有 “Follow TCP Stream” 一键还原 |
| 明文会话内容 | 命令与输出(包括查看的配置、密钥)全部暴露 |
| 无服务端认证 | 无法确认对端身份,中间人可完整代理并篡改 |
| 无完整性保护 | 攻击者可注入字节,实现命令注入(TCP 序号已知时的会话劫持) |
| 会话劫持 | 经典 hunt/juggernaut 类工具即针对 Telnet 设计 |
| 环境变量注入 | NEW-ENVIRON 曾被用于传递 USER/LD_PRELOAD 绕过认证(如 CVE-2011-4862 之外的多起 telnetd 漏洞) |
历史上 telnetd 还爆出过多个远程预认证溢出漏洞(如 FreeBSD/Heimdal telnetd 的 encrypt_keyid 溢出 CVE-2011-4862),进一步坐实其淘汰命运。
4.5 与 SSH 的对应关系
这是干什么的:帮你把"老的 Telnet 概念"直接映射到"新的 SSH 概念",理解 SSH 其实继承了 Telnet 的远程终端语义。
SSH 保留了 Telnet 的诸多语义并将其结构化:
| Telnet 机制 | SSH 对应 |
|---|---|
| TERMINAL-TYPE 子协商 | CHANNEL_REQUEST "pty-req" 中的 TERM 字段 |
| NAWS 窗口大小 | pty-req 的行列参数 + window-change 请求 |
| ECHO / SGA 等终端模式 | pty-req 的 encoded terminal modes(沿用 Telnet 的模式编号思路) |
| IAC IP 中断 | 信号通过 CHANNEL_REQUEST "signal" 传递 |
| 明文口令 | 加密通道内的 password 认证方法 |
常见误区
- “输入口令时屏幕不显示,说明口令被加密了。” 错。那只是服务端发了
IAC WON'T ECHO让客户端停止本地回显,是显示层面的事;口令字节在网络上仍是明文,抓包直接可读。 - “telnet 能连上 = 服务健康。” 错。能连上只证明 TCP 三次握手成功(网络通 + 端口有监听 + 没被拦);应用层服务本身可能已挂。
- “Telnet 传的是带格式的报文。” 错。Telnet 没有固定头部,是纯字节流,控制靠
IAC转义;所谓"报文"是 IAC 命令字节序列。 - “看不懂的乱码是 Telnet 坏了。” 不一定。多半是对端是二进制协议(MySQL/SSH/TLS),被 NVT 当 ASCII 解释;或没协商 BINARY 就传了中文。
- “NEW-ENVIRON、AUTHENTICATION、ENCRYPTION 选项很常用。” 错。认证与加密扩展实现率极低且有历史漏洞;真正常见的是 ECHO/SGA/TERMINAL-TYPE/NAWS/LINEMODE。
速记口诀
- 无头纯流 IAC 引:Telnet 没有固定头部,是纯字节流,看到
0xFF(IAC) 后面就是命令。 - WILL 说我 DO 说你:
WILL/WON'T讲自己要不要做,DO/DON'T要求对方做;配对WILL↔DO/DON'T、DO↔WILL/WON'T。 - 明文二字定生死:口令、内容全明文、无服务端认证、无完整性——这就是被 SSH 取代的全部理由。
- CR LF 换行 CR NUL 单回车:NVT 换行约定是经典互操作坑,记住这两对。
知识框架
| |