1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Add an interactive anti-spoofing prompt in Plink.

At the point when we change over the seat's trust status to untrusted
for the last time, to finish authentication, Plink will now present a
final interactive prompt saying 'Press Return to begin session'. This
is a hint that anything after that that resembles an auth prompt
should be treated with suspicion, because _PuTTY_ thinks it's finished
authenticating.

This is of course an annoying inconvenience for interactive users, so
I've tried to reduce its impact as much as I can. It doesn't happen in
GUI PuTTY at all (because the trust sigil system is used instead); it
doesn't happen if you use plink -batch (because then the user already
knows that they _never_ expect an interactive prompt); and it doesn't
happen if Plink's standard input is being redirected from anywhere
other than the terminal / console (because then it would be pointless
for the server to try to scam passphrases out of the user anyway,
since the user isn't in a position to enter one in response to a spoof
prompt). So it should only happen to people who are using Plink in a
terminal for interactive login purposes, and that's not _really_ what
I ever intended Plink to be used for (which is why it's never had any
out-of-band control UI like OpenSSH's ~ system).

If anyone _still_ doesn't like this new prompt, it can also be turned
off using the new -no-antispoof flag, if the user is willing to
knowingly assume the risk.
This commit is contained in:
Simon Tatham 2019-03-10 14:42:33 +00:00
parent 76d8d363be
commit 514796b7e4
10 changed files with 140 additions and 1 deletions

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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.
*/

View File

@ -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 */

View File

@ -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;
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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();