Skip to content

Commit b7ed446

Browse files
committed
ssh: Disable SFTP subsystem by default
The subsystems daemon option now defaults to an empty list instead of enabling the SFTP subsystem via ssh_sftpd:subsystem_spec([]). This extends the "secure by default" principle to also cover SFTP, complementing the shell and exec default changes. Previously, all authenticated SSH users had access to the entire file system visible to the Erlang VM process through the default SFTP subsystem, with no path restrictions. Changes: - Change subsystems default from [ssh_sftpd:subsystem_spec([])] to [] - Update subsystem_spec doc string in ssh.hrl - Remove dead code in ssh_connection:check_subsystem/2 - Update test suites to explicitly enable SFTP where needed - Update documentation (hardening.md, introduction.md, using_ssh.md, ssh_app.md, terminology.md) Applications requiring SFTP must now explicitly enable it: ssh:daemon(Port, [{subsystems, [ssh_sftpd:subsystem_spec([])]} | Options])
1 parent 746290c commit b7ed446

17 files changed

Lines changed: 66 additions & 54 deletions

lib/ssh/doc/guides/hardening.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ excessive memory consumption.
122122

123123
### SFTP Server Resource Limits
124124

125-
When running an SFTP server via `ssh_sftpd:subsystem_spec/1`, additional
125+
When enabling the SFTP subsystem via `ssh_sftpd:subsystem_spec/1`, additional
126126
resource limits can be configured to protect against resource exhaustion attacks:
127127

128128
#### max_handles
@@ -357,6 +357,10 @@ The negotiation (session setup time) time can be limited with the _parameter_
357357

358358
## SFTP Security
359359

360+
Note that the SFTP server runs with the file access rights of the OS
361+
process running the Erlang emulator, regardless of the authenticated
362+
SSH user. See the [Terminology](terminology.md) section for details.
363+
360364
### Root Directory Isolation
361365

362366
The `root` option (see `m:ssh_sftpd`) restricts SFTP users to a

lib/ssh/doc/guides/introduction.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ The `ssh` application is an implementation of the SSH Transport, Connection and
3434
Authentication Layer Protocols in Erlang. It provides the following:
3535

3636
- API functions to write customized SSH clients and servers applications
37-
- The Erlang shell available over SSH
38-
- An SFTP client (`m:ssh_sftp`) and server (`m:ssh_sftpd`)
37+
- The Erlang shell available over SSH, enabled via the `shell` daemon option
38+
- An SFTP client (`m:ssh_sftp`) and server (`m:ssh_sftpd`), enabled via the
39+
`subsystems` daemon option
3940

4041
## Prerequisites
4142

@@ -114,9 +115,9 @@ the application logic.
114115
Channels come in the following three flavors:
115116

116117
- _Subsystem_ \- Named services that can be run as part of an SSH server, such
117-
as SFTP [(ssh_sftpd)](`m:ssh_sftpd`), that is built into the SSH daemon
118-
(server) by default, but it can be disabled. The Erlang `ssh` daemon can be
119-
configured to run any Erlang- implemented SSH subsystem.
118+
as SFTP [(ssh_sftpd)](`m:ssh_sftpd`). No subsystems are enabled by default.
119+
The Erlang `ssh` daemon can be configured to run any Erlang-implemented SSH
120+
subsystem.
120121
- _Shell_ \- Interactive shell. By default the Erlang daemon does not expose the Erlang
121122
shell. It can be enabled with option `{shell, {shell, start, []}}`
122123
The shell can be customized by providing your own read-eval-print loop.

lib/ssh/doc/guides/terminology.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,9 @@ the server's emulator. The rights in that shell is independent of the just
157157
authenticated user.
158158

159159
In case of an sftp request, an sftp server is started with the rights of the
160-
user of the Erlang emulator's OS process. So with sftp the authenticated user
161-
does not influence the rights.
160+
user of the Erlang emulator's OS process, provided the SFTP subsystem is
161+
enabled. So with sftp, the file access rights are those of the OS process
162+
running the Erlang emulator, regardless of the authenticated SSH user.
162163

163164
So after an authentication, the user name is not used anymore and has no
164165
influence.

lib/ssh/doc/guides/using_ssh.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ and `ClientAddress`). See the
433433

434434
## SFTP Server
435435

436-
Start the Erlang `ssh` daemon with the SFTP subsystem:
436+
The SFTP subsystem is not enabled by default. To start an SSH daemon with
437+
SFTP, configure the `subsystems` option explicitly:
437438

