Compare commits

...
Sign in to create a new pull request.

11 commits

Author SHA1 Message Date
Dmitry Belyavskiy
8e64184089 Fix Terrapin attack
Resolves: CVE-2023-48795
2024-01-10 12:07:27 +01:00
Dmitry Belyavskiy
fdfba5c681 Forbid shell metasymbols in username/hostname
Resolves: CVE-2023-51385
2024-01-10 11:05:32 +01:00
Mattias Ellert
b70f344a02 Update gssapi-keyex patch for OpenSSH 9.0+
userauth_gsskeyex must have the same argument as userauth_gssapi
method_gsskeyex must have the same members as method_gssapi
2023-07-26 23:12:23 +02:00
Dmitry Belyavskiy
416a60e595 Fix remote code execution in ssh-agent PKCS#11 support
Resolves: CVE-2023-38408
2023-07-21 17:54:54 +02:00
Dmitry Belyavskiy
780a027baa Avoid possible self-DoS attack
Resolves: CVE-2023-25136
2023-04-14 12:54:47 +02:00
Timothée Ravier
07c7ff0601 Make sshd & sshd@ units want ssh-host-keys-migration.service
Enabling the unit via the presets does not enable it on
Silverblue/Kinoite/Sericea & IoT as we don't re-preset all units like
it's done in Fedora CoreOS.

See: https://pagure.io/workstation-ostree-config/pull-request/246

Instead, have the sshd & sshd@ service unit `Wants` the
ssh-host-keys-migration service unit so that it's pulled-in only when
sshd is effectively enabled and in all cases.

See: https://src.fedoraproject.org/rpms/fedora-release/pull-request/253

See: https://bugzilla.redhat.com/show_bug.cgi?id=2172956
See: https://src.fedoraproject.org/rpms/fedora-release/pull-request/252
2023-03-17 21:49:26 +01:00
Zoltan Fridrich
a007ed6cde Add sk-dummy subpackage for test purposes
Resolves: rhbz#2176795

Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
2023-03-13 13:37:06 +01:00
Dusty Mabe
f8a488880c Make ssh-host key migration less conditional
If there is a case where some host keys don't have correct
permissions then they won't get migrated. Let's make the
migration script attempt migration for the rest of the keys
too.
2023-03-07 09:33:05 +01:00
Dusty Mabe
2d651fde7d Mark /var/lib/.ssh-host-keys-migration as %ghost file 2023-03-07 09:33:05 +01:00
Dusty Mabe
ff60be4082 Use a service unit to strip ssh_keys group from host keys (rhbz#2172956)
Use a systemd service unit to strip the ssh_keys group and change the
mode for host keys. This ensure that this migration is done right before
the openssh server startup on all kind of systems, either RPM or
rpm-ostree based.

Use a marker file to only do this once. We need to keep this service
unit for two Fedora releases so we will be able to remove it in Fedora
40.

See: https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit
Fixes: 7a21555 Get rid of ssh_keys group for new installations
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2172956

Co-authored-by: Timothée Ravier <tim@siosm.fr>
2023-03-03 17:25:41 +01:00
Dusty Mabe
82b359a76e update date in changelog entry
This entry is out of chronological order, which means we get a
warning/error every time. I'm just updating here to the commitdate
of the commit, which puts everything back in chronological order.
2023-03-03 17:25:41 +01:00
10 changed files with 785 additions and 22 deletions

View file

@ -151,7 +151,7 @@ index 9351e042..d6446c0c 100644
+ * The 'gssapi_keyex' userauth mechanism.
+ */
+static int
+userauth_gsskeyex(struct ssh *ssh)
+userauth_gsskeyex(struct ssh *ssh, const char *method)
+{
+ Authctxt *authctxt = ssh->authctxt;
+ int r, authenticated = 0;
@ -212,19 +212,20 @@ index 9351e042..d6446c0c 100644
else
logit("GSSAPI MIC check failed");
@@ -326,6 +370,12 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh)
@@ -326,6 +370,13 @@ input_gssapi_mic(int type, u_int32_t plen, struct ssh *ssh)
return 0;
}
+Authmethod method_gsskeyex = {
+ "gssapi-keyex",
+ NULL,
+ userauth_gsskeyex,
+ &options.gss_authentication
+};
+
Authmethod method_gssapi = {
"gssapi-with-mic",
NULL,
NULL,
diff --git a/auth2.c b/auth2.c
index 0e776224..1c217268 100644
--- a/auth2.c
@ -384,7 +385,7 @@ index ebd0dbca..1bdac6a4 100644
/* Do channel operations unless rekeying in progress. */
- if (!ssh_packet_is_rekeying(ssh))
+ if (!ssh_packet_is_rekeying(ssh)) {
channel_after_poll(ssh, pfd, npfd_active);
channel_after_poll(ssh, pfd, npfd_active);
+#ifdef GSSAPI
+ if (options.gss_renewal_rekey &&
@ -396,8 +397,8 @@ index ebd0dbca..1bdac6a4 100644
+ }
+
/* Buffer input from the connection. */
if (conn_in_ready)
client_process_net_input(ssh);
if (conn_in_ready)
client_process_net_input(ssh);
diff --git a/configure.ac b/configure.ac
index b689db4b..efafb6bd 100644
--- a/configure.ac
@ -1370,8 +1371,8 @@ index ce85f043..574c7609 100644
+#ifdef GSSAPI
+ free(kex->gss_host);
+#endif /* GSSAPI */
sshbuf_free(kex->initial_sig);
sshkey_free(kex->initial_hostkey);
sshbuf_free(kex->initial_sig);
sshkey_free(kex->initial_hostkey);
free(kex->failed_choice);
diff --git a/kex.h b/kex.h
index a5ae6ac0..fe714141 100644
@ -3568,7 +3569,7 @@ index af00fb30..03bc87eb 100644
+# endif
+#endif /* WITH_OPENSSL */
ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
ssh->kex->verify_host_key=&verify_host_key_callback;
+#if defined(GSSAPI) && defined(WITH_OPENSSL)

View file

@ -0,0 +1,38 @@
diff --git a/compat.c b/compat.c
index 46dfe3a9c2e..478a9403eea 100644
--- a/compat.c
+++ b/compat.c
@@ -190,26 +190,26 @@ compat_pkalg_proposal(struct ssh *ssh, char *pkalg_prop)
char *
compat_kex_proposal(struct ssh *ssh, char *p)
{
- char *cp = NULL;
+ char *cp = NULL, *cp2 = NULL;
if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0)
return xstrdup(p);
debug2_f("original KEX proposal: %s", p);
if ((ssh->compat & SSH_BUG_CURVE25519PAD) != 0)
- if ((p = match_filter_denylist(p,
+ if ((cp = match_filter_denylist(p,
"curve25519-sha256@libssh.org")) == NULL)
fatal("match_filter_denylist failed");
if ((ssh->compat & SSH_OLD_DHGEX) != 0) {
- cp = p;
- if ((p = match_filter_denylist(p,
+ if ((cp2 = match_filter_denylist(cp ? cp : p,
"diffie-hellman-group-exchange-sha256,"
"diffie-hellman-group-exchange-sha1")) == NULL)
fatal("match_filter_denylist failed");
free(cp);
+ cp = cp2;
}
- debug2_f("compat KEX proposal: %s", p);
- if (*p == '\0')
+ if (cp == NULL || *cp == '\0')
fatal("No supported key exchange algorithms found");
- return p;
+ debug2_f("compat KEX proposal: %s", cp);
+ return cp;
}

View file

@ -0,0 +1,98 @@
diff --git a/ssh-agent.c b/ssh-agent.c
index 618bb198..8ea831f4 100644
diff -up openssh-9.3p1/ssh-agent.c.cve openssh-9.3p1/ssh-agent.c
--- openssh-9.3p1/ssh-agent.c.cve 2023-07-21 15:38:13.237276580 +0200
+++ openssh-9.3p1/ssh-agent.c 2023-07-21 15:41:30.269943569 +0200
@@ -169,6 +169,12 @@ char socket_dir[PATH_MAX];
/* Pattern-list of allowed PKCS#11/Security key paths */
static char *allowed_providers;
+/*
+ * Allows PKCS11 providers or SK keys that use non-internal providers to
+ * be added over a remote connection (identified by session-bind@openssh.com).
+ */
+static int remote_add_provider;
+
/* locking */
#define LOCK_SIZE 32
#define LOCK_SALT_SIZE 16
@@ -1228,6 +1234,12 @@ process_add_identity(SocketEntry *e)
if (strcasecmp(sk_provider, "internal") == 0) {
debug_f("internal provider");
} else {
+ if (e->nsession_ids != 0 && !remote_add_provider) {
+ verbose("failed add of SK provider \"%.100s\": "
+ "remote addition of providers is disabled",
+ sk_provider);
+ goto out;
+ }
if (realpath(sk_provider, canonical_provider) == NULL) {
verbose("failed provider \"%.100s\": "
"realpath: %s", sk_provider,
@@ -1368,7 +1380,7 @@ no_identities(SocketEntry *e)
#ifdef ENABLE_PKCS11
static char *
-sanitize_pkcs11_provider(const char *provider)
+sanitize_pkcs11_provider(SocketEntry *e, const char *provider)
{
struct pkcs11_uri *uri = NULL;
char *sane_uri, *module_path = NULL; /* default path */
@@ -1399,6 +1411,11 @@ sanitize_pkcs11_provider(const char *pro
module_path = strdup(provider); /* simple path */
if (module_path != NULL) { /* do not validate default NULL path in URI */
+ if (e->nsession_ids != 0 && !remote_add_provider) {
+ verbose("failed PKCS#11 add of \"%.100s\": remote addition of "
+ "providers is disabled", provider);
+ return NULL;
+ }
if (realpath(module_path, canonical_provider) == NULL) {
verbose("failed PKCS#11 provider \"%.100s\": realpath: %s",
module_path, strerror(errno));
@@ -1455,7 +1472,7 @@ process_add_smartcard_key(SocketEntry *e
goto send;
}
- sane_uri = sanitize_pkcs11_provider(provider);
+ sane_uri = sanitize_pkcs11_provider(e, provider);
if (sane_uri == NULL)
goto send;
@@ -1516,7 +1533,7 @@ process_remove_smartcard_key(SocketEntry
}
free(pin);
- sane_uri = sanitize_pkcs11_provider(provider);
+ sane_uri = sanitize_pkcs11_provider(e, provider);
if (sane_uri == NULL)
goto send;
@@ -2108,7 +2125,9 @@ main(int ac, char **av)
break;
case 'O':
if (strcmp(optarg, "no-restrict-websafe") == 0)
- restrict_websafe = 0;
+ restrict_websafe = 0;
+ else if (strcmp(optarg, "allow-remote-pkcs11") == 0)
+ remote_add_provider = 1;
else
fatal("Unknown -O option");
break;
diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
index 6be647ec..ebddf6c3 100644
--- a/ssh-pkcs11.c
+++ b/ssh-pkcs11.c
@@ -1537,10 +1537,8 @@ pkcs11_register_provider(char *provider_id, char *pin,
error("dlopen %s failed: %s", provider_module, dlerror());
goto fail;
}
- if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) {
- error("dlsym(C_GetFunctionList) failed: %s", dlerror());
- goto fail;
- }
+ if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL)
+ fatal("dlsym(C_GetFunctionList) failed: %s", dlerror());
p->module->handle = handle;
/* setup the pkcs11 callbacks */

View file

@ -0,0 +1,447 @@
diff --git a/PROTOCOL b/PROTOCOL
index d453c779..ded935eb 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -137,6 +137,32 @@ than as a named global or channel request to allow pings with very
described at:
http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.org.txt?h=curve25519
+1.9 transport: strict key exchange extension
+
+OpenSSH supports a number of transport-layer hardening measures under
+a "strict KEX" feature. This feature is signalled similarly to the
+RFC8308 ext-info feature: by including a additional algorithm in the
+initiial SSH2_MSG_KEXINIT kex_algorithms field. The client may append
+"kex-strict-c-v00@openssh.com" to its kex_algorithms and the server
+may append "kex-strict-s-v00@openssh.com". These pseudo-algorithms
+are only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored
+if they are present in subsequent SSH2_MSG_KEXINIT packets.
+
+When an endpoint that supports this extension observes this algorithm
+name in a peer's KEXINIT packet, it MUST make the following changes to
+the the protocol:
+
+a) During initial KEX, terminate the connection if any unexpected or
+ out-of-sequence packet is received. This includes terminating the
+ connection if the first packet received is not SSH2_MSG_KEXINIT.
+ Unexpected packets for the purpose of strict KEX include messages
+ that are otherwise valid at any time during the connection such as
+ SSH2_MSG_DEBUG and SSH2_MSG_IGNORE.
+b) After sending or receiving a SSH2_MSG_NEWKEYS message, reset the
+ packet sequence number to zero. This behaviour persists for the
+ duration of the connection (i.e. not just the first
+ SSH2_MSG_NEWKEYS).
+
2. Connection protocol changes
2.1. connection: Channel write close extension "eow@openssh.com"
diff --git a/kex.c b/kex.c
index aa5e792d..d478ff6e 100644
--- a/kex.c
+++ b/kex.c
@@ -65,7 +65,7 @@
#endif
/* prototype */
-static int kex_choose_conf(struct ssh *);
+static int kex_choose_conf(struct ssh *, uint32_t seq);
static int kex_input_newkeys(int, u_int32_t, struct ssh *);
static const char * const proposal_names[PROPOSAL_MAX] = {
@@ -177,6 +177,18 @@ kex_names_valid(const char *names)
return 1;
}
+/* returns non-zero if proposal contains any algorithm from algs */
+static int
+has_any_alg(const char *proposal, const char *algs)
+{
+ char *cp;
+
+ if ((cp = match_list(proposal, algs, NULL)) == NULL)
+ return 0;
+ free(cp);
+ return 1;
+}
+
/*
* Concatenate algorithm names, avoiding duplicates in the process.
* Caller must free returned string.
@@ -184,7 +196,7 @@ kex_names_valid(const char *names)
char *
kex_names_cat(const char *a, const char *b)
{
- char *ret = NULL, *tmp = NULL, *cp, *p, *m;
+ char *ret = NULL, *tmp = NULL, *cp, *p;
size_t len;
if (a == NULL || *a == '\0')
@@ -201,10 +213,8 @@ kex_names_cat(const char *a, const char *b)
}
strlcpy(ret, a, len);
for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) {
- if ((m = match_list(ret, p, NULL)) != NULL) {
- free(m);
+ if (has_any_alg(ret, p))
continue; /* Algorithm already present */
- }
if (strlcat(ret, ",", len) >= len ||
strlcat(ret, p, len) >= len) {
free(tmp);
@@ -466,7 +485,12 @@ kex_protocol_error(int type, u_int32_t seq, struct ssh *ssh)
{
int r;
- error("kex protocol error: type %d seq %u", type, seq);
+ /* If in strict mode, any unexpected message is an error */
+ if ((ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) {
+ ssh_packet_disconnect(ssh, "strict KEX violation: "
+ "unexpected packet type %u (seqnr %u)", type, seq);
+ }
+ error_f("type %u seq %u", type, seq);
if ((r = sshpkt_start(ssh, SSH2_MSG_UNIMPLEMENTED)) != 0 ||
(r = sshpkt_put_u32(ssh, seq)) != 0 ||
(r = sshpkt_send(ssh)) != 0)
@@ -548,6 +572,11 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_protocol_error);
if ((r = sshpkt_get_u32(ssh, &ninfo)) != 0)
return r;
+ if (ninfo >= 1024) {
+ error("SSH2_MSG_EXT_INFO with too many entries, expected "
+ "<=1024, received %u", ninfo);
+ return dispatch_protocol_error(type, seq, ssh);
+ }
for (i = 0; i < ninfo; i++) {
if ((r = sshpkt_get_cstring(ssh, &name, NULL)) != 0)
return r;
@@ -681,7 +705,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
error_f("no kex");
return SSH_ERR_INTERNAL_ERROR;
}
- ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, NULL);
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_protocol_error);
ptr = sshpkt_ptr(ssh, &dlen);
if ((r = sshbuf_put(kex->peer, ptr, dlen)) != 0)
return r;
@@ -717,7 +741,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
if (!(kex->flags & KEX_INIT_SENT))
if ((r = kex_send_kexinit(ssh)) != 0)
return r;
- if ((r = kex_choose_conf(ssh)) != 0)
+ if ((r = kex_choose_conf(ssh, seq)) != 0)
return r;
if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL)
@@ -981,20 +1005,14 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX])
return (1);
}
-/* returns non-zero if proposal contains any algorithm from algs */
static int
-has_any_alg(const char *proposal, const char *algs)
+kexalgs_contains(char **peer, const char *ext)
{
- char *cp;
-
- if ((cp = match_list(proposal, algs, NULL)) == NULL)
- return 0;
- free(cp);
- return 1;
+ return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext);
}
static int
-kex_choose_conf(struct ssh *ssh)
+kex_choose_conf(struct ssh *ssh, uint32_t seq)
{
struct kex *kex = ssh->kex;
struct newkeys *newkeys;
@@ -1019,13 +1037,23 @@ kex_choose_conf(struct ssh *ssh)
sprop=peer;
}
- /* Check whether client supports ext_info_c */
- if (kex->server && (kex->flags & KEX_INITIAL)) {
- char *ext;
-
- ext = match_list("ext-info-c", peer[PROPOSAL_KEX_ALGS], NULL);
- kex->ext_info_c = (ext != NULL);
- free(ext);
+ /* Check whether peer supports ext_info/kex_strict */
+ if ((kex->flags & KEX_INITIAL) != 0) {
+ if (kex->server) {
+ kex->ext_info_c = kexalgs_contains(peer, "ext-info-c");
+ kex->kex_strict = kexalgs_contains(peer,
+ "kex-strict-c-v00@openssh.com");
+ } else {
+ kex->kex_strict = kexalgs_contains(peer,
+ "kex-strict-s-v00@openssh.com");
+ }
+ if (kex->kex_strict) {
+ debug3_f("will use strict KEX ordering");
+ if (seq != 0)
+ ssh_packet_disconnect(ssh,
+ "strict KEX violation: "
+ "KEXINIT was not the first packet");
+ }
}
/* Check whether client supports rsa-sha2 algorithms */
diff --git a/kex.h b/kex.h
index 5f7ef784..272ebb43 100644
--- a/kex.h
+++ b/kex.h
@@ -149,6 +149,7 @@ struct kex {
u_int kex_type;
char *server_sig_algs;
int ext_info_c;
+ int kex_strict;
struct sshbuf *my;
struct sshbuf *peer;
struct sshbuf *client_version;
diff --git a/packet.c b/packet.c
index 52017def..beb214f9 100644
--- a/packet.c
+++ b/packet.c
@@ -1207,8 +1207,13 @@ ssh_packet_send2_wrapped(struct ssh *ssh)
sshbuf_dump(state->output, stderr);
#endif
/* increment sequence number for outgoing packets */
- if (++state->p_send.seqnr == 0)
+ if (++state->p_send.seqnr == 0) {
+ if ((ssh->kex->flags & KEX_INITIAL) != 0) {
+ ssh_packet_disconnect(ssh, "outgoing sequence number "
+ "wrapped during initial key exchange");
+ }
logit("outgoing seqnr wraps around");
+ }
if (++state->p_send.packets == 0)
if (!(ssh->compat & SSH_BUG_NOREKEY))
return SSH_ERR_NEED_REKEY;
@@ -1216,6 +1221,11 @@ ssh_packet_send2_wrapped(struct ssh *ssh)
state->p_send.bytes += len;
sshbuf_reset(state->outgoing_packet);
+ if (type == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) {
+ debug_f("resetting send seqnr %u", state->p_send.seqnr);
+ state->p_send.seqnr = 0;
+ }
+
if (type == SSH2_MSG_NEWKEYS)
r = ssh_set_newkeys(ssh, MODE_OUT);
else if (type == SSH2_MSG_USERAUTH_SUCCESS && state->server_side)
@@ -1344,8 +1354,7 @@ ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
/* Stay in the loop until we have received a complete packet. */
for (;;) {
/* Try to read a packet from the buffer. */
- r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p);
- if (r != 0)
+ if ((r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p)) != 0)
break;
/* If we got a packet, return it. */
if (*typep != SSH_MSG_NONE)
@@ -1629,10 +1615,16 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
if ((r = sshbuf_consume(state->input, mac->mac_len)) != 0)
goto out;
}
+
if (seqnr_p != NULL)
*seqnr_p = state->p_read.seqnr;
- if (++state->p_read.seqnr == 0)
+ if (++state->p_read.seqnr == 0) {
+ if ((ssh->kex->flags & KEX_INITIAL) != 0) {
+ ssh_packet_disconnect(ssh, "incoming sequence number "
+ "wrapped during initial key exchange");
+ }
logit("incoming seqnr wraps around");
+ }
if (++state->p_read.packets == 0)
if (!(ssh->compat & SSH_BUG_NOREKEY))
return SSH_ERR_NEED_REKEY;
@@ -1698,6 +1690,10 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
#endif
/* reset for next packet */
state->packlen = 0;
+ if (*typep == SSH2_MSG_NEWKEYS && ssh->kex->kex_strict) {
+ debug_f("resetting read seqnr %u", state->p_read.seqnr);
+ state->p_read.seqnr = 0;
+ }
if ((r = ssh_packet_check_rekey(ssh)) != 0)
return r;
@@ -1720,10 +1716,39 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
r = ssh_packet_read_poll2(ssh, typep, seqnr_p);
if (r != 0)
return r;
- if (*typep) {
- state->keep_alive_timeouts = 0;
- DBG(debug("received packet type %d", *typep));
+ if (*typep == 0) {
+ /* no message ready */
+ return 0;
}
+ state->keep_alive_timeouts = 0;
+ DBG(debug("received packet type %d", *typep));
+
+ /* Always process disconnect messages */
+ if (*typep == SSH2_MSG_DISCONNECT) {
+ if ((r = sshpkt_get_u32(ssh, &reason)) != 0 ||
+ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0)
+ return r;
+ /* Ignore normal client exit notifications */
+ do_log2(ssh->state->server_side &&
+ reason == SSH2_DISCONNECT_BY_APPLICATION ?
+ SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR,
+ "Received disconnect from %s port %d:"
+ "%u: %.400s", ssh_remote_ipaddr(ssh),
+ ssh_remote_port(ssh), reason, msg);
+ free(msg);
+ return SSH_ERR_DISCONNECTED;
+ }
+
+ /*
+ * Do not implicitly handle any messages here during initial
+ * KEX when in strict mode. They will be need to be allowed
+ * explicitly by the KEX dispatch table or they will generate
+ * protocol errors.
+ */
+ if (ssh->kex != NULL &&
+ (ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict)
+ return 0;
+ /* Implicitly handle transport-level messages */
switch (*typep) {
case SSH2_MSG_IGNORE:
debug3("Received SSH2_MSG_IGNORE");
@@ -1738,19 +1763,6 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
debug("Remote: %.900s", msg);
free(msg);
break;
- case SSH2_MSG_DISCONNECT:
- if ((r = sshpkt_get_u32(ssh, &reason)) != 0 ||
- (r = sshpkt_get_string(ssh, &msg, NULL)) != 0)
- return r;
- /* Ignore normal client exit notifications */
- do_log2(ssh->state->server_side &&
- reason == SSH2_DISCONNECT_BY_APPLICATION ?
- SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR,
- "Received disconnect from %s port %d:"
- "%u: %.400s", ssh_remote_ipaddr(ssh),
- ssh_remote_port(ssh), reason, msg);
- free(msg);
- return SSH_ERR_DISCONNECTED;
case SSH2_MSG_UNIMPLEMENTED:
if ((r = sshpkt_get_u32(ssh, &seqnr)) != 0)
return r;
@@ -2242,6 +2254,7 @@ kex_to_blob(struct sshbuf *m, struct kex *kex)
(r = sshbuf_put_u32(m, kex->hostkey_type)) != 0 ||
(r = sshbuf_put_u32(m, kex->hostkey_nid)) != 0 ||
(r = sshbuf_put_u32(m, kex->kex_type)) != 0 ||
+ (r = sshbuf_put_u32(m, kex->kex_strict)) != 0 ||
(r = sshbuf_put_stringb(m, kex->my)) != 0 ||
(r = sshbuf_put_stringb(m, kex->peer)) != 0 ||
(r = sshbuf_put_stringb(m, kex->client_version)) != 0 ||
@@ -2404,6 +2417,7 @@ kex_from_blob(struct sshbuf *m, struct kex **kexp)
(r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_type)) != 0 ||
(r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_nid)) != 0 ||
(r = sshbuf_get_u32(m, &kex->kex_type)) != 0 ||
+ (r = sshbuf_get_u32(m, &kex->kex_strict)) != 0 ||
(r = sshbuf_get_stringb(m, kex->my)) != 0 ||
(r = sshbuf_get_stringb(m, kex->peer)) != 0 ||
(r = sshbuf_get_stringb(m, kex->client_version)) != 0 ||
@@ -2732,6 +2746,7 @@ sshpkt_disconnect(struct ssh *ssh, const char *fmt,...)
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
+ debug2_f("sending SSH2_MSG_DISCONNECT: %s", buf);
if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 ||
(r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 ||
(r = sshpkt_put_cstring(ssh, buf)) != 0 ||
diff --git a/sshconnect2.c b/sshconnect2.c
index df6caf81..0cccbcc4 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -253,7 +253,8 @@ ssh_kex2(struct ssh *ssh, char *host, st
fatal_fr(r, "kex_assemble_namelist");
free(all_key);
- if ((s = kex_names_cat(options.kex_algorithms, "ext-info-c")) == NULL)
+ if ((s = kex_names_cat(options.kex_algorithms,
+ "ext-info-c,kex-strict-c-v00@openssh.com")) == NULL)
fatal_f("kex_names_cat");
myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh, s);
myproposal[PROPOSAL_ENC_ALGS_CTOS] =
@@ -358,7 +358,6 @@ struct cauthmethod {
};
static int input_userauth_service_accept(int, u_int32_t, struct ssh *);
-static int input_userauth_ext_info(int, u_int32_t, struct ssh *);
static int input_userauth_success(int, u_int32_t, struct ssh *);
static int input_userauth_failure(int, u_int32_t, struct ssh *);
static int input_userauth_banner(int, u_int32_t, struct ssh *);
@@ -472,7 +471,7 @@ ssh_userauth2(struct ssh *ssh, const char *local_user,
ssh->authctxt = &authctxt;
ssh_dispatch_init(ssh, &input_userauth_error);
- ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &input_userauth_ext_info);
+ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, kex_input_ext_info);
ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_ACCEPT, &input_userauth_service_accept);
ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt.success); /* loop until success */
pubkey_cleanup(ssh);
@@ -531,12 +530,6 @@ input_userauth_service_accept(int type, u_int32_t seq, struct ssh *ssh)
}
/* ARGSUSED */
-static int
-input_userauth_ext_info(int type, u_int32_t seqnr, struct ssh *ssh)
-{
- return kex_input_ext_info(type, seqnr, ssh);
-}
-
void
userauth(struct ssh *ssh, char *authlist)
{
@@ -615,6 +608,7 @@ input_userauth_success(int type, u_int32_t seq, struct ssh *ssh)
free(authctxt->methoddata);
authctxt->methoddata = NULL;
authctxt->success = 1; /* break out */
+ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, dispatch_protocol_error);
return 0;
}
diff -up openssh-8.7p1/sshd.c.kexstrict openssh-8.7p1/sshd.c
--- openssh-8.7p1/sshd.c.kexstrict 2023-11-27 13:19:18.855433602 +0100
+++ openssh-8.7p1/sshd.c 2023-11-27 13:28:10.441325314 +0100
@@ -2531,10 +2531,14 @@ do_ssh2_kex(struct ssh *ssh)
struct kex *kex;
char *hostkey_types = NULL;
char *prop_kex = NULL, *prop_enc = NULL, *prop_hostkey = NULL;
+ char *cp;
int r;
- myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh,
- options.kex_algorithms);
+ if ((cp = kex_names_cat(options.kex_algorithms,
+ "kex-strict-s-v00@openssh.com")) == NULL)
+ fatal_f("kex_names_cat");
+
+ myproposal[PROPOSAL_KEX_ALGS] = prop_kex = compat_kex_proposal(ssh, cp);
myproposal[PROPOSAL_ENC_ALGS_CTOS] =
myproposal[PROPOSAL_ENC_ALGS_STOC] = prop_enc =
compat_cipher_proposal(ssh, options.ciphers);
@@ -2586,7 +2586,7 @@ do_ssh2_kex(struct ssh *ssh)
if (gss && orig)
xasprintf(&newstr, "%s,%s", gss, orig);
else if (gss)
- newstr = gss;
+ xasprintf(&newstr, "%s,%s", gss, "kex-strict-s-v00@openssh.com");
else if (orig)
newstr = orig;
@@ -2650,6 +2654,7 @@ do_ssh2_kex(struct ssh *ssh)
#endif
free(prop_kex);
free(prop_enc);
+ free(cp);
free(prop_hostkey);
debug("KEX done");
}

