Telnet 原理与报文 — NVT、IAC 命令与选项协商

Telnet 的网络虚拟终端模型、IAC 转义命令码、DO/DON'T/WILL/WON'T 协商与子协商 / 应用层 / TCP 23 / RFC 854

先建立直觉

Telnet 可以想象成一台"对讲机":你说的话你发出的指挥动作都走同一条无线电波,而且谁都能拿个收音机在旁边听个一字不漏——这就是它"明文"和"带内控制"的本质。为了不让指挥动作(比如"我要改回显规则了")被误当成普通说话内容,Telnet 规定一个特殊代号:凡是看到 0xFF 这个字节,后面跟的就不再是文字,而是"命令"。有了这条规则,一条纯字节流上就能同时跑"聊天内容"和"终端控制指令",而不需要开两条连接。

它解决什么问题

Telnet 诞生于终端机型五花八门的年代:这台机器回车是 CR,那台是 LF,还有机器退格键发的是 DEL。如果让每台客户端去适配所有远端终端,组合爆炸。Telnet 的解法见 1. 工作原理,核心是用一个"标准虚拟终端"做中转,再用"按需协商"叠加高级能力。

工作流程(简化版)

  1. 客户端与服务端先完成 TCP 三次握手(端口 23)。
  2. 双方用 IAC 命令互相"商量"能力:要不要远端回显、要不要传窗口大小、传什么终端类型。
  3. 协商出字符模式后,你每敲一个键,Telnet 就发一个字节过去,由服务端回显。
  4. 登录与后续所有命令、输出都以明文字节流传输。
  5. 想中断就发 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 与其他文本协议互操作时的经典坑。
  • 必需控制码NULLFCRBEL(7)、BS(8)、HT(9)、VT(11)、FF(12)。

1.2 协商原则:“商定的选项”

RFC 854 的设计哲学:协议核心保持最小,能力通过选项按需叠加,且遵守两条防死循环规则:

  1. 只有在改变状态时才发送请求;已处于目标状态时收到请求不再回应。
  2. 收到请求必须回应(同意或拒绝),但同意后不得再重复请求。

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)

十进制十六进制名称含义
2550xFFIAC命令解释引导符;数据中的 0xFF 需写成 IAC IAC
2540xFEDON’T要求对方停止使用某选项
2530xFDDO要求对方启用某选项
2520xFCWON’T声明自己不会使用某选项
2510xFBWILL声明自己将要使用某选项
2500xFASB子协商开始(Subnegotiation Begin)
2490xF9GAGo Ahead,半双工下交出发送权
2480xF8ELErase Line,擦除当前行
2470xF7ECErase Character,擦除前一字符
2460xF6AYTAre You There,探活(服务端通常回一行提示)
2450xF5AOAbort Output,丢弃尚未送达的输出
2440xF4IPInterrupt Process,中断进程(对应 Ctrl+C)
2430xF3BRKBreak,映射到终端 BREAK 键
2420xF2DMData Mark,Synch 的同步标记,配合 TCP 紧急指针
2410xF1NOP无操作
2400xF0SE子协商结束(Subnegotiation End)

2.2 命令格式

形式字节序列说明
双字节命令IAC <cmd>FF F4 = 中断进程
三字节协商IAC <WILL/WONT/DO/DONT> <option>FF FD 18 = DO TERMINAL-TYPE
子协商IAC SB <option> <参数...> IAC SEFF FA 18 00 78 74 65 72 6D FF F0 = 终端类型 “xterm”
数据中的 0xFFIAC IACFF FF转义,表示一个字面量 255

2.3 常用选项码(RFC 855 及后续)

