5

mqtt 攻击面和挖掘思路浅析

 1 year ago
source link: https://paper.seebug.org/2040/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者: 360漏洞研究院 苏熙杰
原文链接:https://vul.360.net/archives/649

在很多IOT设备中默认存在MQTT服务,这是一个值得关注的攻击面。下面对MQTT协议及其挖掘思路进行分析。

WHAT?

MQTT是基于TCP/IP协议栈构建的异步通信消息协议,是一种轻量级的发布、订阅信息传输协议。 可在不可靠的网络环境中进行扩展,适用于设备硬件存储空间或网络带宽有限的场景。 使用MQTT协议,消息发送者与接收者不受时间和空间的限制。 物联网平台支持设备使用MQTT协议接入。

img

实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:

(1)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);

(2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。

开源实现项目 Mosquitto

大部分设备中的MQTT协议实现是基于Mosquitto 改的,如果开源项目本身存在漏洞的话,将影响大部分设备的MQTT组件。下面简单分析下Mosquitto的实现和我们能够接触到的攻击面

基本启动方式

img
img
img

流程模块:websocket.c -> read_handle.c

在switch case LWS_CALLBACK_RECEIVE:

img
img

认证部分security.c

在handle_connect和authentic相关的时候,会执行mosquitto_security_auth_start,通过返回值rc确定数据流

img

其中mosquitto_security_auth_start 调用auth_start_v4相关

img
img
img

由此可知这个方法恒定返回MOSQ_ERR_SUCCESS,之后会调用到connect__on_authorised,里面的connection_check_acl再调用mosquitto_acl_check函数对权限进行检测

img

之后的mosquitto_acl_check函数会去配置信息,再根据策略去执行相关的认证函数。

网络net处理数据包

net__socket_accept()函数

img

mux_epoll__handle()函数

img
img

最终来自main函数调用的mosquitto_main_loop

img

回到生成sock本身,最后调用的是epoll_ctrl(epoll库)来生成

img

数据接收处理的地方

在callback_mqtt() 中: case LWS_CALLBACK_RECEIVE

case LWS_CALLBACK_RECEIVE:
   if(!u || !u->mosq){
    return -1;
   }
   mosq = u->mosq;
   pos = 0;
   buf = (uint8_t *)in;
   G_BYTES_RECEIVED_INC(len);
   while(pos < len){
    if(!mosq->in_packet.command){
     mosq->in_packet.command = buf[pos];
     pos++;
     /* Clients must send CONNECT as their first command. */
     if(mosq->state == mosq_cs_new && (mosq->in_packet.command&0xF0) != CMD_CONNECT){
      return -1;
     }
    }
    if(mosq->in_packet.remaining_count <= 0){
     do{
      if(pos == len){
       return 0;
      }
      byte = buf[pos];
      pos++;

      mosq->in_packet.remaining_count--;
      /* Max 4 bytes length for remaining length as defined by protocol.
      * Anything more likely means a broken/malicious client.
      */
      if(mosq->in_packet.remaining_count < -4){
       return -1;
      }

      mosq->in_packet.remaining_length += (byte & 127) * mosq->in_packet.remaining_mult;
      mosq->in_packet.remaining_mult *= 128;
     }while((byte & 128) != 0);
     mosq->in_packet.remaining_count = (int8_t)(mosq->in_packet.remaining_count * -1);

     if(mosq->in_packet.remaining_length > 0){
      mosq->in_packet.payload = mosquitto__malloc(mosq->in_packet.remaining_length*sizeof(uint8_t));
      if(!mosq->in_packet.payload){
       return -1;
      }
      mosq->in_packet.to_process = mosq->in_packet.remaining_length;
     }
    }
    if(mosq->in_packet.to_process>0){
     if((uint32_t)len - pos >= mosq->in_packet.to_process){
      memcpy(&mosq->in_packet.payload[mosq->in_packet.pos], &buf[pos], mosq->in_packet.to_process);
      mosq->in_packet.pos += mosq->in_packet.to_process;
      pos += mosq->in_packet.to_process;
      mosq->in_packet.to_process = 0;
     }else{
      memcpy(&mosq->in_packet.payload[mosq->in_packet.pos], &buf[pos], len-pos);
      mosq->in_packet.pos += (uint32_t)(len-pos);
      mosq->in_packet.to_process -= (uint32_t)(len-pos);
      return 0;
     }
    }
    /* All data for this packet is read. */
    mosq->in_packet.pos = 0;

#ifdef WITH_SYS_TREE
    G_MSGS_RECEIVED_INC(1);
    if(((mosq->in_packet.command)&0xF0) == CMD_PUBLISH){
     G_PUB_MSGS_RECEIVED_INC(1);
    }
#endif
    rc = handle__packet(mosq);

    /* Free data and reset values */
    packet__cleanup(&mosq->in_packet);

    keepalive__update(mosq);

    if(rc && (mosq->out_packet || mosq->current_out_packet)) {
     if(mosq->state != mosq_cs_disconnecting){
      mosquitto__set_state(mosq, mosq_cs_disconnect_ws);
     }
     lws_callback_on_writable(mosq->wsi);
    } else if (rc) {
     do_disconnect(mosq, MOSQ_ERR_CONN_LOST);
     return -1;
    }
   }
   break;

数据赋值的地方

img

可惜定死了mosq->in_packet.to_process

img

比较明显的攻击面就是Mosquitto本身和自定义的topic。未授权的情况下,组件本身的漏洞产生的点可能来自于数据流的处理,认证相关,如果存在弱密码或者配置文件认证信息写死的情况下,还可以关注topic的攻击面。

MQTT主要有两大版本: v3 和 v5,

MQTT v3 不支持 auth,所以遇到这种版本的相当于可以直接看topic的攻击面

Mosquitto本身

下面介绍一些Mosquitto的历史漏洞

CVE-2021-34434

NULL point漏洞,该漏洞是授权后的一个数据请求导致的。Client在连接之后可以在数据包中指定context->in_packet.command,handlepacket函数会根据命令执行相应的函数,如果是CMD_CONNACK命令,将会步入handleconnack函数中。下图是patch后的代码,可以看出添加了对NULL的检查,如果没有检查的情况下,之后调用context->bridge->name会异常崩溃。

img

CVE-2017-7650

漏洞函数是acl__check_single,由mosquitto_acl_check函数引用,前面提到,mosquitto_acl_check是认证权限相关的函数。当username 带有+#,可以绕过认证check。下图是patch后的代码,添加了对+#的检查

img

Broker 创建alc 文件可以指定权限配置

user admin
topic readwrite #

user user
topic /iot/user/+

漏洞产生的原因和这个样式有关。

CVE-2017-7651

mosquitto__calloc()这类型的的函数是自定义的内存分配函数,patch的地方就是限制了size,如果在没有限制size的情况,通过大量发包,可能导致资源占用过大dos

这个内存分配函数在前期接收数据包并生成mosquitto context的时候会调用,所以这是一个未授权就可以触发的漏洞。下图是patch后的代码,可以看到添加了大小限制。

img

自定义的topic

Mosquito本身的订阅和发布方式

//订阅
mosquitto_sub -h 127.0.0.1 -p 1883 -u admin -P 123 -t "#"

//发布
mosquitto_pub -h 127.0.0.1 -p 1883 -u admin -P 123 -t "/iot/user/pub/" -m "message_to_publish"

把mqtt问题转变成http问题:

Topic = url

Payload = data

Sub <--------> broker (1883)<--------> pub

为了更好的理解sub和pub的关系及其topic的攻击面,可以阅读下面的例子。

在发布者像broker发布(pub)消息的时候,订阅者会接收(sub)到消息并执行回调(callback)函数。所以攻击者就相当于发布者的身份,被攻击的目标就是订阅者的身份,所以自定义topic的攻击面就是订阅者的回调函数。通过到了相应的路由,那么漏洞类型和http相关的类型差不多,可能涉及到逻辑漏洞、命令注入、溢出等。

Python实现的小例子
img
img
img

本文介绍了MQTT协议和MQTT协议的开源实现Mosquitto,很多设备的mqtt组件都是基于Mosquitto改的,当然还有些是自定义实现的。不管是哪种,我们去挖掘其攻击面无非还是挖掘组件本身和路由(topic),挖掘的过程可以借鉴Mosquitto的代码和它的框架类型,快速地找到相应的逻辑代码。在实际挖掘过程中发现有些设备的mqtt组件存在协议版本过久和一些配置文件写死的情况,在这种情况下topic的攻击面将大大增加。

万变不离其宗:字符串大法好!映射函数有可能在so中,也可能在别的service bin中(根据改写的方式而定),所以如果在单个bin中找不到相应的逻辑,需要在大目录下搜索相关字符串,找到映射路由topic/function ,就可以对应审计了。

  1. 找到一个可确定的topic
  2. 定位1883的bin
  3. 找引用,比如so库或者system调用后可能申请了子进程
  4. 自顶向下和自底向上减少搜索路径
  5. 最后确认路由 ……

思路相通,和http fuzz差不多,还是需要通过逆向找到相应的topic,然后和http fuzz一样怼着topic去发送变异数据包。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/2040/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK