vs调试发现字符串中的字符无效_TPLink Archer路由器发现的漏洞可允许远程管理账户密码...

TP-Link路由器漏洞:远程接管管理员密码
文章揭示了TP-Link Archer路由器的一个关键漏洞,允许未经授权的用户通过不验证的HTTP请求获取管理员权限。此漏洞影响设备的默认设置,可能导致商业网络风险增加。攻击者可以借此进入企业网络,进行侦察和横向移动。文章详细描述了漏洞的发现过程,包括固件分析、调试和利用。TP-Link已发布补丁修复该问题,建议用户尽快更新。

38b915c1ecce04dd1f4b3c4723d58ec9.gif

4329db422eaaeb226c9779bfb4d87ced.png

5fb4149db14ffaa309d6188a84d6e2b3.png

该漏洞被认为是至关重要的,因为它可以授予未经授权的第三方使用管理员特权访问路由器,而这是所有用户在该设备上的默认设置,而无需进行适当的身份验证。在使用此类路由器启用访客Wi-Fi的商业网络上,风险更大。如果将其放置在企业网络上,则受损的路由器可能成为攻击者的切入点,并且成为侦察和横向移动策略的前哨站。

此漏洞的发布遵循TP-Link负责任的披露流程,旨在帮助用户和防御者采取措施来缓解这些设备的风险。

6550200be204dd9d70bdee57bc1ba1e2.png密码溢出漏洞

在深入探究如何发现此漏洞之前,描述此漏洞的简短方法是使用户密码无效的易受攻击的HTTP请求。在各种各样的溢出漏洞中,当将比预期的字符串长度短的字符串作为用户密码发送时,密码值会失真为一些非ASCII字节。

d3d2ed5651c76142555d637fd1106832.png

6550200be204dd9d70bdee57bc1ba1e2.png触发路由器漏洞

2c849b352842744ceaacfe861eb164cc.png

c0718ea0b55d67872bd3ab51eda0b242.png

易受攻击的HTTP POST请求未验证所需的参数

如果该引用从标头中删除,请求将返回一个“禁止访问”响应。

612daae045ba3c8c96d44bc94308667c.png

从请求中删除的引用——HTTP响应返回“403禁止”

此漏洞以相同的方式影响HTTP POST和GET请求,当字符串长度超过允许的字节数时,管理密码无效。

7e533b90c287bc3170def4c5e9888529.png

HTTP GET请求同样会使密码无效

6550200be204dd9d70bdee57bc1ba1e2.png看看固件

此时,看到这些请求使密码无效后,我们想看一下设备的固件。由于固件并不总是在供应商的网站上可用,因此我们不得不从设备的闪存芯片中提取固件。

有了存储芯片的版本号,可以更轻松地在线查找有关它的更多信息,并且我们能够使用芯片夹和二进制文件分析工具BinWalk直接从芯片中提取固件。

6b99d48fadf2a0a701bb8e61ef595fcb.png

使用BinWalk提取的路由器固件

2924bded2e9dcbb3111b3c779939fa39.png

38069d7bbdd7e31a79a75a71b27f24a9.png

对路由器的root访问

通过root级访问,我们现在可以获得对二进制文件的一些控制。我们也有TFTP,所以我们下载了MIPS gdbServer,这是一个用于类Unix系统的控制程序,允许操作员从另一个系统远程调试其他程序。这样,调试器不需要驻留在同一设备上,只需要我们要调试的可执行文件驻留在目标系统上即可。

我们将此工具与IDA(一种多处理器反汇编器和调试器)结合使用,以进行静态和动态分析,因此我们可以找到路由器漏洞的来源,并通过此过程来说明如何在找到该漏洞并触发它。

下图显示了在IDA上查看的已解析HTTP标头的相关部分:

84c7542fead85d21eb487a0054550cad.png

已解析的HTTP标头显示了引用程序的设置

请注意,此处使用函数strncmp来验证带有字符串tplinkwifi.net的Referrer标头。前面还验证了IP地址,附加调试器可以查看这些详细信息,确认这确实是一个可利用的路由器漏洞。

6550200be204dd9d70bdee57bc1ba1e2.png攻击请求

我们的下一步是检查当我们使用不同的字符串长度发送易受攻击的请求时,密码文件会如何处理。首先,我们尝试发送一个较短的字符串,只有几个字节。

dff83fdd537aba126f7953e4139b4e1a.png

使用较短的密码字符串发送HTTP GET请求

该短字符串通过并损坏了密码文件,结果是用户将无法登录,攻击者也将无法登录。此漏洞影响Telnet,FTP和Web服务。

01b8fbad560d4bbc78b08ddbb112552c.png

被用户提交的短字节字符串损坏的密码文件

接下来,我们尝试发送长度超过允许字符数的密码。

36e27ef29fc60ad9b3f229dc86be5e53.png

6550200be204dd9d70bdee57bc1ba1e2.png发送一个很长的密码

这次,密码完全无效,并且该值现在为空。从现在开始,只使用“admin”作为用户名,即可访问TELNET和FTP,而无需输入任何密码,默认情况下,该用户名是设备上唯一可用的用户。

271b19030dba0379d9fc1b75470e6a46.png

管理员密码已失效

6550200be204dd9d70bdee57bc1ba1e2.png其他漏洞

在无需真正身份验证即可获得管理员访问权限后,我们发现该特定设备还允许在WAN上配置FTP。此外,我们可以通过安全的HTTPS连接远程管理路由器,这也容易受到CGI攻击。如果被恶意第三方利用,此路由器漏洞的影响和影响可能是有害的。

57b36161cb33a6e4aaf40ec0638da4e2.png

潜在影响的另一个方面是RSA加密密钥可能会失败,因为它不是设计用于空密码值的。

最重要的是,受害者的设备FTP(如果配置为在WAN上使用)和Telnet(仅LAN)可以完全暴露给攻击者。

6550200be204dd9d70bdee57bc1ba1e2.png易受攻击的路由器比比皆是:如何保护你的数据

最近在TP-Link路由器上的这一关键漏洞只是物联网(IoT)安全领域的冰山一角。如今,几乎每个家庭都使用路由器,但根据美国消费者协会(American Consumer Institute)的一项研究,六分之五的路由器因安全漏洞没有得到充分更新。当这些漏洞出现时,数百万家庭和企业用户将面临数据泄露的风险。不仅仅是文本信息会丢失,想想网络摄像头、婴儿监视器和其他家用连接设备使用同一路由器连接互联网的画面。

50ba9b90d1293fd0700f4713275cd090.png

6550200be204dd9d70bdee57bc1ba1e2.pngTP-Link修补程序

TP-Link已发布以下修补程序,以解决TP-Link Archer C5 v4版本和可能公开的其他版本上的此漏洞,我们建议尽快修补设备。

补丁地址:

Firmware for Archer C5 V4:

https://static.tp-link.com/2019/201909/20190917/Archer_C5v4190815.rar

Archer MR200v4:

https://static.tp-link.com/2019/201909/20190903/Archer%20MR200(EU)_V4_20190730.zip

Archer MR6400v4:

https://static.tp-link.com/2019/201908/20190826/Archer%20MR6400(EU)_V4_20190730.zip

Archer MR400v3:

https://static.tp-link.com/2019/201908/20190826/Archer%20MR400(EU)_V3_20190730.zip

本文翻译自:https://securityintelligence.com/posts/tp-link-archer-router-vulnerability-voids-admin-password-can-allow-remote-takeover/f7f11ea90e9e4eac8494a793b8c2f078.png

652bb52a46766741c9f1584983695396.png

