diff --git a/doc/plink.but b/doc/plink.but index b3afb9b4..b0f4f09c 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -328,6 +328,46 @@ channel. \dt Do not sanitise server data written to Plink's standard output channel. +\S2{plink-option-antispoof} \I{-no-antispoof}: turn off authentication spoofing protection prompt + +In SSH, some possible server authentication methods require user input +(for example, password authentication, or entering a private key +passphrase), and others do not (e.g. a private key held in Pageant). + +If you use Plink to run an interactive login session, and if Plink +authenticates without needing any user interaction, and if the server +is malicious or compromised, it could try to trick you into giving it +authentication data that should not go to the server (such as your +private key passphrase), by sending what \e{looks} like one of Plink's +local prompts, as if Plink had not already authenticated. + +To protect against this, Plink's default policy is to finish the +authentication phase with a final trivial prompt looking like this: + +\c Access granted. Press Return to begin session. + +so that if you saw anything that looked like an authentication prompt +\e{after} that line, you would know it was not from Plink. + +That extra interactive step is inconvenient. So Plink will turn it off +in as many situations as it can: + +\b If Plink's standard input is not pointing at a console or terminal +device \dash for example, if you're using Plink as a transport for +some automated application like version control \dash then you +\e{can't} type passphrases into the server anyway. In that situation, +Plink won't try to protect you from the server trying to fool you into +doing so. + +\b If Plink is in batch mode (see \k{plink-usage-batch}), then it +\e{never} does any interactive authentication. So anything looking +like an interactive authentication prompt is automatically suspect, +and so Plink omits the anti-spoofing prompt. + +But if you still find the protective prompt inconvenient, and you +trust the server not to try a trick like this, you can turn it off +using the \cq{-no-antispoof} option. + \H{plink-batch} Using Plink in \i{batch files} and \i{scripts} Once you have set up Plink to be able to log in to a remote server diff --git a/putty.h b/putty.h index 3d30cf08..de4d410a 100644 --- a/putty.h +++ b/putty.h @@ -1926,7 +1926,7 @@ bool have_ssh_host_key(const char *host, int port, const char *keytype); * Exports from console frontends (wincons.c, uxcons.c) * that aren't equivalents to things in windlg.c et al. */ -extern bool console_batch_mode; +extern bool console_batch_mode, console_antispoof_prompt; int console_get_userpass_input(prompts_t *p); bool is_interactive(void); void console_print_error_msg(const char *prefix, const char *msg); diff --git a/ssh2connection-client.c b/ssh2connection-client.c index b5a07c64..6a7a6064 100644 --- a/ssh2connection-client.c +++ b/ssh2connection-client.c @@ -474,3 +474,8 @@ void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) put_uint32(pktout, 0); /* pixel height */ pq_push(s->ppl.out_pq, pktout); } + +bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) +{ + return !seat_set_trust_status(s->ppl.seat, false); +} diff --git a/ssh2connection-server.c b/ssh2connection-server.c index 38c37e14..274d9e3e 100644 --- a/ssh2connection-server.c +++ b/ssh2connection-server.c @@ -297,3 +297,8 @@ void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) { unreachable("Should never be called in the server"); } + +bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) +{ + return false; +} diff --git a/ssh2connection.c b/ssh2connection.c index 76c3ccfd..1a40ca7e 100644 --- a/ssh2connection.c +++ b/ssh2connection.c @@ -953,6 +953,41 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) if (s->connshare) share_activate(s->connshare, s->peer_verstring); + /* + * Signal the seat that authentication is done, so that it can + * deploy spoofing defences. If it doesn't have any, deploy our + * own fallback one. + * + * We do this here rather than at the end of userauth, because we + * might not have gone through userauth at all (if we're a + * connection-sharing downstream). + */ + if (ssh2_connection_need_antispoof_prompt(s)) { + s->antispoof_prompt = new_prompts(); + s->antispoof_prompt->to_server = true; + s->antispoof_prompt->from_server = false; + s->antispoof_prompt->name = dupstr("Authentication successful"); + add_prompt( + s->antispoof_prompt, + dupstr("Access granted. Press Return to begin session. "), false); + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, NULL); + while (1) { + while (s->antispoof_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); + + if (s->antispoof_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + free_prompts(s->antispoof_prompt); + } + /* * Enable port forwardings. */ diff --git a/ssh2connection.h b/ssh2connection.h index 1c70aca4..3858414c 100644 --- a/ssh2connection.h +++ b/ssh2connection.h @@ -37,6 +37,9 @@ struct ssh2_connection_state { PortFwdManager *portfwdmgr; bool portfwdmgr_configured; + prompts_t *antispoof_prompt; + int antispoof_ret; + const SftpServerVtable *sftpserver_vt; /* @@ -228,4 +231,6 @@ ChanopenResult ssh2_connection_parse_channel_open( bool ssh2_connection_parse_global_request( struct ssh2_connection_state *s, ptrlen type, PktIn *pktin); +bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s); + #endif /* PUTTY_SSH2CONNECTION_H */ diff --git a/unix/uxcons.c b/unix/uxcons.c index 8b966cd4..8e2a9085 100644 --- a/unix/uxcons.c +++ b/unix/uxcons.c @@ -414,8 +414,26 @@ static int console_askappend(LogPolicy *lp, Filename *filename, return 0; } +bool console_antispoof_prompt = true; bool console_set_trust_status(Seat *seat, bool trusted) { + if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + /* + * In batch mode, we don't need to worry about the server + * mimicking our interactive authentication, because the user + * already knows not to expect any. + * + * If standard input isn't connected to a terminal, likewise, + * because even if the server did send a spoof authentication + * prompt, the user couldn't respond to it via the terminal + * anyway. + * + * We also vacuously return success if the user has purposely + * disabled the antispoof prompt. + */ + return true; + } + return false; } diff --git a/unix/uxplink.c b/unix/uxplink.c index 5de61b22..5973b492 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -534,6 +534,8 @@ static void usage(void) "-no-sanitise-stderr, -no-sanitise-stdout\n"); printf(" do/don't strip control chars from standard " "output/error\n"); + printf(" -no-antispoof omit anti-spoofing prompt after " + "authentication\n"); printf(" -m file read remote command(s) from file\n"); printf(" -s remote command is an SSH subsystem (SSH-2 only)\n"); printf(" -N don't start a shell/command (SSH-2 only)\n"); @@ -678,6 +680,8 @@ int main(int argc, char **argv) } else if (!strcmp(p, "-no-sanitise-stderr") || !strcmp(p, "-no-sanitize-stderr")) { sanitise_stderr = FORCE_OFF; + } else if (!strcmp(p, "-no-antispoof")) { + console_antispoof_prompt = false; } else if (*p != '-') { strbuf *cmdbuf = strbuf_new(); diff --git a/windows/wincons.c b/windows/wincons.c index a8e382b8..b786372a 100644 --- a/windows/wincons.c +++ b/windows/wincons.c @@ -279,8 +279,31 @@ int console_confirm_weak_cached_hostkey( } } +bool is_interactive(void) +{ + return is_console_handle(GetStdHandle(STD_INPUT_HANDLE)); +} + +bool console_antispoof_prompt = true; bool console_set_trust_status(Seat *seat, bool trusted) { + if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + /* + * In batch mode, we don't need to worry about the server + * mimicking our interactive authentication, because the user + * already knows not to expect any. + * + * If standard input isn't connected to a terminal, likewise, + * because even if the server did send a spoof authentication + * prompt, the user couldn't respond to it via the terminal + * anyway. + * + * We also vacuously return success if the user has purposely + * disabled the antispoof prompt. + */ + return true; + } + return false; } diff --git a/windows/winplink.c b/windows/winplink.c index f149aea6..446de78d 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -171,6 +171,8 @@ static void usage(void) "-no-sanitise-stderr, -no-sanitise-stdout\n"); printf(" do/don't strip control chars from standard " "output/error\n"); + printf(" -no-antispoof omit anti-spoofing prompt after " + "authentication\n"); printf(" -m file read remote command(s) from file\n"); printf(" -s remote command is an SSH subsystem (SSH-2 only)\n"); printf(" -N don't start a shell/command (SSH-2 only)\n"); @@ -353,6 +355,8 @@ int main(int argc, char **argv) } else if (!strcmp(p, "-no-sanitise-stderr") || !strcmp(p, "-no-sanitize-stderr")) { sanitise_stderr = FORCE_OFF; + } else if (!strcmp(p, "-no-antispoof")) { + console_antispoof_prompt = false; } else if (*p != '-') { strbuf *cmdbuf = strbuf_new();