View file

@ -0,0 +1,57 @@
diff --git a/ssh.c b/ssh.c
index 35c48e62..48d93ddf 100644
--- a/ssh.c
+++ b/ssh.c
@@ -626,6 +626,41 @@ ssh_conn_info_free(struct ssh_conn_info *cinfo)
free(cinfo);
}
+static int
+valid_hostname(const char *s)
+{
+ size_t i;
+
+ if (*s == '-')
+ return 0;
+ for (i = 0; s[i] != 0; i++) {
+ if (strchr("'`\"$\\;&<>|(){}", s[i]) != NULL ||
+ isspace((u_char)s[i]) || iscntrl((u_char)s[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static int
+valid_ruser(const char *s)
+{
+ size_t i;
+
+ if (*s == '-')
+ return 0;
+ for (i = 0; s[i] != 0; i++) {
+ if (strchr("'`\";&<>|(){}", s[i]) != NULL)
+ return 0;
+ /* Disallow '-' after whitespace */
+ if (isspace((u_char)s[i]) && s[i + 1] == '-')
+ return 0;
+ /* Disallow \ in last position */
+ if (s[i] == '\\' && s[i + 1] == '\0')
+ return 0;
+ }
+ return 1;
+}
+
/*
* Main program for the ssh client.
*/
@@ -1118,6 +1153,10 @@ main(int ac, char **av)
if (!host)
usage();
+ if (!valid_hostname(host))
+ fatal("hostname contains invalid characters");
+ if (options.user != NULL && !valid_ruser(options.user))
+ fatal("remote username contains invalid characters");
host_arg = xstrdup(host);
/* Initialize the command to execute on remote host. */

View file

@ -47,14 +47,14 @@
# Do not forget to bump pam_ssh_agent_auth release if you rewind the main package release to 1
%global openssh_ver 9.0p1
%global openssh_rel 10
%global openssh_rel 18
%global pam_ssh_agent_ver 0.10.4
%global pam_ssh_agent_rel 7
Summary: An open source implementation of SSH protocol version 2
Name: openssh
Version: %{openssh_ver}
Release: %{openssh_rel}%{?dist}.1
Release: %{openssh_rel}%{?dist}
URL: http://www.openssh.com/portable.html
#URL1: https://github.com/jbeverly/pam_ssh_agent_auth/
Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz
@ -74,6 +74,8 @@ Source15: sshd-keygen.target
Source16: ssh-agent.service
Source17: ssh-agent.socket
Source19: openssh-server-systemd-sysusers.conf
Source20: ssh-host-keys-migration.sh
Source21: ssh-host-keys-migration.service
#https://bugzilla.mindrot.org/show_bug.cgi?id=2581
Patch100: openssh-6.7p1-coverity.patch
@ -233,10 +235,12 @@ Patch1004: openssh-8.7p1-gssapi-auth.patch
# upstream MR:
# https://github.com/openssh/openssh-portable/pull/323
Patch1006: openssh-8.7p1-negotiate-supported-algs.patch
# CVE-2023-25136
Patch1007: openssh-8.7p1-CVE-2023-25136.patch
# downstream only
# we skip some ssh-rsa/ssh-dss tests to make native test suite pass
#Patch1100: openssh-8.8p1-skip-some-tests.patch
Patch1015: openssh-9.3p1-upstream-cve-2023-38408.patch
Patch1016: openssh-9.6p1-CVE-2023-51385.patch
Patch1017: openssh-9.6p1-CVE-2023-48795.patch
License: BSD
Requires: /sbin/nologin
@ -305,10 +309,14 @@ Requires: openssh = %{version}-%{release}
Summary: A passphrase dialog for OpenSSH and X
Requires: openssh = %{version}-%{release}
%package sk-dummy
Summary: OpenSSH SK driver for test purposes
Requires: openssh = %{version}-%{release}
%package -n pam_ssh_agent_auth
Summary: PAM module for authentication with ssh-agent
Version: %{pam_ssh_agent_ver}
Release: %{pam_ssh_agent_rel}.%{openssh_rel}%{?dist}.1
Release: %{pam_ssh_agent_rel}.%{openssh_rel}%{?dist}
License: BSD
%description
@ -345,6 +353,9 @@ OpenSSH is a free version of SSH (Secure SHell), a program for logging
into and executing commands on a remote machine. This package contains
an X11 passphrase dialog for OpenSSH.
%description sk-dummy
This package contains a test SK driver used for OpenSSH test purposes
%description -n pam_ssh_agent_auth
This package contains a PAM module which can be used to authenticate
users using ssh keys stored in a ssh-agent. Through the use of the
@ -431,8 +442,11 @@ popd
%patch1004 -p1 -b .gssapi-auth
%patch1006 -p1 -b .negotiate-supported-algs
%patch1007 -p1 -b .cve-2023-25136
#%patch1100 -p1 -b .skipsshrsadsstests
%patch -P 1015 -p1 -b .cve-2023-38408
%patch -P 1016 -p1 -b .cve-2023-51385
%patch -P 1017 -p1 -b .cve-2023-48795
%patch100 -p1 -b .coverity
@ -512,6 +526,7 @@ perl -pi -e "s|-lcrypto|%{_libdir}/libcrypto.a|g" Makefile
%endif
%make_build
make regress/misc/sk-dummy/sk-dummy.so
# Define a variable to toggle gtk2/gtk3 building. This is necessary
# because RPM doesn't handle nested %%if statements.
@ -582,6 +597,13 @@ install -m755 contrib/ssh-copy-id $RPM_BUILD_ROOT%{_bindir}/
install contrib/ssh-copy-id.1 $RPM_BUILD_ROOT%{_mandir}/man1/
install -d -m711 ${RPM_BUILD_ROOT}/%{_datadir}/empty.sshd
install -p -D -m 0644 %{SOURCE19} %{buildroot}%{_sysusersdir}/openssh-server.conf
# Migration service/script for Fedora 38 change to remove group ownership for standard host keys
# See https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit
install -m744 %{SOURCE20} $RPM_BUILD_ROOT/%{_libexecdir}/openssh/ssh-host-keys-migration.sh
# Pulled-in via a `Wants=` in `sshd.service` & `sshd@.service`
install -m644 %{SOURCE21} $RPM_BUILD_ROOT/%{_unitdir}/ssh-host-keys-migration.service
install -d $RPM_BUILD_ROOT/%{_localstatedir}/lib
touch $RPM_BUILD_ROOT/%{_localstatedir}/lib/.ssh-host-keys-migration
%if ! %{no_gnome_askpass}
install contrib/gnome-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/gnome-ssh-askpass
@ -606,15 +628,21 @@ pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}
popd
%endif
install -m 755 -d $RPM_BUILD_ROOT%{_libdir}/sshtest/
install -m 755 regress/misc/sk-dummy/sk-dummy.so $RPM_BUILD_ROOT%{_libdir}/sshtest
%pre server
%sysusers_create_compat %{SOURCE19}
# Migration scriptlet for Fedora 38/39
# We want to remove group ownership for standard host keys if they exist
test -f /etc/ssh/ssh_host_rsa_key && /usr/bin/chmod g-r /etc/ssh/ssh_host_rsa_key || :
test -f /etc/ssh/ssh_host_ecdsa_key && /usr/bin/chmod g-r /etc/ssh/ssh_host_ecdsa_key || :
test -f /etc/ssh/ssh_host_ed25519_key && /usr/bin/chmod g-r /etc/ssh/ssh_host_ed25519_key || :
%post server
if [ $1 -gt 1 ]; then
# In the case of an upgrade (never true on OSTree systems) run the migration
# script for Fedora 38 to remove group ownership for host keys.
%{_libexecdir}/openssh/ssh-host-keys-migration.sh
# Prevent the systemd unit that performs the same service (useful for
# OSTree systems) from running.
touch /var/lib/.ssh-host-keys-migration
fi
%systemd_post sshd.service sshd.socket
# Migration scriptlet for Fedora 31 and 32 installations to sshd_config
# drop-in directory (in F32+).
@ -699,6 +727,9 @@ test -f %{sysconfig_anaconda} && \
%attr(0644,root,root) %{_unitdir}/sshd-keygen@.service
%attr(0644,root,root) %{_unitdir}/sshd-keygen.target
%attr(0644,root,root) %{_sysusersdir}/openssh-server.conf
%attr(0644,root,root) %{_unitdir}/ssh-host-keys-migration.service
%attr(0744,root,root) %{_libexecdir}/openssh/ssh-host-keys-migration.sh
%ghost %attr(0644,root,root) %{_localstatedir}/lib/.ssh-host-keys-migration
%files keycat
%doc HOWTO.ssh-keycat
@ -712,6 +743,9 @@ test -f %{sysconfig_anaconda} && \
%attr(0755,root,root) %{_libexecdir}/openssh/ssh-askpass
%endif
%files sk-dummy
%attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so
%if %{pam_ssh_agent}
%files -n pam_ssh_agent_auth
%license pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver}/OPENSSH_LICENSE
@ -720,7 +754,38 @@ test -f %{sysconfig_anaconda} && \
%endif
%changelog
* Fri Dec 02 2022 Dmitry Belyavskiy <dbelyavs@redhat.com> - 9.0p1-10
* Wed Jan 10 2024 Dmitry Belyavskiy <dbelyavs@redhat.com> - 9.0p1-18
- Forbid shell metasymbols in username/hostname
Resolves: CVE-2023-51385
- Fix Terrapin attack
Resolves: CVE-2023-48795
* Wed Jul 26 2023 Mattias Ellert <mattias.ellert@physics.uu.se> - 9.0p1-17
- Update gssapi-keyex patch for OpenSSH 9.0+
* Fri Jul 21 2023 Dmitry Belyavskiy <dbelyavs@redhat.com> - 9.0p1-16
- Fix remote code execution in ssh-agent PKCS#11 support
Resolves: CVE-2023-38408
* Fri Apr 14 2023 Dmitry Belyavskiy <dbelyavs@redhat.com> - 9.0p1-15
- Avoid possible self-DoS attack
Resolves: CVE-2023-25136
* Tue Mar 14 2023 Timothée Ravier <tim@siosm.fr> - 9.0p1-14
- Make sshd & sshd@ units want ssh-host-keys-migration.service
* Mon Mar 13 2023 Zoltan Fridrich <zfridric@redhat.com> - 9.0p1-13
- Add sk-dummy subpackage for test purposes (rhbz#2176795)
* Mon Mar 06 2023 Dusty Mabe <dusty@dustymabe.com> - 9.0p1-12
- Mark /var/lib/.ssh-host-keys-migration as %ghost file
- Make ssh-host key migration less conditional
* Wed Mar 01 2023 Dusty Mabe <dusty@dustymabe.com> - 9.0p1-11
- Provide a systemd unit for restoring default host key permissions (rhbz#2172956)
- Co-Authored by Timothée Ravier <tim@siosm.fr>
* Mon Jan 23 2023 Dmitry Belyavskiy <dbelyavs@redhat.com> - 9.0p1-10
- Restore upstream behaviour and default host key permissions (rhbz#2141272)
* Thu Jan 19 2023 Fedora Release Engineering <releng@fedoraproject.org> - 9.0p1-9.1

View file

@ -0,0 +1,15 @@
[Unit]
Description=Update OpenSSH host key permissions
Documentation=https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit
Before=sshd.service
After=ssh-keygen.target
ConditionPathExists=!/var/lib/.ssh-host-keys-migration
[Service]
Type=oneshot
ExecStart=-/usr/libexec/openssh/ssh-host-keys-migration.sh
ExecStart=touch /var/lib/.ssh-host-keys-migration
RemainAfterExit=yes
[Install]
WantedBy=sshd.service

View file

@ -0,0 +1,36 @@
#!/usr/bin/bash
set -eu -o pipefail
# Detect existing non-conforming host keys and perform the permissions migration
# https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit
#
# Example output looks like:
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# @ WARNING: UNPROTECTED PRIVATE KEY FILE! @
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# Permissions 0640 for '/etc/ssh/ssh_host_rsa_key' are too open.
# It is required that your private key files are NOT accessible by others.
# This private key will be ignored.
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# @ WARNING: UNPROTECTED PRIVATE KEY FILE! @
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# Permissions 0640 for '/etc/ssh/ssh_host_ecdsa_key' are too open.
# It is required that your private key files are NOT accessible by others.
# This private key will be ignored.
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# @ WARNING: UNPROTECTED PRIVATE KEY FILE! @
# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# Permissions 0640 for '/etc/ssh/ssh_host_ed25519_key' are too open.
# It is required that your private key files are NOT accessible by others.
# This private key will be ignored.
# sshd: no hostkeys available -- exiting.
#
output="$(sshd -T 2>&1 || true)" # expected to fail
while read line; do
if [[ $line =~ ^Permissions\ [0-9]+\ for\ \'(.*)\'\ are\ too\ open. ]]; then
keyfile=${BASH_REMATCH[1]}
echo $line
echo -e "\t-> changing permissions on $keyfile"
chmod --verbose g-r $keyfile
chown --verbose root:root $keyfile
fi
done <<< "$output"

View file

@ -3,6 +3,9 @@ Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.target
Wants=sshd-keygen.target
# Migration for Fedora 38 change to remove group ownership for standard host keys
# See https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit
Wants=ssh-host-keys-migration.service
[Service]
Type=notify

View file

@ -3,6 +3,9 @@ Description=OpenSSH per-connection server daemon
Documentation=man:sshd(8) man:sshd_config(5)
Wants=sshd-keygen.target
After=sshd-keygen.target
# Migration for Fedora 38 change to remove group ownership for standard host keys
# See https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit
Wants=ssh-host-keys-migration.service
[Service]
EnvironmentFile=-/etc/sysconfig/sshd