选项码名称RFC作用
0BINARYRFC 8568 位二进制传输(关闭 NVT 7 位限制)
1ECHORFC 857谁负责回显。服务端 WILL ECHO = 远端回显
3SUPPRESS-GO-AHEAD (SGA)RFC 858抑制 GA 信号,进入全双工字符模式
5STATUSRFC 859查询选项状态
6TIMING-MARKRFC 860同步标记
24TERMINAL-TYPERFC 1091子协商传递 xterm / vt100 / ansi
31NAWS (Negotiate About Window Size)RFC 1073子协商传递列数/行数(16 位各一)
32TERMINAL-SPEEDRFC 1079终端速率
33TOGGLE-FLOW-CONTROLRFC 1372远端流控开关
34LINEMODERFC 1184行模式,客户端本地行编辑
35X-DISPLAY-LOCATIONRFC 1096X 显示位置
36 / 39ENVIRON / NEW-ENVIRONRFC 1408 / 1572传递环境变量(曾被用于 USER 免认证攻击)
37AUTHENTICATIONRFC 2941认证扩展(Kerberos/SRP),实现率极低
38ENCRYPTIONRFC 2946加密扩展,实现率极低且历史漏洞多

2.4 NAWS 子协商示例(窗口 80×24)

1
2
3
4
5
6
7
FF FA 1F 00 50 00 18 FF F0
│ │ │ └─┬─┘ └─┬─┘ │ └── SE 子协商结束
│ │ │ │ └───────── 高16位行数 = 0x0018 = 24
│ │ │ └─────────────── 高16位列数 = 0x0050 = 80
│ │ └──────────────────── 选项 31 (0x1F) = NAWS
│ └─────────────────────── SB 子协商开始
└────────────────────────── IAC

交互时序

一句话看懂这张图:从 TCP 建连 → 选项协商进入字符模式 → 明文登录(连口令都是明文)→ 逐字符交互 → 用 IAC IP + TCP 紧急指针中断 → 退出,全过程没有任何加密。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
sequenceDiagram
    autonumber
    participant C as 客户端 (telnet)
    participant S as 服务端 (telnetd:23)

    Note over C,S: ① TCP 建连
    C->>S: TCP 三次握手 (23)

    Note over C,S: ② 选项协商(进入字符模式)
    S->>C: IAC DO TERMINAL-TYPE (FF FD 18)
    C->>S: IAC WILL TERMINAL-TYPE (FF FB 18)
    S->>C: IAC SB TERMINAL-TYPE SEND IAC SE
    C->>S: IAC SB TERMINAL-TYPE IS "xterm" IAC SE
    S->>C: IAC DO NAWS (FF FD 1F)
    C->>S: IAC WILL NAWS + IAC SB NAWS 80x24 IAC SE
    S->>C: IAC WILL SUPPRESS-GO-AHEAD (FF FB 03)
    C->>S: IAC DO SUPPRESS-GO-AHEAD (FF FD 03)
    S->>C: IAC WILL ECHO (FF FB 01)
    C->>S: IAC DO ECHO (FF FD 01)
    Note right of C: 此后:客户端不本地回显<br/>逐字符发送,由服务端回显

    Note over C,S: ③ 登录(全部明文!)
    S->>C: "Ubuntu 22.04 LTS\r\nlogin: "
    C->>S: 'a' (1 字节)
    S->>C: 'a' (回显)
    C->>S: 'l' → 回显 'l' … 逐字符
    C->>S: CR LF
    S->>C: "Password: "
    Note right of S: 服务端此时发 IAC WON'T ECHO<br/>使客户端不回显口令(仅本地不显示)
    C->>S: 'p','a','s','s' … (**明文上网,抓包可读**)
    C->>S: CR LF
    S->>C: "Last login: ...\r\n$ "

    Note over C,S: ④ 交互会话
    loop 每个按键
        C->>S: 单字符
        S->>C: 回显 + 命令输出
    end

    Note over C,S: ⑤ 中断(Ctrl+C)
    C->>S: IAC IP (FF F4)
    C->>S: IAC DM (FF F2) + TCP URG 紧急指针
    S->>C: 丢弃缓冲输出,回到提示符

    Note over C,S: ⑥ 结束
    C->>S: "exit" CR LF
    S->>C: "logout" + TCP FIN

关键机制 / 变体

