sshcatch 0.1.0__tar.gz → 0.2.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.
@@ -0,0 +1,95 @@
1
+ # Changelog
2
+
3
+ All notable changes to **sshcatch** are documented here.
4
+ This project follows [Keep a Changelog](https://keepachangelog.com/) and
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.2.0] - 2026-07-19
8
+
9
+ Large rewrite of the SFTP layer and the logging system.
10
+
11
+ ### Added
12
+
13
+ - **Subdirectory support** for SCP/SFTP. `map_path()` is overridden to normalize
14
+ paths into the chroot, so `scp -r`, nested uploads/downloads and recursive
15
+ `sftp` listings now work. The old "root directory only" restriction
16
+ (`_require_flat`) is gone.
17
+ - **Single-connection mode** (`-1` / `--single`). Accepts the first successful
18
+ authentication, closes the listener to free the port, and exits once that
19
+ connection ends. Implemented via an asyncio future resolved in
20
+ `auth_completed()`.
21
+ - **Tiered, verbosity-controlled logging.** New mutually exclusive `-q`/`--quiet`
22
+ and `-v`/`--verbose` (repeatable) flags map to WARNING (default) / INFO (`-v`) /
23
+ DEBUG (`-vv`). Every log helper now takes a `level`. The `--output` logfile
24
+ always records everything (DEBUG) regardless of console verbosity.
25
+ - **Recursive-upload plumbing.** `mkdir` is allowed during uploads and
26
+ `_ensure_parent()` auto-creates missing parent directories (logged as
27
+ `MKPARENT`), so uploading a tree lands intact.
28
+ - **Directory listings** via `scandir` (download only), which also **hides
29
+ protected files and symlinks** from the listing.
30
+ - **SCP log de-duplication**: identical SCP messages within a 1-second window are
31
+ collapsed to cut noise.
32
+ - **Error-handling wrapper** `_execute_wrapped_log()` that reports
33
+ `NOTFOUND` (FileNotFoundError) and `ERROR` (OSError) cleanly instead of raising
34
+ raw tracebacks.
35
+ - **`--single` and banner details in the startup summary**, plus a
36
+ `banner_preview()` helper that truncates long banners.
37
+
38
+ ### Changed
39
+
40
+ - **Symlink handling redesigned.**
41
+ - On **download**, symlinks are denied (`_require_not_symlink`) and hidden from
42
+ listings — a transfer can never follow a link out of the chroot.
43
+ - On **upload**, a symlink is no longer rejected outright; instead a small
44
+ placeholder file recording the target is written, so recursive uploads that
45
+ contain a link still complete.
46
+ - **SFTP server implementation.** Replaced the metaprogramming approach
47
+ (`_SFTP_ALL_OPS` / `_SFTP_WHITELIST` / dynamic `setattr` of deny stubs) with
48
+ explicit, individually-commented methods for every allowed and denied
49
+ operation. Newly explicit denials: `open56`, `readlink`, `posix_rename`,
50
+ `statvfs`, `fstatvfs`, `fsync`, `lock`, `unlock` (in addition to
51
+ `remove`/`rename`/`rmdir`/`link`).
52
+ - **Post-auth banner delivery** is now reliable: it wraps asyncssh's
53
+ `send_userauth_success` and fires exactly once, instead of being triggered from
54
+ inside password/key validation.
55
+ - **Host key algorithm** changed from RSA-2048 to **ed25519**.
56
+ - **Protected files** are now matched by their path relative to the SCP directory
57
+ (supports subdirectories) instead of just basename, and the **sshcatch script
58
+ itself** (`__file__`) is now protected alongside the host key, authorized_keys
59
+ and logfile.
60
+ - **Startup summary** is now emitted through the logger (so it also lands in
61
+ `--output` and is suppressed by `-q`) rather than printed directly. The auth
62
+ line now shows counts, e.g. `restricted (2 users, 1 key)`.
63
+ - **Connection-close logging** is level-aware: authenticated disconnects log at
64
+ WARNING, unauthenticated ones at DEBUG.
65
+ - **Clean channel close.** `exit()` now calls `channel.exit(0)` so clients don't
66
+ hang waiting for a session that never opens.
67
+ - Full offered public keys are now shown automatically at `-vv` (DEBUG) instead
68
+ of requiring a dedicated flag.
69
+ - Help text / argument groups updated: "SCP / SFTP file transfer", refreshed
70
+ `--scp-*` descriptions (symlink behavior, no subdirectory restriction).
71
+
72
+ ### Removed
73
+
74
+ - **`-K` / `--full-keys` flag** — superseded by `-vv` verbosity.
75
+ - **Startup symlink scan** that refused to start if the SCP directory contained
76
+ any symlink — replaced by the per-operation symlink handling above.
77
+
78
+ ### Security
79
+
80
+ - Every SFTP operation is now explicitly allow-listed or denied, and symlink
81
+ traversal out of the chroot is blocked on every path-taking operation rather
82
+ than only checked once at startup.
83
+
84
+ ## [0.1.1]
85
+
86
+ ### Fixed
87
+
88
+ - **Hardened the flat-directory (no-subdirectory) check.** The upload/download
89
+ path is now run through `posixpath.normpath()` before it is compared against
90
+ its basename, so `./`-prefixed and other non-canonical paths are collapsed
91
+ first and can no longer slip past the "root directory only" restriction.
92
+
93
+ ## [0.1.0]
94
+
95
+ - Initial version published on GitHub.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sshcatch
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Quick-deploy SSH server for tunneling and simple SCP transfers - never opens a shell.
5
5
  Project-URL: Homepage, https://github.com/LorenzMap/sshcatch
