diff --git a/cmdline.c b/cmdline.c index 7ea5cacc..62d65e19 100644 --- a/cmdline.c +++ b/cmdline.c @@ -598,6 +598,14 @@ int cmdline_process_param(const char *p, char *value, SAVEABLE(0); conf_set_bool(conf, CONF_tryagent, false); } + + if (!strcmp(p, "-no-trivial-auth")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_bool(conf, CONF_ssh_no_trivial_userauth, true); + } + if (!strcmp(p, "-share")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); diff --git a/config.c b/config.c index ca808511..b8348530 100644 --- a/config.c +++ b/config.c @@ -2772,6 +2772,10 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_auth_bypass), conf_checkbox_handler, I(CONF_ssh_no_userauth)); + ctrl_checkbox(s, "Disconnect if authentication succeeds trivially", + 'n', HELPCTX(ssh_no_trivial_userauth), + conf_checkbox_handler, + I(CONF_ssh_no_trivial_userauth)); s = ctrl_getset(b, "Connection/SSH/Auth", "methods", "Authentication methods"); diff --git a/doc/config.but b/doc/config.but index a00ae476..77313282 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2623,6 +2623,49 @@ interact with them.) This option only affects SSH-2 connections. SSH-1 connections always require an authentication step. +\S{config-ssh-notrivialauth} \q{Disconnect if authentication succeeds +trivially} + +This option causes PuTTY to abandon an SSH session and disconnect from +the server, if the server accepted authentication without ever having +asked for any kind of password or signature or token. + +This might be used as a security measure. There are some forms of +attack against an SSH client user which work by terminating the SSH +authentication stage early, and then doing something in the main part +of the SSH session which \e{looks} like part of the authentication, +but isn't really. + +For example, instead of demanding a signature from your public key, +for which PuTTY would ask for your key's passphrase, a compromised or +malicious server might allow you to log in with no signature or +password at all, and then print a message that \e{imitates} PuTTY's +request for your passphrase, in the hope that you would type it in. +(In fact, the passphrase for your public key should not be sent to any +server.) + +PuTTY's main defence against attacks of this type is the \q{trust +sigil} system: messages in the PuTTY window that are truly originated +by PuTTY itself are shown next to a small copy of the PuTTY icon, +which the server cannot fake when it tries to imitate the same message +using terminal output. + +However, if you think you might be at risk of this kind of thing +anyway (if you don't watch closely for the trust sigils, or if you +think you're at extra risk of one of your servers being malicious), +then you could enable this option as an extra defence. Then, if the +server tries any of these attacks involving letting you through the +authentication stage, PuTTY will disconnect from the server before it +can send a follow-up fake prompt or other type of attack. + +On the other hand, some servers \e{legitimately} let you through the +SSH authentication phase trivially, either because they are genuinely +public, or because the important authentication step happens during +the terminal session. (An example might be an SSH server that connects +you directly to the terminal login prompt of a legacy mainframe.) So +enabling this option might cause some kinds of session to stop +working. It's up to you. + \S{config-ssh-tryagent} \q{Attempt authentication using Pageant} If this option is enabled, then PuTTY will look for Pageant (the SSH diff --git a/pscp.c b/pscp.c index 0bfda92d..a11d1e18 100644 --- a/pscp.c +++ b/pscp.c @@ -2203,6 +2203,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -hostkey keyid\n"); printf(" manually specify a host key (may be repeated)\n"); printf(" -batch disable all interactive prompts\n"); diff --git a/psftp.c b/psftp.c index 40cb73f5..c4b5374b 100644 --- a/psftp.c +++ b/psftp.c @@ -2538,6 +2538,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -hostkey keyid\n"); printf(" manually specify a host key (may be repeated)\n"); printf(" -batch disable all interactive prompts\n"); diff --git a/putty.h b/putty.h index ef6f7a37..7789f217 100644 --- a/putty.h +++ b/putty.h @@ -1428,6 +1428,7 @@ NORETURN void cleanup_exit(int); X(INT, NONE, sshprot) \ X(BOOL, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ X(BOOL, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ + X(BOOL, NONE, ssh_no_trivial_userauth) /* disable trivial types of auth */ \ X(BOOL, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ X(BOOL, NONE, try_tis_auth) \ X(BOOL, NONE, try_ki_auth) \ diff --git a/settings.c b/settings.c index 547af0dc..32a53c54 100644 --- a/settings.c +++ b/settings.c @@ -609,6 +609,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) #endif write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); write_setting_b(sesskey, "SshNoAuth", conf_get_bool(conf, CONF_ssh_no_userauth)); + write_setting_b(sesskey, "SshNoTrivialAuth", conf_get_bool(conf, CONF_ssh_no_trivial_userauth)); write_setting_b(sesskey, "SshBanner", conf_get_bool(conf, CONF_ssh_show_banner)); write_setting_b(sesskey, "AuthTIS", conf_get_bool(conf, CONF_try_tis_auth)); write_setting_b(sesskey, "AuthKI", conf_get_bool(conf, CONF_try_ki_auth)); @@ -1025,6 +1026,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) gpps(sesskey, "LogHost", "", conf, CONF_loghost); gppb(sesskey, "SSH2DES", false, conf, CONF_ssh2_des_cbc); gppb(sesskey, "SshNoAuth", false, conf, CONF_ssh_no_userauth); + gppb(sesskey, "SshNoTrivialAuth", false, conf, CONF_ssh_no_trivial_userauth); gppb(sesskey, "SshBanner", true, conf, CONF_ssh_show_banner); gppb(sesskey, "AuthTIS", false, conf, CONF_try_tis_auth); gppb(sesskey, "AuthKI", true, conf, CONF_try_ki_auth); diff --git a/ssh.c b/ssh.c index 00a516ea..91e7e869 100644 --- a/ssh.c +++ b/ssh.c @@ -254,7 +254,9 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, connection_layer, ssh->savedhost, ssh->fullhostname, conf_get_filename(ssh->conf, CONF_keyfile), conf_get_bool(ssh->conf, CONF_ssh_show_banner), - conf_get_bool(ssh->conf, CONF_tryagent), username, + conf_get_bool(ssh->conf, CONF_tryagent), + conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth), + username, conf_get_bool(ssh->conf, CONF_change_username), conf_get_bool(ssh->conf, CONF_try_ki_auth), #ifndef NO_GSSAPI diff --git a/ssh1login.c b/ssh1login.c index 8486cbf0..519838d7 100644 --- a/ssh1login.c +++ b/ssh1login.c @@ -27,7 +27,7 @@ struct ssh1_login_state { char *savedhost; int savedport; - bool try_agent_auth; + bool try_agent_auth, is_trivial_auth; int remote_protoflags; int local_protoflags; @@ -105,6 +105,8 @@ PacketProtocolLayer *ssh1_login_new( s->savedhost = dupstr(host); s->savedport = port; s->successor_layer = successor_layer; + s->is_trivial_auth = true; + return &s->ppl; } @@ -645,6 +647,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); put_data(pkt, ret + 5, 16); pq_push(s->ppl.out_pq, pkt); + s->is_trivial_auth = false; crMaybeWaitUntilV( (pktin = ssh1_login_pop(s)) != NULL); @@ -814,6 +817,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); put_data(pkt, buffer, 16); pq_push(s->ppl.out_pq, pkt); + s->is_trivial_auth = false; mp_free(challenge); mp_free(response); @@ -1105,6 +1109,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0])); pq_push(s->ppl.out_pq, pkt); } + s->is_trivial_auth = false; ppl_logevent("Sent password"); free_prompts(s->cur_prompt); s->cur_prompt = NULL; @@ -1121,6 +1126,13 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) } } + if (conf_get_bool(s->conf, CONF_ssh_no_trivial_userauth) && + s->is_trivial_auth) { + ssh_proto_error(s->ppl.ssh, "Authentication was trivial! " + "Abandoning session as specified in configuration."); + return; + } + ppl_logevent("Authentication successful"); if (conf_get_bool(s->conf, CONF_compression)) { diff --git a/ssh2userauth.c b/ssh2userauth.c index 01243baf..451d5abe 100644 --- a/ssh2userauth.c +++ b/ssh2userauth.c @@ -28,7 +28,7 @@ struct ssh2_userauth_state { PacketProtocolLayer *transport_layer, *successor_layer; Filename *keyfile; - bool show_banner, tryagent, change_username; + bool show_banner, tryagent, notrivialauth, change_username; char *hostname, *fullhostname; char *default_username; bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; @@ -82,6 +82,7 @@ struct ssh2_userauth_state { int len; PktOut *pktout; bool want_user_input; + bool is_trivial_auth; agent_pending_query *auth_agent_query; bufchain banner; @@ -134,7 +135,7 @@ static const PacketProtocolLayerVtable ssh2_userauth_vtable = { PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, + Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) @@ -149,6 +150,7 @@ PacketProtocolLayer *ssh2_userauth_new( s->keyfile = filename_copy(keyfile); s->show_banner = show_banner; s->tryagent = tryagent; + s->notrivialauth = notrivialauth; s->default_username = dupstr(default_username); s->change_username = change_username; s->try_ki_auth = try_ki_auth; @@ -157,6 +159,7 @@ PacketProtocolLayer *ssh2_userauth_new( s->gssapi_fwd = gssapi_fwd; s->shgss = shgss; s->last_methods_string = strbuf_new(); + s->is_trivial_auth = true; bufchain_init(&s->banner); bufchain_sink_init(&s->banner_bs, &s->banner); @@ -818,6 +821,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) sigblob); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY; + s->is_trivial_auth = false; } else { ppl_logevent("Pageant refused signing request"); ppl_printf("Pageant failed to " @@ -1038,6 +1042,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ssh_key_free(key->key); sfree(key->comment); sfree(key); + s->is_trivial_auth = false; } #ifndef NO_GSSAPI @@ -1169,6 +1174,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * no longer says CONTINUE_NEEDED */ if (s->gss_sndtok.length != 0) { + s->is_trivial_auth = false; s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); @@ -1288,7 +1294,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Loop while the server continues to send INFO_REQUESTs. */ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - ptrlen name, inst; strbuf *sb; @@ -1308,6 +1313,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ s->num_prompts = get_uint32(pktin); for (uint32_t i = 0; i < s->num_prompts; i++) { + s->is_trivial_auth = false; ptrlen prompt = get_string(pktin); bool echo = get_bool(pktin); @@ -1472,7 +1478,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) pq_push_front(s->ppl.in_pq, pktin); } else if (s->can_passwd) { - + s->is_trivial_auth = false; /* * Plain old password authentication. */ @@ -1731,6 +1737,12 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } userauth_success: + if (s->notrivialauth && s->is_trivial_auth) { + ssh_proto_error(s->ppl.ssh, "Authentication was trivial! " + "Abandoning session as specified in configuration."); + return; + } + /* * We've just received USERAUTH_SUCCESS, and we haven't sent * any packets since. Signal the transport layer to consider diff --git a/sshppl.h b/sshppl.h index b339d67c..2c1dbf42 100644 --- a/sshppl.h +++ b/sshppl.h @@ -116,7 +116,7 @@ PacketProtocolLayer *ssh2_transport_new( PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, + Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, diff --git a/unix/uxplink.c b/unix/uxplink.c index 3e2a9b6b..240783f4 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -527,6 +527,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -noshare disable use of connection sharing\n"); printf(" -share enable use of connection sharing\n"); printf(" -hostkey keyid\n"); diff --git a/windows/winhelp.h b/windows/winhelp.h index ae5a7a7f..9011df45 100644 --- a/windows/winhelp.h +++ b/windows/winhelp.h @@ -111,6 +111,7 @@ #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" #define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" +#define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth" #define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" #define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" #define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" diff --git a/windows/winplink.c b/windows/winplink.c index 9bda0712..58d43e6d 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -149,6 +149,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -noshare disable use of connection sharing\n"); printf(" -share enable use of connection sharing\n"); printf(" -hostkey keyid\n");