谷谷小师妹 发表于 2023-11-22 10:37:37

技术前瞻|mqtt攻击面和挖掘思路浅析

技术前瞻|mqtt攻击面和挖掘思路浅析
在很多IOT设备中默认存在MQTT服务,这是一个值得关注的攻击面。下面对MQTT协议及其挖掘思路进行分析。
|WHAT?MQTT是基于TCP/IP协议栈构建的异步通信消息协议,是一种轻量级的发布、订阅信息传输协议。可在不可靠的网络环境中进行扩展,适用于设备硬件存储空间或网络带宽有限的场景。使用MQTT协议,消息发送者与接收者不受时间和空间的限制。物联网平台支持设备使用MQTT协议接入。
|实现方式实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:(1)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);(2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。|开源实现项目 Mosquitto大部分设备中的MQTT协议实现是基于Mosquitto 改的,如果开源项目本身存在漏洞的话,将影响大部分设备的MQTT组件。下面简单分析下Mosquitto的实现和我们能够接触到的攻击面基本启动方式流程模块:websocket.c -> read_handle.c在switch case LWS_CALLBACK_RECEIVE:认证部分security.c在handle_connect和authentic相关的时候,会执行mosquitto_security_auth_start,通过返回值rc确定数据流其中mosquitto_security_auth_start 调用auth_start_v4相关由此可知这个方法恒定返回MOSQ_ERR_SUCCESS,之后会调用到connect__on_authorised,里面的connection_check_acl再调用mosquitto_acl_check函数对权限进行检测之后的mosquitto_acl_check函数会去配置信息,再根据策略去执行相关的认证函数。网络net处理数据包net__socket_accept()函数mux_epoll__handle()函数最终来自main函数调用的mosquitto_main_loop回到生成sock本身,最后调用的是epoll_ctrl(epoll库)来生成数据接收处理的地方在callback_mqtt() 中:case LWS_CALLBACK_RECEIVEcase 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++;
   /* 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++;

      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, &buf, 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, &buf, 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;
数据赋值的地方可惜定死了mosq->in_packet.to_process|攻击面比较明显的攻击面就是Mosquitto本身和自定义的topic。未授权的情况下,组件本身的漏洞产生的点可能来自于数据流的处理,认证相关,如果存在弱密码或者配置文件认证信息写死的情况下,还可以关注topic的攻击面。MQTT主要有两大版本:v3 和 v5,MQTT v3 不支持 auth,所以遇到这种版本的相当于可以直接看topic的攻击面Mosquitto本身下面介绍一些Mosquitto的历史漏洞(1)CVE-2021-34434NULL point漏洞,该漏洞是授权后的一个数据请求导致的。Client在连接之后可以在数据包中指定context->in_packet.command,handle__packet函数会根据命令执行相应的函数,如果是CMD_CONNACK命令,将会步入handle__connack函数中。下图是patch后的代码,可以看出添加了对NULL的检查,如果没有检查的情况下,之后调用context->bridge->name会异常崩溃。(2)CVE-2017-7650漏洞函数是acl__check_single,由mosquitto_acl_check函数引用,前面提到,mosquitto_acl_check是认证权限相关的函数。当username 带有+#,可以绕过认证check。下图是patch后的代码,添加了对+#的检查Broker 创建alc 文件可以指定权限配置user admin
topic readwrite #

user user
topic /iot/user/+
漏洞产生的原因和这个样式有关。(3)CVE-2017-7651mosquitto__calloc()这类型的的函数是自定义的内存分配函数,patch的地方就是限制了size,如果在没有限制size的情况,通过大量发包,可能导致资源占用过大dos这个内存分配函数在前期接收数据包并生成mosquitto context的时候会调用,所以这是一个未授权就可以触发的漏洞。下图是patch后的代码,可以看到添加了大小限制。
自定义的topicMosquito本身的订阅和发布方式//订阅
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实现的小例子|总结本文介绍了MQTT协议和MQTT协议的开源实现Mosquitto,很多设备的mqtt组件都是基于Mosquitto改的,当然还有些是自定义实现的。不管是哪种,我们去挖掘其攻击面无非还是挖掘组件本身和路由(topic),挖掘的过程可以借鉴Mosquitto的代码和它的框架类型,快速地找到相应的逻辑代码。在实际挖掘过程中发现有些设备的mqtt组件存在协议版本过久和一些配置文件写死的情况,在这种情况下topic的攻击面将大大增加。代码审计万变不离其宗:字符串大法好!映射函数有可能在so中,也可能在别的service bin中(根据改写的方式而定),所以如果在单个bin中找不到相应的逻辑,需要在大目录下搜索相关字符串,找到映射路由topic/function ,就可以对应审计了。比如:
[*]找到一个可确定的topic
[*]定位1883的bin
[*]找引用,比如so库或者system调用后可能申请了子进程
[*]自顶向下和自底向上减少搜索路径
[*]最后确认路由 ......
Fuzz思路相通,和http fuzz差不多,还是需要通过逆向找到相应的topic,然后和http fuzz一样怼着topic去发送变异数据包。

页: [1]
查看完整版本: 技术前瞻|mqtt攻击面和挖掘思路浅析