6
6
  Project-URL: Repository, https://github.com/LorenzMap/sshcatch
@@ -21,65 +21,95 @@ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Topic :: Security
22
22
  Classifier: Topic :: System :: Networking
23
23
  Requires-Python: >=3.10
24
- Requires-Dist: asyncssh>=2.15
24
+ Requires-Dist: asyncssh<2.25.0,>=2.21.0
25
25
  Description-Content-Type: text/markdown
26
26
 
27
27
  # sshcatch
28
28
 
29
- A quick-deploy SSH server for tunneling (local/remote/dynamic) and simple SCP
30
- transfers - it NEVER opens a shell.
29
+ A quick-deploy SSH server for tunneling (local/remote/dynamic) and simple SCP /
30
+ SFTP transfers - it **never opens a shell**.
31
31
 
32
- By default all features are disabled: connections are logged and closed.
33
- Turn on what you need with the flags described below. Handy on an engagement
34
- when you want a controlled SSH endpoint without using a full `sshd`.
32
+ By default all features are disabled: connections are logged and closed. Turn on
33
+ only what you need with the flags described below. Handy on an engagement when
34
+ you want a controlled SSH endpoint (a tunnel relay or a file drop) without
35
+ setting up a full `sshd`.
35
36
 
36
37
  Built on [asyncssh](https://github.com/ronf/asyncssh).
37
38
 
39
+ This is a pentesting tool. Only point it at systems and networks you are authorized
40
+ to test.
41
+
38
42
  ## Install
39
43
 
44
+ With `pipx` (recommended, installs into an isolated environment and puts
45
+ `sshcatch` on your `PATH`):
46
+
47
+ ```
48
+ pipx install sshcatch
49
+ ```
50
+
51
+ With `pip`:
52
+
40
53
  ```
41
54
  pip install sshcatch
42
55
  ```
43
56
 
44
- Or from source:
57
+ From source:
45
58
 
46
59
  ```
47
60
  git clone https://github.com/LorenzMap/sshcatch
48
61
  cd sshcatch
49
- pip install .
62
+ pipx install . # or: pip install .
50
63
  ```
51
64
 
52
65
  Needs Python 3.10+. A host key is auto-generated in the working directory on
53
66
  first run (or point `--host-key` at your own).
54
67
 
55
- ## Usage
68
+ ## How it works
56
69
 
57
- Because no shell is created on the server **always** use the `-N` flag on
58
- your tunnel connections or you get disconnected instantly!
70
+ Without any flags sshcatch is in **log-only** mode: It accepts the
71
+ connection, records the client version, username, offered passwords and public
72
+ keys, then closes. Nothing else is enabled until you ask for it.
59
73
 
60
- The SCP directory does intentionally **NOT** support subdirectories!
74
+ Adjust the amount of logging using `-vv` and `-q`. Or put everything into a
75
+ logfile using `-o`.
61
76
 
62
- Log-only - just capture creds and full public keys:
77
+ #### Tunnels
63
78
 
64
- ```
65
- # Server
66
- sshcatch -K
79
+ Because no shell is ever created, tunnel clients **must** pass `-N` (e.g.
80
+ `ssh -NL ...`) or they get disconnected instantly.
67
81
 
68
- # Client
69
- ssh user@host
70
- ```
82
+ Turning tunneling on with `--open-auth` means **anyone** who connects can pivot
83
+ through your host!
84
+
85
+ #### SCP / SFTP
86
+
87
+ **Symlinks** are handled very restrictively: On upload they create a placeholder file
88
+ that contains the original target. On download they are outright denied.
89
+
90
+ The host key, `authorized_keys`, logfile and the sshcatch script itself are **protected**
91
+ and hidden when they live inside the SCP directory.
71
92
 
72
- Let one user pull files via SCP/SFTP:
93
+ Uploads **never overwrite** an existing file. The new file gets a numeric suffix
94
+ (`loot.tar` -> `loot_1.tar`). Non-existent parent folders are created.
95
+
96
+ Renames and Remove operations are denied.
97
+
98
+ ## Examples
99
+
100
+ Let one user pull/put files from the current directory via SCP/SFTP:
73
101
 
74
102
  ```
75
103
  # Server
76
- sshcatch -u user:pass --scp-download
104
+ sshcatch -u user:pass --scp-download --scp-upload
77
105
 
78
106
  # Client
79
107
  scp user@host:secret.txt .
108
+ scp -r loot/ user@host:pete/pc/
109
+ sftp user@host
80
110
  ```
81
111
 
82
- Let anyone tunnel through the server (local and dynamic forwards):
112
+ Let anyone tunnel through the server (local and dynamic forwards): **Be careful with this one!**
83
113
 
84
114
  ```
85
115
  # Server
@@ -90,8 +120,21 @@ ssh -NL 8080:internal:80 user@host # local forward
90
120
  ssh -ND 1080 user@host # dynamic (SOCKS)
91
121
  ```
92
122
 
93
- My favorite one - reverse tunnel and SCP uploads for the keys in
94
- `./authorized-keys`, while posing as a ubuntu SSH server on port 2222:
123
+ Using single-mode to return something to the first successful authentication
124
+ by closing the server afterwards, while printing timestamped logs to the console
125
+ and saving them into a file:
126
+
127
+ ```
128
+ # Server
129
+ sshcatch -1 -u arthur:42 --version-banner debian \
130
+ --pre-auth-banner "What is the answer to life the universe and everything" \
131
+ --post-auth-banner "flag{So_Long_and_Thanks_for_All_the_Fish}" \
132
+ -o sshcatch.log -t
133
+ ```
134
+
135
+
136
+ My favorite one: Reverse tunnel and SCP uploads for the keys in
137
+ `./authorized-keys` while posing as an Ubuntu SSH server on port 2222:
95
138
 
96
139
  ```
97
140
  # Server
@@ -102,15 +145,16 @@ ssh -NR 9000:localhost:22 user@host -p 2222 # reverse tunnel
102
145
  scp -P 2222 loot.tar user@host:. # upload
103
146
  ```
104
147
 
148
+
105
149
  ## Options
106
150
 
107
151
  ```
108
- usage: sshcatch [-h] [--version] [-p PORT] [-b BIND] [--host-key FILE]
109
- [-u USER:PASS] [--open-auth] [--authorized-keys FILE] [-K]
152
+ usage: sshcatch [-h] [--version] [-p PORT] [-b BIND] [--host-key FILE] [-1]
153
+ [-u USER:PASS] [--open-auth] [--authorized-keys FILE]
110
154
  [--forward] [--reverse] [--scp-upload] [--scp-download]
111
155
  [--scp-dir DIR] [--version-banner STRING]
112
156
  [--pre-auth-banner STRING] [--post-auth-banner STRING]
113
- [-o FILE] [-t] [--plain]
157
+ [-q | -v] [-o FILE] [-t] [--plain]
114
158
 
115
159
  options:
116
160
  -h, --help show this help message and exit
@@ -118,6 +162,8 @@ options:
118
162
  -p PORT, --port PORT listen port (default: 22)
119
163
  -b BIND, --bind BIND bind address (default: all IPv4/v6 interfaces)
120
164
  --host-key FILE server host key file (default: auto-generate)
165
+ -1, --single close the listener after first successful
166
+ authentication (and exit when that connection ends)
121
167
 
122
168
  authentication:
123
169
  -u USER:PASS, --user USER:PASS
@@ -126,21 +172,20 @@ authentication:
126
172
  --authorized-keys FILE
127
173
  authorized_keys file for key auth (username
128
174
  independent)
129
- -K, --full-keys log the full offered public key, not just its
130
- fingerprint
131
175
 
132
176
  tunneling:
133
177
  --forward enable forward tunnels (client: ssh -NL / -ND)
134
178
  --reverse enable reverse tunnels (client: ssh -NR)
135
179
 
136
- SCP / file transfer:
137
- --scp-upload enable file upload (SCP/SFTP write) - subdirectories
138
- are disabled - files get suffix instead of overwriting
139
- --scp-download enable file download (SCP/SFTP read) - subdirectories
140
- are disabled
141
- --scp-dir DIR directory for SCP/SFTP (default: cwd) - subdirectories
142
- are disabled - host-key (and optional authorized_keys
143
- and logfile) are protected
180
+ SCP / SFTP file transfer:
181
+ --scp-upload enable file upload (SCP/SFTP write) - files get suffix
182
+ instead of overwriting - symlinks become placeholder
183
+ files
184
+ --scp-download enable file download (SCP/SFTP read) - symlinks are
185
+ denied
186
+ --scp-dir DIR directory for SCP/SFTP (default: cwd) - sensitive
187
+ files (host-key, authorized_keys, logfile) are
188
+ protected
144
189
 
145
190
  banners:
146
191
  --version-banner STRING
@@ -154,20 +199,15 @@ banners:
154
199
  successfully
155
200
 
156
201
  logging:
202
+ -q, --quiet print nothing on console
203
+ -v, --verbose print additional information to the console
204
+ (repeatable)
157
205
  -o FILE, --output FILE
158
206
  append the log to FILE (plain with timestamps)
159
207
  -t, --timestamps prefix console lines with a timestamp
160
208
  --plain disable ANSI colors on the console
161
209
  ```
162
210
 
163
- ## A word of warning
164
-
165
- - This is a pentesting tool. Only point it at systems and networks you are
166
- authorized to test.
167
-
168
- - `--open-auth --forward` means ANYONE can tunnel through
169
- your host - know what you're exposing before you run it.
170
-
171
211
  ## License
172
212
 
173
213
  MIT
@@ -0,0 +1,187 @@
1
+ # sshcatch
2
+
3
+ A quick-deploy SSH server for tunneling (local/remote/dynamic) and simple SCP /
4
+ SFTP transfers - it **never opens a shell**.
5
+
6
+ By default all features are disabled: connections are logged and closed. Turn on
7
+ only what you need with the flags described below. Handy on an engagement when
8
+ you want a controlled SSH endpoint (a tunnel relay or a file drop) without
9
+ setting up a full `sshd`.
10
+
11
+ Built on [asyncssh](https://github.com/ronf/asyncssh).
12
+
13
+ This is a pentesting tool. Only point it at systems and networks you are authorized
14
+ to test.
15
+
16
+ ## Install
17
+
18
+ With `pipx` (recommended, installs into an isolated environment and puts
19
+ `sshcatch` on your `PATH`):
20
+
21
+ ```
22
+ pipx install sshcatch
23
+ ```
24
+
25
+ With `pip`:
26
+
27
+ ```
28
+ pip install sshcatch
29
+ ```
30
+
31
+ From source:
32
+
33
+ ```
34
+ git clone https://github.com/LorenzMap/sshcatch
35
+ cd sshcatch
36
+ pipx install . # or: pip install .
37
+ ```
38
+
39
+ Needs Python 3.10+. A host key is auto-generated in the working directory on
40
+ first run (or point `--host-key` at your own).
41
+
42
+ ## How it works
43
+
44
+ Without any flags sshcatch is in **log-only** mode: It accepts the
45
+ connection, records the client version, username, offered passwords and public
46
+ keys, then closes. Nothing else is enabled until you ask for it.
47
+
48
+ Adjust the amount of logging using `-vv` and `-q`. Or put everything into a
49
+ logfile using `-o`.
50
+
51
+ #### Tunnels
52
+
53
+ Because no shell is ever created, tunnel clients **must** pass `-N` (e.g.
54
+ `ssh -NL ...`) or they get disconnected instantly.
55
+
56
+ Turning tunneling on with `--open-auth` means **anyone** who connects can pivot
57
+ through your host!
58
+
59
+ #### SCP / SFTP
60
+
61
+ **Symlinks** are handled very restrictively: On upload they create a placeholder file
62
+ that contains the original target. On download they are outright denied.
63
+
64
+ The host key, `authorized_keys`, logfile and the sshcatch script itself are **protected**
65
+ and hidden when they live inside the SCP directory.
66
+
67
+ Uploads **never overwrite** an existing file. The new file gets a numeric suffix
68
+ (`loot.tar` -> `loot_1.tar`). Non-existent parent folders are created.
69
+
70
+ Renames and Remove operations are denied.
71
+
72
+ ## Examples
73
+
74
+ Let one user pull/put files from the current directory via SCP/SFTP:
75
+
76
+ ```
77
+ # Server
78
+ sshcatch -u user:pass --scp-download --scp-upload
79
+
80
+ # Client
81
+ scp user@host:secret.txt .
82
+ scp -r loot/ user@host:pete/pc/
83
+ sftp user@host
84
+ ```
85
+
86
+ Let anyone tunnel through the server (local and dynamic forwards): **Be careful with this one!**
87
+
88
+ ```
89
+ # Server
90
+ sshcatch --open-auth --forward
91
+
92
+ # Client
93
+ ssh -NL 8080:internal:80 user@host # local forward
94
+ ssh -ND 1080 user@host # dynamic (SOCKS)
95
+ ```
96
+
97
+ Using single-mode to return something to the first successful authentication
98
+ by closing the server afterwards, while printing timestamped logs to the console
99
+ and saving them into a file:
100
+
101
+ ```
102
+ # Server
103
+ sshcatch -1 -u arthur:42 --version-banner debian \
104
+ --pre-auth-banner "What is the answer to life the universe and everything" \
105
+ --post-auth-banner "flag{So_Long_and_Thanks_for_All_the_Fish}" \
106
+ -o sshcatch.log -t
107
+ ```
108
+
109
+
110
+ My favorite one: Reverse tunnel and SCP uploads for the keys in
111
+ `./authorized-keys` while posing as an Ubuntu SSH server on port 2222:
112
+
113
+ ```
114
+ # Server
115
+ sshcatch --reverse --authorized-keys ./authorized-keys --scp-upload --version-banner ubuntu -p 2222
116
+
117
+ # Client
118
+ ssh -NR 9000:localhost:22 user@host -p 2222 # reverse tunnel
119
+ scp -P 2222 loot.tar user@host:. # upload
120
+ ```
121
+
122
+
123
+ ## Options
124
+
125
+ ```
126
+ usage: sshcatch [-h] [--version] [-p PORT] [-b BIND] [--host-key FILE] [-1]
127
+ [-u USER:PASS] [--open-auth] [--authorized-keys FILE]
128
+ [--forward] [--reverse] [--scp-upload] [--scp-download]
129
+ [--scp-dir DIR] [--version-banner STRING]
130
+ [--pre-auth-banner STRING] [--post-auth-banner STRING]
131
+ [-q | -v] [-o FILE] [-t] [--plain]
132
+
133
+ options:
134
+ -h, --help show this help message and exit
135
+ --version show program's version number and exit
136
+ -p PORT, --port PORT listen port (default: 22)
137
+ -b BIND, --bind BIND bind address (default: all IPv4/v6 interfaces)
138
+ --host-key FILE server host key file (default: auto-generate)
139
+ -1, --single close the listener after first successful
140
+ authentication (and exit when that connection ends)
141
+
142
+ authentication:
143
+ -u USER:PASS, --user USER:PASS
144
+ allowed user:password (repeatable)
145
+ --open-auth accept any credentials (open mode)
146
+ --authorized-keys FILE
147
+ authorized_keys file for key auth (username
148
+ independent)
149
+
150
+ tunneling:
151
+ --forward enable forward tunnels (client: ssh -NL / -ND)
152
+ --reverse enable reverse tunnels (client: ssh -NR)
153
+
154
+ SCP / SFTP file transfer:
155
+ --scp-upload enable file upload (SCP/SFTP write) - files get suffix
156
+ instead of overwriting - symlinks become placeholder
157
+ files
158
+ --scp-download enable file download (SCP/SFTP read) - symlinks are
159
+ denied
160
+ --scp-dir DIR directory for SCP/SFTP (default: cwd) - sensitive
161
+ files (host-key, authorized_keys, logfile) are
162
+ protected
163
+
164
+ banners:
165
+ --version-banner STRING
166
+ sent as 'SSH-2.0-STRING' version banner - presets
167
+ (case-insensitive): ubuntu, debian, dropbear, windows,
168
+ macos
169
+ --pre-auth-banner STRING
170
+ banner shown to every client before login
171
+ --post-auth-banner STRING
172
+ banner shown only to clients that authenticate
173
+ successfully
174
+
175
+ logging:
176
+ -q, --quiet print nothing on console
177
+ -v, --verbose print additional information to the console
178
+ (repeatable)
179
+ -o FILE, --output FILE
180
+ append the log to FILE (plain with timestamps)
181
+ -t, --timestamps prefix console lines with a timestamp
182
+ --plain disable ANSI colors on the console
183
+ ```
184
+
185
+ ## License
186
+
187
+ MIT
@@ -25,7 +25,7 @@ classifiers = [
25
25
  "Topic :: Security",
26
26
  "Topic :: System :: Networking",
27
27
  ]
28
- dependencies = ["asyncssh>=2.15"]
28
+ dependencies = ["asyncssh>=2.21.0,<2.25.0"]
29
29
 
30
30
  [project.urls]
31
31
  Homepage = "https://github.com/LorenzMap/sshcatch"