4.1 协商防死循环规则(RFC 854 的精髓)

这是干什么的:防止双方就同一个选项无限来回确认,把连接变成"乒乓死循环"。

若无约束,A 发 WILL X、B 回 DO X、A 又回 WILL X……会无限循环。RFC 854 规定:

  1. 只在状态改变时发请求:若已启用某选项,不再重复发 WILL
  2. 收到请求必须应答DOWILL/WON'TWILLDO/DON'T
  3. 应答不触发新请求:收到的是对自己请求的应答时,不再回应。
  4. 拒绝方向不可逆:一方 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 的解法:

  1. 客户端发 IAC IP(中断进程);
  2. 紧接着发 IAC DM,并把 TCP 紧急指针(URG) 指向 DM 字节;
  3. 服务端 TCP 收到紧急数据后进入"紧急模式",丢弃数据流直到 DM,只处理其中的 Telnet 命令;
  4. 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 认证方法

常见误区

  1. “输入口令时屏幕不显示,说明口令被加密了。” 错。那只是服务端发了 IAC WON'T ECHO 让客户端停止本地回显,是显示层面的事;口令字节在网络上仍是明文,抓包直接可读。
  2. “telnet 能连上 = 服务健康。” 错。能连上只证明 TCP 三次握手成功(网络通 + 端口有监听 + 没被拦);应用层服务本身可能已挂。
  3. “Telnet 传的是带格式的报文。” 错。Telnet 没有固定头部,是纯字节流,控制靠 IAC 转义;所谓"报文"是 IAC 命令字节序列。
  4. “看不懂的乱码是 Telnet 坏了。” 不一定。多半是对端是二进制协议(MySQL/SSH/TLS),被 NVT 当 ASCII 解释;或没协商 BINARY 就传了中文。
  5. “NEW-ENVIRON、AUTHENTICATION、ENCRYPTION 选项很常用。” 错。认证与加密扩展实现率极低且有历史漏洞;真正常见的是 ECHO/SGA/TERMINAL-TYPE/NAWS/LINEMODE。

速记口诀

  1. 无头纯流 IAC 引:Telnet 没有固定头部,是纯字节流,看到 0xFF(IAC) 后面就是命令。
  2. WILL 说我 DO 说你WILL/WON'T 讲自己要不要做,DO/DON'T 要求对方做;配对 WILL↔DO/DON'TDO↔WILL/WON'T
  3. 明文二字定生死:口令、内容全明文、无服务端认证、无完整性——这就是被 SSH 取代的全部理由。
  4. CR LF 换行 CR NUL 单回车:NVT 换行约定是经典互操作坑,记住这两对。

知识框架

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
mindmap
  root((Telnet 远程终端协议))
    基础模型
      NVT 网络虚拟终端
        7位ASCII
        CR LF 换行
        CR NUL 单回车
        打印机抽象
      对称协议
      带内控制 IAC 转义
    命令体系
      IAC 255
      WILL 251 WONT 252
      DO 253 DONT 254
      SB 250 SE 240
      IP 244 中断
      AO 245 AYT 246
      EC 247 EL 248
      DM 242 Synch
      GA 249
    常用选项
      0 BINARY
      1 ECHO
      3 SUPPRESS-GO-AHEAD
      24 TERMINAL-TYPE
      31 NAWS 窗口大小
      34 LINEMODE
      39 NEW-ENVIRON
      37/38 认证与加密 罕见
    运行模式
      半双工行模式 GA
      字符模式 ECHO+SGA
      LINEMODE 行编辑
      本地回显模式
    协商规则
      仅状态改变时请求
      收到请求必应答
      应答不再触发请求
      拒绝方向不可逆
    安全问题
      口令明文
      内容明文
      无服务端认证
      无完整性保护
      会话劫持
      telnetd 历史漏洞
      应改用 SSH
    现代用途
      端口连通性探测
      手工调试 HTTP/SMTP/POP3
      老旧设备带内管理
      MUD 文本游戏