针对BACnet MSTP的帧失步攻击

如前面的文章 “BACnet MSTP 帧失步” 所指, BACnet MSTP 有一个帧失步的设计缺陷,但是是否可以利用这个缺陷,在完全遵守协议的前提下,对MSTP总线进行破坏呢?

设计这个攻击,我们先做以下几个假设:

  1. 总线上至少有3个设备,MAC地址分别为1, 8, 10。其中设备1是精心设计用来发动攻击的,设备8与10是无辜的。
  2. 设备1支持扩展帧,设备8与10不支持。
  3. 这3个设备的定时器都足够精确。

设备1的工作流程如下:

  1. 得到令牌,发送A帧
  2. 传递令牌到其它设备
  3. 再次得到令牌时,发送B帧
  4. 传递令牌到其它设备
  5. 重复步骤1.

A帧是一个合法的私有数据帧,十六进制数据如下:

55 ff 80 ff 01 00 1d a3 02 2b 72 fe 55 ff 03 08 01 00 11 a0 ff 55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf

B帧也是一个合法的私有数据帧,十六进制数据如下:

55 ff 80 ff 01 00 1d a3 02 2b fe dc 55 ff 03 0a 01 00 11 b1 ff 55 ff 21 01 0a 00 09 fd 8a 51 55 ff 00 01 0a 00 00 8c

如果没有帧失步,一切都将正常运行。但是可能几小时,也可能几天后,设备8对设备1发出的A帧失步了,错过了A帧的帧头(设备10如对B帧失步,也是同样的效果),则设备8继续扫描A帧的数据部分,发现另一个有效帧:

55 ff 03 08 01 00 11 a0 ff 55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf

这是发给设备8的Test-Request帧,设备8等待Tturnaround后发送Test-Response帧进行应答:

55 ff 04 01 08 00 11 ae ff 55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf

但是此时,设备1正在传出令牌:

55 ff 00 02 01 00 00 73

令牌帧与Test_Response的前8个字节冲突了,对于设备10来说,收到了几个错误字节后,继续扫描,在Test_Response的数据部分又发现了一个帧:

55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf

对设备10来说,这不是发给它的帧,所以他进入SKIP-DATA状态,抛弃数据,等这个帧结束,但是直到设备8发完数据,设备10还差一个字节来结束帧,他将继续等待。

对设备1来说,它发出令牌帧后,收到如下数据:

55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf

这是一个扩展数据帧,因为它支持扩展帧,所以他按 Addendum 135-2012an规定的流程校验帧头,发现数据长度过短,中断前帧后又开始扫描,发现新帧:

55 ff 00 01 08 00 00 bf

这是一个发给设备1的令牌帧,设备1又得到令牌,经过Tturnaround后,重新发送B帧:

55 ff 80 ff 01 00 1d a3 02 2b fe dc 55 ff 03 0a 01 00 11 b1 ff 55 ff 21 01 0a 00 09 fd 8a 51 55 ff 00 01 0a 00 00 8c

在前面提到,设备10还差1个字节来结束前面一帧,因Tframe_abort>Ttrurnaround,所以解析没有中断,B帧的第一个55字节被设备10抛弃,然后开始扫描新帧,发现了:

55 ff 03 0a 01 00 11 b1 ff 55 ff 21 01 0a 00 09 fd 8a 51 55 ff 00 01 0a 00 00 8c

这是一个发给设备10的Test-Request帧,事情又开始重复。

从上面可以看出,每个设备都严格地遵守标准,但是一旦帧失步发生,整条总线就永远地瘫痪了。

更多信息见:MSTP帧失步解决方案

MSTP帧失步解决方案

因为固件版本3.x,于2020.3.25更新

我们曾在下面的文章中讨论过BACnet MSTP协议中有帧失步的弱点:

BACnet MSTP 帧失步

针对 BACnet MSTP 的帧失步攻击

对于BACRouter 来说,怎么来防范这个漏洞呢?让我们从标准的 9.5.3章节找线索:

Tframe_gap 翻译为字节间隔,指的是 “节点在发送一个帧时,在两个字节之间允许的最长的空闲时间”,它的值是20位时间。市面大部分MSTP设备的字节间隔为0。

所以BACRouter采用一个改进的接收有限状态机:

  1. 在一个MSTP帧中,字节间隔如大于Tframe_gap,认为MSTP帧中断。
  2. 字节间隔如大于帧间隔Tturnaround,认为新的MSTP帧出现。考虑到字节帧失步引起的测量误差,我们实际采用的值是30.5位时间。
  3. 为了尽量兼容部分不遵守Tturnaround的设备,所有在有效MSTP帧后的数据认为是新的一帧。

在115200波特率下,一个数据位的时间仅8.7微秒,为了精确地测量空闲时间,BACRouter采用了5微秒精度的定时器,它有效地防止帧失步出现,并且 在115.2kbps下达到98.8%的带宽利用率 因为BACRouter发包时精确地遵守40位的Tturnaround,没有浪费多余的等待时间。

BACnet MSTP 帧失步

在MSTP领域有2个帧的概念:

  1. BACnet MSTP数据链路层的帧,它由最少8个字节组成,包括:2个前导字节为0x55, 0xff,帧类型,目标MAC地址,源MAC地址,2个字节的数据长度,crc8校验,如果数据长度不为零,还包括数据与数据较验。这里把这种帧称为MSTP帧。
  2. EIA-485帧,它由位组成,包括一个起始位,数据位,较验位,停止位。MSTP采用NRZ不归零编码,8个数据位,无较验位,一个停止位。起始位为0低电平,停止位为1高电平,数据位低位优先传输。这里把这种帧称为“字节帧”。

BACnet MSTP的接收有限状态机采用前导字节分辨MSTP帧的起始。如果在中途超过Tframe_abourt(最小60位时间,最大可达100毫秒)没有收到数据或错误,则放弃帧,重新开始搜寻帧前导字节。

因为前导字节可以出现在MSTP帧的数据部分(Addendum 135-2012an 引入的扩展帧的数据及数据较验部分采用COBS编码避免出现前导字节)所以如果出现帧失步,接收状态机有可能会把前一个MSTP帧的数据部分认为是新的MSTP帧。

MSTP帧之间最小时间间隔是Tturnaround(40位时间)小于Tframe_abort,如果接收状态机对MSTP帧的解析失步,有可能会跨过帧间隔继续解析。

MSTP帧失频有几个可能原因:

  1. 发送设备与接收设备的程序漏洞,这可以通过代码审查与除错解决。
  2. 时间精度. BACnet MSTP标准仅要求1%精度的定时器,分辨率最小5毫秒。而应答方的最大延迟时间与等待方的最小超时之间的时间冗余只有5毫秒(如Tusage_delay与Tusage_timeout, Treply_delay与Treply_timeout),因此非常容易触发冲突引起失步。
  3. 总线上噪声。噪声导致的接收错误或数据校验错引起失步。

有人可以争辨说MSTP帧有crc校验保护。即使不考虑恶意设备(这里是一个例子), 现实中仍然有可能在帧数据中包含完整的MSTP帧。

例如,市场上很多串口转IP的设备,它们通常有一个或多个RS232/RS485串口及一个有线或无线网口,可以通过TCP/UDP远程在串口上接收、发送裸包。串口的协议可能是Modbus或其它非特定协议。这种设备对简单的协议集成非常有用。

那么是否可以把这类设备的网络端协议封装为BACnetPrivateTransfer服务,然后通过BACnet网络传输呢。如果用户把这个设备串口端RS485接到一个MSTP总线上,并且网络端传输又经过一个MSTP网络时,结果就是出现在这个MSTP网络的传输帧的数据部分包括了完整的MSTP帧。

帧失步的后果不仅仅是可能导致总线拥堵,甚至设备的误动作(如果数据部分的帧是个APDU),更有甚者如果错误的帧是路由配置包的话,可能导致整个BACnet互联网络瘫痪。