当前python版本2.6.5,运行以上代码总是报错,我现在不能升级python,帮我修改这段,在保持原来意义的情况下,修改语言模式适配版本import requests import re import typing import hashlib import base64 import argparse import sys import tp_link_crypto DEBUG = False USERNAME = "admin" # Hardcoded in the router USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0" AES_KEY = "A" * 16 AES_IV = "B" * 16 def print_d(msg: str) -> None: if DEBUG: print(msg) def is_supported_model(ip_addr: str) -> bool: """ Determines if a router is supported. :param ip_addr: IP address of the router :return: True if supported, otherwise False """ # Tuples of model name and model description supported_models = [ ("Archer C20", "AC750 Wireless Dual Band Router ") # The space at the end of the model desc is intentional ] headers = { "User-Agent": USER_AGENT, "Accept": "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Connection": "keep-alive", "Update-Insecure-Request": "1", } r = requests.post(f"http://{ip_addr}/", headers=headers) print_d(r.text) # Parse the response for the model name and model description match = re.search(r"modelName=\"([a-zA-Z0-9 ]+)\"", r.text) if not match: print("[-] Could not find the router's model name") return False model_name: str = match.group(1) match = re.search(r"modelDesc=\"(.+)\"", r.text) if not match: print("[-] Could not find the router's model description") return False model_desc: str = match.group(1) # Determine if it's in the list of supported models for supported_model in supported_models: if supported_model[0] in model_name and supported_model[1] in model_desc: print(f"[+] Found supported device: {model_name} {model_desc}") return True else: return True print(f"[-] Model name \"{model_name}\" and model description \"{model_desc}\" is not supported") return False def get_rsa_public_key(s: requests.Session, ip_addr: str) -> typing.Union[typing.Tuple[int, int, int], None]: """ Requests the public key and sequence from the router. :param s: The active HTTP session with the router. :param ip_addr: The router's IP address :return: A tuple of RSA e and n values with the sequence number on success, otherwise None """ headers = { "User-Agent": USER_AGENT, "Accept": "*/*", "Origin": f"http://{ip_addr}", "Connection": "keep-alive", "Referer": f"http://{ip_addr}", "Accept-Language": "en-US,en;q=0.5", } data = "[/cgi/getParm#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n" resp = s.post(f"http://{ip_addr}/cgi?8", headers=headers, data=data) # On success, response should look like the following: # # ``` # [cgi]0 # var ee="010001"; # var nn="CB8FD67593B228445BBB882ED34B0787AF19AF3F6BE73793AC64BC64D3C4C41EBD149599F5801848DF92C244749DB07834789060B420979377D24DF7C7E437EB"; # var seq="690699493"; # $.ret=0; # [error]0 # ``` print_d(resp.headers) print_d(resp.text) # Get the RSA public key (i.e. n and e values) match = re.search("nn=\"(.+)\"", resp.text) if not match: print("[-] Could not find RSA n value in get RSA public key response") return None n_bytes = match.group(1) print(f"[+] RSA n: {n_bytes}") match = re.search("ee=\"(.+)\"", resp.text) if not match: print("[-] Could not find RSA e value in get RSA public key response") return None e_bytes = match.group(1) print(f"[+] RSA e: {e_bytes}") # Get the sequence. This is set to sequence += data_len and verified server-side. match = re.search("seq=\"(.+)\"", resp.text) if not match: print("[-] Could not find seq value in get RSA public key response") return None seq_bytes = match.group(1) print(f"[+] Sequence: {seq_bytes}") e = int(e_bytes, 16) n = int(n_bytes, 16) seq = int(seq_bytes, 10) return e, n, seq def authenticate(s: requests.Session, ip_addr: str, password: str) -> bool: """ Authenticates with the TP-Link router. :param s: The active requests session :param ip_addr: The router's IP address :param password: The password to the router's web server :return: True on success, otherwise False """ # Get the RSA public key parameters and the sequence rsa_vals = get_rsa_public_key(s, ip_addr) if rsa_vals is None: print("[-] Failed to get RSA public key and sequence values") return False e, n, seq = rsa_vals # Create the data field aes_key = AES_KEY.encode("utf-8") aes_iv = AES_IV.encode("utf-8") login_data: str = f"8\r\n[/cgi/login#0,0,0,0,0,0#0,0,0,0,0,0]0,2\r\nusername={USERNAME}\r\npassword={password}\r\n" data_ciphertext = tp_link_crypto.aes_encrypt(aes_key, aes_iv, login_data.encode()) data = base64.b64encode(data_ciphertext).decode() print_d(login_data) # Create the sign field seq_with_data_len = seq + len(data) auth_hash = hashlib.md5(f"{USERNAME}{password}".encode()).digest() # The string __must__ be null terminated, otherwise strlen gets the wrong size print(f"[*] Setting AES key to {AES_KEY}") print(f"[*] Setting AES IV to {AES_IV}") plaintext = f"key={AES_KEY}&iv={AES_IV}&h={auth_hash.hex()}&s={seq_with_data_len}\x00\r\n" sign = tp_link_crypto.rsa_encrypt(e, n, plaintext.encode()) print_d(plaintext) # Send the authentication request headers = { "User-Agent": USER_AGENT, "Content-Type": "text/plain", "Accept": "*/*", "Origin": f"http://{ip_addr}", "Connection": "keep-alive", "Referer": f"http://{ip_addr}/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", } request_data = f"sign={sign.hex()}\r\ndata={data}\r\n" resp = s.post(f"http://{ip_addr}/cgi_gdpr", headers=headers, data=request_data) # Get the session cookie cookie = resp.headers["Set-Cookie"] if cookie is None: print("[-] Login response did not include a Set-Cookie field in the header") return False # Example of the cookie field: # ``` # JSESSIONID=fc1e35a7a860e860be66d44bc7b34e; Path=/; HttpOnly # ``` # Get the JSESSIONID field because it's used during other requests. match = re.search(r"JSESSIONID=([a-z0-9]+)", cookie) if not match: print("[-] Could not find the JSESSIONID in the Set-Cookie filed of the login response") return False jsessionid = match.group(1) print(f"[+] JSESSIONID: {jsessionid}") # Decode the Base64 encoded response decoded: bytes = base64.b64decode(resp.text) decrypted_resp = tp_link_crypto.aes_decrypt(aes_key, aes_iv, decoded) # Remove the PKCS #7 padding num_padding_bytes = int(decrypted_resp[-1]) decrypted_resp = decrypted_resp[:-num_padding_bytes] decrypted_resp_str: str = decrypted_resp.decode() print_d(decrypted_resp_str) if "[cgi]0" in decrypted_resp_str and "$.ret=0" in decrypted_resp_str and "[error]0" in decrypted_resp_str: print("[+] Successfully authenticated with the router") else: # This might not be an error because other routers may have different response codes. The Archer C20 returns: # ``` # [cgi]0 # $.ret=0; # [error]0 # ``` print("[-] Unknown response message from router") print(decrypted_resp_str) return True def main(ip_addr: str, password: str) -> int: print(f"[*] Connecting to router at {ip_addr}") if not is_supported_model(ip_addr): print("[-] Router is not supported") return 1 s = requests.Session() success = authenticate(s, ip_addr, password) if not success: print("[-] Could not authenticate with the router") s.close() return 1 s.close() return 0 if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("ip_address", type=str) parser.add_argument("password", type=str) args = parser.parse_args() sys.exit(main(args.ip_address, args.password))
最新发布
10-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值