Skip to content

Commit

Permalink
Merge branch 'hans/ssh/banner_grabbing/OTP-12659' into maint-17
Browse files Browse the repository at this point in the history
* hans/ssh/banner_grabbing/OTP-12659:
  ssh: added id_string option for server and client
  • Loading branch information
Erlang/OTP committed Apr 22, 2015
2 parents 871c5af + 20707ef commit da3d366
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 7 deletions.
17 changes: 17 additions & 0 deletions lib/ssh/doc/src/ssh.xml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@
<item>
<p>If true, the client will not print out anything on authorization.</p>
</item>

<tag><c><![CDATA[{id_string, random | string()}]]></c></tag>
<item>
<p>The string that the client presents to a connected server initially. The default value is "Erlang/VSN" where VSN is the ssh application version number.
</p>
<p>The value <c>random</c> will cause a random string to be created at each connection attempt. This is to make it a bit more difficult for a malicious peer to find the ssh software brand and version.
</p>
</item>

<tag><c><![CDATA[{fd, file_descriptor()}]]></c></tag>
<item>
<p>Allow an existing file descriptor to be used
Expand Down Expand Up @@ -344,6 +353,14 @@
</p>
</item>

<tag><c><![CDATA[{id_string, random | string()}]]></c></tag>
<item>
<p>The string the daemon will present to a connecting peer initially. The default value is "Erlang/VSN" where VSN is the ssh application version number.
</p>
<p>The value <c>random</c> will cause a random string to be created at each connection attempt. This is to make it a bit more difficult for a malicious peer to find the ssh software brand and version.
</p>
</item>

<tag><c><![CDATA[{key_cb, atom()}]]></c></tag>
<item>
<p>Module implementing the behaviour <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
Expand Down
6 changes: 6 additions & 0 deletions lib/ssh/src/ssh.erl
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ handle_option([parallel_login|Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]);
handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{id_string, _ID} = Opt|Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions).

Expand Down Expand Up @@ -439,6 +441,10 @@ handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 ->
Opt;
handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) ->
Opt;
handle_ssh_option({id_string, random}) ->
{id_string, {random,2,5}}; %% 2 - 5 random characters
handle_ssh_option({id_string, ID} = Opt) when is_list(ID) ->
Opt;
handle_ssh_option(Opt) ->
throw({error, {eoptions, Opt}}).

Expand Down
34 changes: 28 additions & 6 deletions lib/ssh/src/ssh_transport.erl
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,34 @@

versions(client, Options)->
Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION),
Version = format_version(Vsn),
{Vsn, Version};
{Vsn, format_version(Vsn, software_version(Options))};
versions(server, Options) ->
Vsn = proplists:get_value(vsn, Options, ?DEFAULT_SERVER_VERSION),
Version = format_version(Vsn),
{Vsn, Version}.
{Vsn, format_version(Vsn, software_version(Options))}.

software_version(Options) ->
case proplists:get_value(id_string, Options) of
undefined ->
"Erlang"++ssh_vsn();
{random,Nlo,Nup} ->
random_id(Nlo,Nup);
ID ->
ID
end.

ssh_vsn() ->
try {ok,L} = application:get_all_key(ssh),
proplists:get_value(vsn,L,"")
of
"" -> "";
VSN when is_list(VSN) -> "/" ++ VSN;
_ -> ""
catch
_:_ -> ""
end.

random_id(Nlo, Nup) ->
[crypto:rand_uniform($a,$z+1) || _<- lists:duplicate(crypto:rand_uniform(Nlo,Nup+1),x) ].

hello_version_msg(Data) ->
[Data,"\r\n"].
Expand Down Expand Up @@ -77,9 +99,9 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm,
yes_no(Ssh, Prompt) ->
(Ssh#ssh.io_cb):yes_no(Prompt, Ssh).

format_version({Major,Minor}) ->
format_version({Major,Minor}, SoftwareVersion) ->
"SSH-" ++ integer_to_list(Major) ++ "." ++
integer_to_list(Minor) ++ "-Erlang".
integer_to_list(Minor) ++ "-" ++ SoftwareVersion.

handle_hello_version(Version) ->
try
Expand Down
109 changes: 109 additions & 0 deletions lib/ssh/test/ssh_basic_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ all() ->
ssh_connect_arg4_timeout,
packet_size_zero,
ssh_daemon_minimal_remote_max_packet_size_option,
id_string_no_opt_client,
id_string_own_string_client,
id_string_random_client,
id_string_no_opt_server,
id_string_own_string_server,
id_string_random_server,
{group, hardening_tests}
].