关于字节帧的失步,可能原因一般是:数据噪声,不当的终端电阻,总线未偏置引起。失步的表现有两个,一是将数据位的1解析为总线的空闲,二是将数据位的0解析为新字节的起始位。失步除了导致接收到错误的数据,如果发生在MSTP帧的最后一个字节,将可能引起对总线空闲时间的测量误差(可能过长或过短)。

更多信息见:MSTP帧失步解决方案

一次最大发包数与根据令牌占用时间

从固件版本2.0开始,BACRouter引入一个新的特性:根据令牌占用时间的一次最大发包数。

在BACnet MSTP标准中,一个主站得到令牌后,可以发送“一次最大发包数”的包后,再传递出令牌。“一次最大发包数”的默认值是1。路由器作为流量汇聚点,提高这个值可以改进网络交换带宽,但是会增加令牌占用时间。大多数路由的建议值为5到20。

MSTP作为常见的现场总线,通常由控制器、传感器、执行器互联,这些设备构成直接的控制回路,数据交换延迟通常要得到保证。我们建议设备得到令牌的时间间隔要小于1秒 。

路由器发送的NPDU的长度通常在10~50字节之间。但是最大可达到501字节(或扩展帧的1497字节)。越大的帧需要越长的时间来收发。

对于需要回应的NPDU来说,路由器需要等待目标设备应答。通常目标设备需要更长的时间来处理长帧,即路由器需要等待更长的时间。

所以同样的“一次最大发包数”,每次路由持用令牌的时间变化很大,对MSTP总线的延迟保证非常不利。

为避免这个问题,我们引进“根据令牌占用时间”特性来限制路由器持有令牌的时间。这个特性启用后。路由器不计算发包数,而是计量持有令牌的时间,当时间达到:

每字节发送时长 * 32 * “一次最大发包数”

就不再发送新帧,并传出令牌。例如“一次最大发包数”为10, 波特率为76.8kbps,每字节发送时长为0.13毫秒,则最大令牌持有时间为:

0.13 * 32 * 10 = 41.6 毫秒.

这个特性可以通过WebUI方便地启用与关闭。

MSTP包延时保证

BACnet有两种类型的服务,分为无确认与有确认。有确认服务的发送者(客户端)会等待应答直至超时。

通常情况下,无确认服务的包延时不会带来副作用。但对于有确认服务的请求包与应答包,过长的包延时将导致应答包因为超时而被抛弃,浪费了通讯带宽。

更有甚者,过迟到达的应答包可能导致应用层的逻辑错误!原因如下:

有确认服务的请求包头带有一个InvokeID,值范围0~255。应答包有同样的InvokeID。客户端通过这个InvokeID匹配请求与应答。在一个繁忙的客户端,InvokeID会快速耗尽, 此时只能回收重用已完成的服务的InvokeID。

如果一个有确认服务的应答包被过分延迟,客户端可能因为超时而结束服务,其InvokeID被回收,并被重新分配使用。此时被延迟的应答包被收到后,其InvokeID将被匹配到错误的服务。例如:

  • 客户端发出一个WriteProperty请求A,写设备X对象Y属性Z,分配的InvokeID为0。写入成功,但此请求包或者应答包延迟了。
  • 客户端等待超时而结束服务,InvokeID 0被回收。
  • 客户端发出一个WriteProperty请求B,写设备X对象U属性V,InvokeID又分配到0。写入失败。
  • 请求A的应答包先到达,由其InvokeID匹配到请求B。客户端认为请求B写入成功,造成应用层逻辑错误。

对高速的链路层如以太网或IP, 包延时通常是可忽略的,但是对于MSTP,有很多原因将造成过长的包延时:

  1. 信号噪声造成的令牌丢失或冲突。
  2. 不适当的设备配置(波特率,最大扫描站号,最大发包数)。
  3. 过高的流量。
  4. 过慢的设备。

为了避免InvokeID冲突及提高网络性能,版本2.0及以上的BACRouter实现了10秒钟的包延时保证,在收到后不能在10秒钟内完全转发的包将被抛弃。

虽然此策略可能造成服务无应答,但是比起错误应答,无应答是可以通过应用层的重试机制处理。