438439
```erlang
439440
1> ssh:start().

lib/ssh/doc/ssh_app.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ SSH File Transfer Protocol (SFTP) client and server.
2828

2929
The `ssh` application is an implementation of the SSH protocol in Erlang. `ssh`
3030
offers API functions to write customized SSH clients and servers as well as
31-
making the Erlang shell available over SSH. An SFTP client, `ssh_sftp`, and
32-
server, `ssh_sftpd`, are also included.
31+
making the Erlang shell available over SSH. An SFTP client (`ssh_sftp`) and
32+
server (`ssh_sftpd`) are also included. The SFTP server can be enabled via
33+
the `subsystems` daemon option.
3334

3435
## Dependencies
3536

lib/ssh/src/ssh.hrl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,13 @@ The `channel_callback` is the module that implements the `m:ssh_server_channel`
208208
[Creating a Subsystem](using_ssh.md#usersguide_creating_a_subsystem) in the
209209
User's Guide for more information and an example.
210210

211-
If the subsystems option is not present, the value of
212-
`ssh_sftpd:subsystem_spec([])` is used. This enables the sftp subsystem by
213-
default. The option can be set to the empty list if you do not want the daemon
214-
to run any subsystems.
211+
If the subsystems option is not present, the default is an empty list
212+
and no subsystems are enabled.
213+
214+
To enable the SFTP subsystem:
215+
```
216+
ssh:daemon(Port, [{subsystems, [ssh_sftpd:subsystem_spec([])]} | Options])
217+
```
215218
""".
216219
-doc(#{group => <<"Daemon Options">>}).
217220
-type subsystem_spec() :: {Name::string(), mod_args()} .

lib/ssh/src/ssh_connection.erl

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,7 +1505,8 @@ start_subsystem(BinName, #connection{options = Options,
15051505
connection_supervisor = ConnectionSup},
15061506
#channel{local_id = ChannelId}, _ReplyMsg) ->
15071507
Name = binary_to_list(BinName),
1508-
case check_subsystem(Name, Options) of
1508+
Subsystems = ?GET_OPT(subsystems, Options),
1509+
case proplists:get_value(Name, Subsystems, {none, []}) of
15091510
{Callback, Opts} when is_atom(Callback), Callback =/= none ->
15101511
%% Exec is not used by subsystems; undefined is filtered
15111512
%% out by channel_cb_init_args/1 so it won't be appended
@@ -1518,26 +1519,6 @@ start_subsystem(BinName, #connection{options = Options,
15181519
{error, legacy_option_not_supported}
15191520
end.
15201521

1521-
1522-
%%% Helpers for starting cli/subsystems
1523-
check_subsystem("sftp"= SsName, Options) ->
1524-
case ?GET_OPT(subsystems, Options) of
1525-
no_subsys -> % FIXME: Can 'no_subsys' ever be matched?
1526-
{SsName, {Cb, Opts}} = ssh_sftpd:subsystem_spec([]),
1527-
{Cb, Opts};
1528-
SubSystems ->
1529-
proplists:get_value(SsName, SubSystems, {none, []})
1530-
end;
1531-
1532-
check_subsystem(SsName, Options) ->
1533-
Subsystems = ?GET_OPT(subsystems, Options),
1534-
case proplists:get_value(SsName, Subsystems, {none, []}) of
1535-
Fun when is_function(Fun) ->
1536-
{Fun, []};
1537-
{_, _} = Value ->
1538-
Value
1539-
end.
1540-
15411522
%%%----------------------------------------------------------------
15421523
%%%
15431524
%%% Send-window handling

lib/ssh/src/ssh_options.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ default(server) ->
405405
(default(common))
406406
#{
407407
subsystems =>
408-
#{default => [ssh_sftpd:subsystem_spec([])],
408+
#{default => [],
409409
chk => fun(L) ->
410410
is_list(L) andalso
411411
lists:all(fun(SubSystem = {Name,{CB,Args}}) ->

lib/ssh/test/ssh_algorithms_SUITE.erl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ init_per_group(Tag, Algs, Alg, PA, Config) ->
231231
end;
232232

233233
_ ->
234-
start_std_daemon([{exec, erlang_eval}] ++ [PrefAlgs],
234+
start_std_daemon([{exec, erlang_eval},
235+
{subsystems, [ssh_sftpd:subsystem_spec([])]}
236+
| [PrefAlgs]],
235237
[{pref_algs,PrefAlgs},
236238
{tag_alg,{Tag,[Alg]}}
237239
| Config])

lib/ssh/test/ssh_basic_SUITE.erl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ idle_time_common(DaemonExtraOpts, ClientExtraOpts, Config) ->
459459
UserDir = proplists:get_value(priv_dir, Config),
460460

461461
{Pid, Host, Port} = ssh_test_lib:daemon([{exec, erlang_eval},
462+
{subsystems, [ssh_sftpd:subsystem_spec([])]},
462463
{system_dir, SystemDir},
463464
{user_dir, UserDir},
464465
{failfun, fun ssh_test_lib:failfun/2}

0 commit comments

Comments
 (0)