Expand Down Expand Up @@ -816,6 +822,66 @@ ssh_daemon_minimal_remote_max_packet_size_option(Config) ->
ssh:close(Conn),
ssh:stop_daemon(Server).

%%--------------------------------------------------------------------
id_string_no_opt_client(Config) ->
{Server, Host, Port} = fake_daemon(Config),
{error,_} = ssh:connect(Host, Port, []),
receive
{id,Server,"SSH-2.0-Erlang/"++Vsn} ->
true = expected_ssh_vsn(Vsn);
{id,Server,Other} ->
ct:fail("Unexpected id: ~s.",[Other])
end.

%%--------------------------------------------------------------------
id_string_own_string_client(Config) ->
{Server, Host, Port} = fake_daemon(Config),
{error,_} = ssh:connect(Host, Port, [{id_string,"Pelle"}]),
receive
{id,Server,"SSH-2.0-Pelle\r\n"} ->
ok;
{id,Server,Other} ->
ct:fail("Unexpected id: ~s.",[Other])
end.

%%--------------------------------------------------------------------
id_string_random_client(Config) ->
{Server, Host, Port} = fake_daemon(Config),
{error,_} = ssh:connect(Host, Port, [{id_string,random}]),
receive
{id,Server,Id="SSH-2.0-Erlang"++_} ->
ct:fail("Unexpected id: ~s.",[Id]);
{id,Server,Rnd="SSH-2.0-"++_} ->
ct:log("Got ~s.",[Rnd]);
{id,Server,Id} ->
ct:fail("Unexpected id: ~s.",[Id])
end.

%%--------------------------------------------------------------------
id_string_no_opt_server(Config) ->
{_Server, Host, Port} = std_daemon(Config, []),
{ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]),
{ok,"SSH-2.0-Erlang/"++Vsn} = gen_tcp:recv(S1, 0, 2000),
true = expected_ssh_vsn(Vsn).

%%--------------------------------------------------------------------
id_string_own_string_server(Config) ->
{_Server, Host, Port} = std_daemon(Config, [{id_string,"Olle"}]),
{ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]),
{ok,"SSH-2.0-Olle\r\n"} = gen_tcp:recv(S1, 0, 2000).

%%--------------------------------------------------------------------
id_string_random_server(Config) ->
{_Server, Host, Port} = std_daemon(Config, [{id_string,random}]),
{ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]),
{ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000),
case Rnd of
"Erlang"++_ -> ct:log("Id=~p",[Rnd]),
{fail,got_default_id};
"Olle\r\n" -> {fail,got_previous_tests_value};
_ -> ct:log("Got ~s.",[Rnd])
end.

%%--------------------------------------------------------------------
ssh_connect_negtimeout_parallel(Config) -> ssh_connect_negtimeout(Config,true).
ssh_connect_negtimeout_sequential(Config) -> ssh_connect_negtimeout(Config,false).
Expand Down Expand Up @@ -1095,3 +1161,46 @@ do_shell(IO, Shell) ->
%% {'EXIT', Shell, killed} ->
%% ok
%% end.


std_daemon(Config, ExtraOpts) ->
SystemDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
file:make_dir(UserDir),
{_Server, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2} | ExtraOpts]).

expected_ssh_vsn(Str) ->
try
{ok,L} = application:get_all_key(ssh),
proplists:get_value(vsn,L,"")++"\r\n"
of
Str -> true;
"\r\n" -> true;
_ -> false
catch
_:_ -> true %% ssh not started so we dont't know
end.


fake_daemon(_Config) ->
Parent = self(),
%% start the server
Server = spawn(fun() ->
{ok,Sl} = gen_tcp:listen(0,[]),
{ok,{Host,Port}} = inet:sockname(Sl),
Parent ! {sockname,self(),Host,Port},
Rsa = gen_tcp:accept(Sl),
ct:log("Server gen_tcp:accept got ~p",[Rsa]),
{ok,S} = Rsa,
receive
{tcp, S, Id} -> Parent ! {id,self(),Id}
end
end),
%% Get listening host and port
receive
{sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort}
end.

2 changes: 1 addition & 1 deletion lib/ssh/vsn.mk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode

SSH_VSN = 3.2.1
SSH_VSN = 3.2.2
APP_VSN = "ssh-$(SSH_VSN)"

0 comments on commit da3d366

Please sign in to comment.