From 20707ef7688bed44fec39c4673a8823211e94149 Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Thu, 16 Apr 2015 16:12:49 +0200
Subject: ssh: added id_string option for server and client
For limiting Banner Grabbing attempts.
---
lib/ssh/doc/src/ssh.xml | 17 ++++++
lib/ssh/src/ssh.erl | 6 +++
lib/ssh/src/ssh_transport.erl | 34 +++++++++---
lib/ssh/test/ssh_basic_SUITE.erl | 109 +++++++++++++++++++++++++++++++++++++++
lib/ssh/vsn.mk | 2 +-
5 files changed, 161 insertions(+), 7 deletions(-)
(limited to 'lib')
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 0e7e3848ad..72dafc0c09 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -180,6 +180,15 @@
-
If true, the client will not print out anything on authorization.
+
+
+ -
+
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.
+
+ The value random 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.
+
+
+
-
Allow an existing file descriptor to be used
@@ -344,6 +353,14 @@
+
+ -
+
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.
+
+ The value random 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.
+
+
+
-
Module implementing the behaviour ssh_server_key_api.
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 51ad691ba2..d4b02a024e 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -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).
@@ -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}}).
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 76fa776113..8669be570e 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -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"].
@@ -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
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 81c7b5cd15..f5f8991acc 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -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}
].
@@ -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).
@@ -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.
+
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index fec8dacab7..b2b85a717f 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -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)"
--
cgit v1.2.3
From ab9c37a30c960cbc5e5b72c73a3a315afc4c3920 Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Thu, 16 Apr 2015 16:40:06 +0200
Subject: inets: Add value 'none' in server_tokens config
When the Server header has empty info (or 'none' in config), it is not generated. This is for limiting Banner Grabbing attempts.
---
lib/inets/doc/src/httpd.xml | 3 ++-
lib/inets/src/http_server/httpd_conf.erl | 8 +++++---
lib/inets/src/http_server/httpd_response.erl | 7 +++++--
lib/inets/vsn.mk | 2 +-
4 files changed, 13 insertions(+), 7 deletions(-)
(limited to 'lib')
diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml
index 20c8a6b1b1..e40660ab39 100644
--- a/lib/inets/doc/src/httpd.xml
+++ b/lib/inets/doc/src/httpd.xml
@@ -315,7 +315,7 @@ text/plain asc txt
- {server_tokens, prod|major|minor|minimal|os|full|{private, string()}}
+ {server_tokens, none|prod|major|minor|minimal|os|full|{private, string()}}
-
ServerTokens defines how the value of the server header
should look.
@@ -323,6 +323,7 @@ text/plain asc txt
here is what the server header string could look like for
the different values of server-tokens:
+none "" % A Server: header will not be generated
prod "inets"
major "inets/5"
minor "inets/5.8"
diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl
index 78dda794db..dbdc1be272 100644
--- a/lib/inets/src/http_server/httpd_conf.erl
+++ b/lib/inets/src/http_server/httpd_conf.erl
@@ -219,14 +219,14 @@ load("ServerName " ++ ServerName, []) ->
load("ServerTokens " ++ ServerTokens, []) ->
%% These are the valid *plain* server tokens:
- %% sprod, major, minor, minimum, os, full
+ %% none, prod, major, minor, minimum, os, full
%% It can also be a "private" server token: private:
case string:tokens(ServerTokens, [$:]) of
["private", Private] ->
{ok,[], {server_tokens, clean(Private)}};
[TokStr] ->
Tok = list_to_atom(clean(TokStr)),
- case lists:member(Tok, [prod, major, minor, minimum, os, full]) of
+ case lists:member(Tok, [none, prod, major, minor, minimum, os, full]) of
true ->
{ok,[], {server_tokens, Tok}};
false ->
@@ -850,6 +850,8 @@ server(full = _ServerTokens) ->
OS = os_info(full),
lists:flatten(
io_lib:format("~s ~s OTP/~s", [?SERVER_SOFTWARE, OS, OTPRelease]));
+server(none = _ServerTokens) ->
+ "";
server({private, Server} = _ServerTokens) when is_list(Server) ->
%% The user provide its own
Server;
@@ -1299,7 +1301,7 @@ ssl_ca_certificate_file(ConfigDB) ->
end.
plain_server_tokens() ->
- [prod, major, minor, minimum, os, full].
+ [none, prod, major, minor, minimum, os, full].
error_report(Where,M,F,Error) ->
error_logger:error_report([{?MODULE, Where},
diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl
index 0895729d05..2fa91d47a0 100644
--- a/lib/inets/src/http_server/httpd_response.erl
+++ b/lib/inets/src/http_server/httpd_response.erl
@@ -287,8 +287,11 @@ create_header(ConfigDb, KeyValueTupleHeaders) ->
ContentType = "text/html",
Server = server(ConfigDb),
NewHeaders = add_default_headers([{"date", Date},
- {"content-type", ContentType},
- {"server", Server}],
+ {"content-type", ContentType}
+ | if Server=="" -> [];
+ true -> [{"server", Server}]
+ end
+ ],
KeyValueTupleHeaders),
lists:map(fun fix_header/1, NewHeaders).
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index e5b63a6446..e9ecb2632a 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -18,6 +18,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 5.10.6
+INETS_VSN = 5.10.7
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
--
cgit v1.2.3
From 0bdd37192dd76acdf575a2482eabaeba99d4fdf6 Mon Sep 17 00:00:00 2001
From: Erlang/OTP
Date: Wed, 22 Apr 2015 13:39:56 +0200
Subject: Update release notes
---
lib/inets/doc/src/notes.xml | 23 ++++++++++++++++++++++-
lib/ssh/doc/src/notes.xml | 21 +++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
(limited to 'lib')
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 2c3ee79f31..12bbc2b736 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -32,7 +32,28 @@
notes.xml
- Inets 5.10.6
+ Inets 5.10.7
+
+ Improvements and New Features
+
+ -
+
+ New value in server_tokens config for limiting
+ banner grabbing attempts.
+
+ By setting {server_tokens, none} in
+ ServiceConfig for inets:start(httpd,
+ ServiceConfig), the "Server:" header will not be set
+ in messages from the server.
+
+ Own Id: OTP-12661 Aux Id: seq12840
+
+
+
+
+
+
+Inets 5.10.6
Fixed Bugs and Malfunctions
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index acbf3124ef..41885c684c 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -29,6 +29,27 @@
notes.xml
+Ssh 3.2.2
+
+ Improvements and New Features
+
+ -
+
+ New option id_string for ssh:daemon and
+ ssh:connect for limiting banner grabbing attempts.
+
+ The possible values are: {id_string,string()} and
+ {id_string,random}. The latter will make ssh
+ generate a random nonsence id-string for each new
+ connection.
+
+ Own Id: OTP-12659
+
+
+
+
+
+
Ssh 3.2.1
Fixed Bugs and Malfunctions
--
cgit v1.2.3