358 lines
11 KiB
Diff
358 lines
11 KiB
Diff
|
diff --git a/auth.c b/auth.c
|
||
|
index 00b168b4..8ee93581 100644
|
||
|
--- a/auth.c
|
||
|
+++ b/auth.c
|
||
|
@@ -729,118 +729,6 @@ fakepw(void)
|
||
|
return (&fake);
|
||
|
}
|
||
|
|
||
|
-/*
|
||
|
- * Returns the remote DNS hostname as a string. The returned string must not
|
||
|
- * be freed. NB. this will usually trigger a DNS query the first time it is
|
||
|
- * called.
|
||
|
- * This function does additional checks on the hostname to mitigate some
|
||
|
- * attacks on based on conflation of hostnames and IP addresses.
|
||
|
- */
|
||
|
-
|
||
|
-static char *
|
||
|
-remote_hostname(struct ssh *ssh)
|
||
|
-{
|
||
|
- struct sockaddr_storage from;
|
||
|
- socklen_t fromlen;
|
||
|
- struct addrinfo hints, *ai, *aitop;
|
||
|
- char name[NI_MAXHOST], ntop2[NI_MAXHOST];
|
||
|
- const char *ntop = ssh_remote_ipaddr(ssh);
|
||
|
-
|
||
|
- /* Get IP address of client. */
|
||
|
- fromlen = sizeof(from);
|
||
|
- memset(&from, 0, sizeof(from));
|
||
|
- if (getpeername(ssh_packet_get_connection_in(ssh),
|
||
|
- (struct sockaddr *)&from, &fromlen) == -1) {
|
||
|
- debug("getpeername failed: %.100s", strerror(errno));
|
||
|
- return xstrdup(ntop);
|
||
|
- }
|
||
|
-
|
||
|
- ipv64_normalise_mapped(&from, &fromlen);
|
||
|
- if (from.ss_family == AF_INET6)
|
||
|
- fromlen = sizeof(struct sockaddr_in6);
|
||
|
-
|
||
|
- debug3("Trying to reverse map address %.100s.", ntop);
|
||
|
- /* Map the IP address to a host name. */
|
||
|
- if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
|
||
|
- NULL, 0, NI_NAMEREQD) != 0) {
|
||
|
- /* Host name not found. Use ip address. */
|
||
|
- return xstrdup(ntop);
|
||
|
- }
|
||
|
-
|
||
|
- /*
|
||
|
- * if reverse lookup result looks like a numeric hostname,
|
||
|
- * someone is trying to trick us by PTR record like following:
|
||
|
- * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5
|
||
|
- */
|
||
|
- memset(&hints, 0, sizeof(hints));
|
||
|
- hints.ai_socktype = SOCK_DGRAM; /*dummy*/
|
||
|
- hints.ai_flags = AI_NUMERICHOST;
|
||
|
- if (getaddrinfo(name, NULL, &hints, &ai) == 0) {
|
||
|
- logit("Nasty PTR record \"%s\" is set up for %s, ignoring",
|
||
|
- name, ntop);
|
||
|
- freeaddrinfo(ai);
|
||
|
- return xstrdup(ntop);
|
||
|
- }
|
||
|
-
|
||
|
- /* Names are stored in lowercase. */
|
||
|
- lowercase(name);
|
||
|
-
|
||
|
- /*
|
||
|
- * Map it back to an IP address and check that the given
|
||
|
- * address actually is an address of this host. This is
|
||
|
- * necessary because anyone with access to a name server can
|
||
|
- * define arbitrary names for an IP address. Mapping from
|
||
|
- * name to IP address can be trusted better (but can still be
|
||
|
- * fooled if the intruder has access to the name server of
|
||
|
- * the domain).
|
||
|
- */
|
||
|
- memset(&hints, 0, sizeof(hints));
|
||
|
- hints.ai_family = from.ss_family;
|
||
|
- hints.ai_socktype = SOCK_STREAM;
|
||
|
- if (getaddrinfo(name, NULL, &hints, &aitop) != 0) {
|
||
|
- logit("reverse mapping checking getaddrinfo for %.700s "
|
||
|
- "[%s] failed.", name, ntop);
|
||
|
- return xstrdup(ntop);
|
||
|
- }
|
||
|
- /* Look for the address from the list of addresses. */
|
||
|
- for (ai = aitop; ai; ai = ai->ai_next) {
|
||
|
- if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2,
|
||
|
- sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 &&
|
||
|
- (strcmp(ntop, ntop2) == 0))
|
||
|
- break;
|
||
|
- }
|
||
|
- freeaddrinfo(aitop);
|
||
|
- /* If we reached the end of the list, the address was not there. */
|
||
|
- if (ai == NULL) {
|
||
|
- /* Address not found for the host name. */
|
||
|
- logit("Address %.100s maps to %.600s, but this does not "
|
||
|
- "map back to the address.", ntop, name);
|
||
|
- return xstrdup(ntop);
|
||
|
- }
|
||
|
- return xstrdup(name);
|
||
|
-}
|
||
|
-
|
||
|
-/*
|
||
|
- * Return the canonical name of the host in the other side of the current
|
||
|
- * connection. The host name is cached, so it is efficient to call this
|
||
|
- * several times.
|
||
|
- */
|
||
|
-
|
||
|
-const char *
|
||
|
-auth_get_canonical_hostname(struct ssh *ssh, int use_dns)
|
||
|
-{
|
||
|
- static char *dnsname;
|
||
|
-
|
||
|
- if (!use_dns)
|
||
|
- return ssh_remote_ipaddr(ssh);
|
||
|
- else if (dnsname != NULL)
|
||
|
- return dnsname;
|
||
|
- else {
|
||
|
- dnsname = remote_hostname(ssh);
|
||
|
- return dnsname;
|
||
|
- }
|
||
|
-}
|
||
|
-
|
||
|
/* These functions link key/cert options to the auth framework */
|
||
|
|
||
|
/* Log sshauthopt options locally and (optionally) for remote transmission */
|
||
|
diff --git a/canohost.c b/canohost.c
|
||
|
index a810da0e..18e9d8d4 100644
|
||
|
--- a/canohost.c
|
||
|
+++ b/canohost.c
|
||
|
@@ -202,3 +202,117 @@ get_local_port(int sock)
|
||
|
{
|
||
|
return get_sock_port(sock, 1);
|
||
|
}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Returns the remote DNS hostname as a string. The returned string must not
|
||
|
+ * be freed. NB. this will usually trigger a DNS query the first time it is
|
||
|
+ * called.
|
||
|
+ * This function does additional checks on the hostname to mitigate some
|
||
|
+ * attacks on legacy rhosts-style authentication.
|
||
|
+ * XXX is RhostsRSAAuthentication vulnerable to these?
|
||
|
+ * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?)
|
||
|
+ */
|
||
|
+
|
||
|
+static char *
|
||
|
+remote_hostname(struct ssh *ssh)
|
||
|
+{
|
||
|
+ struct sockaddr_storage from;
|
||
|
+ socklen_t fromlen;
|
||
|
+ struct addrinfo hints, *ai, *aitop;
|
||
|
+ char name[NI_MAXHOST], ntop2[NI_MAXHOST];
|
||
|
+ const char *ntop = ssh_remote_ipaddr(ssh);
|
||
|
+
|
||
|
+ /* Get IP address of client. */
|
||
|
+ fromlen = sizeof(from);
|
||
|
+ memset(&from, 0, sizeof(from));
|
||
|
+ if (getpeername(ssh_packet_get_connection_in(ssh),
|
||
|
+ (struct sockaddr *)&from, &fromlen) == -1) {
|
||
|
+ debug("getpeername failed: %.100s", strerror(errno));
|
||
|
+ return xstrdup(ntop);
|
||
|
+ }
|
||
|
+
|
||
|
+ ipv64_normalise_mapped(&from, &fromlen);
|
||
|
+ if (from.ss_family == AF_INET6)
|
||
|
+ fromlen = sizeof(struct sockaddr_in6);
|
||
|
+
|
||
|
+ debug3("Trying to reverse map address %.100s.", ntop);
|
||
|
+ /* Map the IP address to a host name. */
|
||
|
+ if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
|
||
|
+ NULL, 0, NI_NAMEREQD) != 0) {
|
||
|
+ /* Host name not found. Use ip address. */
|
||
|
+ return xstrdup(ntop);
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * if reverse lookup result looks like a numeric hostname,
|
||
|
+ * someone is trying to trick us by PTR record like following:
|
||
|
+ * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5
|
||
|
+ */
|
||
|
+ memset(&hints, 0, sizeof(hints));
|
||
|
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
|
||
|
+ hints.ai_flags = AI_NUMERICHOST;
|
||
|
+ if (getaddrinfo(name, NULL, &hints, &ai) == 0) {
|
||
|
+ logit("Nasty PTR record \"%s\" is set up for %s, ignoring",
|
||
|
+ name, ntop);
|
||
|
+ freeaddrinfo(ai);
|
||
|
+ return xstrdup(ntop);
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Names are stored in lowercase. */
|
||
|
+ lowercase(name);
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Map it back to an IP address and check that the given
|
||
|
+ * address actually is an address of this host. This is
|
||
|
+ * necessary because anyone with access to a name server can
|
||
|
+ * define arbitrary names for an IP address. Mapping from
|
||
|
+ * name to IP address can be trusted better (but can still be
|
||
|
+ * fooled if the intruder has access to the name server of
|
||
|
+ * the domain).
|
||
|
+ */
|
||
|
+ memset(&hints, 0, sizeof(hints));
|
||
|
+ hints.ai_family = from.ss_family;
|
||
|
+ hints.ai_socktype = SOCK_STREAM;
|
||
|
+ if (getaddrinfo(name, NULL, &hints, &aitop) != 0) {
|
||
|
+ logit("reverse mapping checking getaddrinfo for %.700s "
|
||
|
+ "[%s] failed.", name, ntop);
|
||
|
+ return xstrdup(ntop);
|
||
|
+ }
|
||
|
+ /* Look for the address from the list of addresses. */
|
||
|
+ for (ai = aitop; ai; ai = ai->ai_next) {
|
||
|
+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2,
|
||
|
+ sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 &&
|
||
|
+ (strcmp(ntop, ntop2) == 0))
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ freeaddrinfo(aitop);
|
||
|
+ /* If we reached the end of the list, the address was not there. */
|
||
|
+ if (ai == NULL) {
|
||
|
+ /* Address not found for the host name. */
|
||
|
+ logit("Address %.100s maps to %.600s, but this does not "
|
||
|
+ "map back to the address.", ntop, name);
|
||
|
+ return xstrdup(ntop);
|
||
|
+ }
|
||
|
+ return xstrdup(name);
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Return the canonical name of the host in the other side of the current
|
||
|
+ * connection. The host name is cached, so it is efficient to call this
|
||
|
+ * several times.
|
||
|
+ */
|
||
|
+
|
||
|
+const char *
|
||
|
+auth_get_canonical_hostname(struct ssh *ssh, int use_dns)
|
||
|
+{
|
||
|
+ static char *dnsname;
|
||
|
+
|
||
|
+ if (!use_dns)
|
||
|
+ return ssh_remote_ipaddr(ssh);
|
||
|
+ else if (dnsname != NULL)
|
||
|
+ return dnsname;
|
||
|
+ else {
|
||
|
+ dnsname = remote_hostname(ssh);
|
||
|
+ return dnsname;
|
||
|
+ }
|
||
|
+}
|
||
|
diff --git a/readconf.c b/readconf.c
|
||
|
index 03369a08..b45898ce 100644
|
||
|
--- a/readconf.c
|
||
|
+++ b/readconf.c
|
||
|
@@ -161,6 +161,7 @@ typedef enum {
|
||
|
oClearAllForwardings, oNoHostAuthenticationForLocalhost,
|
||
|
oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
|
||
|
oAddressFamily, oGssAuthentication, oGssDelegateCreds,
|
||
|
+ oGssTrustDns,
|
||
|
oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
|
||
|
oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist,
|
||
|
oHashKnownHosts,
|
||
|
@@ -207,9 +208,11 @@ static struct {
|
||
|
#if defined(GSSAPI)
|
||
|
{ "gssapiauthentication", oGssAuthentication },
|
||
|
{ "gssapidelegatecredentials", oGssDelegateCreds },
|
||
|
+ { "gssapitrustdns", oGssTrustDns },
|
||
|
# else
|
||
|
{ "gssapiauthentication", oUnsupported },
|
||
|
{ "gssapidelegatecredentials", oUnsupported },
|
||
|
+ { "gssapitrustdns", oUnsupported },
|
||
|
#endif
|
||
|
#ifdef ENABLE_PKCS11
|
||
|
{ "pkcs11provider", oPKCS11Provider },
|
||
|
@@ -1117,6 +1120,10 @@ parse_time:
|
||
|
intptr = &options->gss_deleg_creds;
|
||
|
goto parse_flag;
|
||
|
|
||
|
+ case oGssTrustDns:
|
||
|
+ intptr = &options->gss_trust_dns;
|
||
|
+ goto parse_flag;
|
||
|
+
|
||
|
case oBatchMode:
|
||
|
intptr = &options->batch_mode;
|
||
|
goto parse_flag;
|
||
|
@@ -2307,6 +2314,7 @@ initialize_options(Options * options)
|
||
|
options->pubkey_authentication = -1;
|
||
|
options->gss_authentication = -1;
|
||
|
options->gss_deleg_creds = -1;
|
||
|
+ options->gss_trust_dns = -1;
|
||
|
options->password_authentication = -1;
|
||
|
options->kbd_interactive_authentication = -1;
|
||
|
options->kbd_interactive_devices = NULL;
|
||
|
@@ -2465,6 +2473,8 @@ fill_default_options(Options * options)
|
||
|
options->gss_authentication = 0;
|
||
|
if (options->gss_deleg_creds == -1)
|
||
|
options->gss_deleg_creds = 0;
|
||
|
+ if (options->gss_trust_dns == -1)
|
||
|
+ options->gss_trust_dns = 0;
|
||
|
if (options->password_authentication == -1)
|
||
|
options->password_authentication = 1;
|
||
|
if (options->kbd_interactive_authentication == -1)
|
||
|
diff --git a/readconf.h b/readconf.h
|
||
|
index f7d53b06..c3a91898 100644
|
||
|
--- a/readconf.h
|
||
|
+++ b/readconf.h
|
||
|
@@ -40,6 +40,7 @@ typedef struct {
|
||
|
int hostbased_authentication; /* ssh2's rhosts_rsa */
|
||
|
int gss_authentication; /* Try GSS authentication */
|
||
|
int gss_deleg_creds; /* Delegate GSS credentials */
|
||
|
+ int gss_trust_dns; /* Trust DNS for GSS canonicalization */
|
||
|
int password_authentication; /* Try password
|
||
|
* authentication. */
|
||
|
int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
|
||
|
diff --git a/ssh_config.5 b/ssh_config.5
|
||
|
index cd0eea86..27101943 100644
|
||
|
--- a/ssh_config.5
|
||
|
+++ b/ssh_config.5
|
||
|
@@ -832,6 +832,16 @@ The default is
|
||
|
Forward (delegate) credentials to the server.
|
||
|
The default is
|
||
|
.Cm no .
|
||
|
+Note that this option applies to protocol version 2 connections using GSSAPI.
|
||
|
+.It Cm GSSAPITrustDns
|
||
|
+Set to
|
||
|
+.Dq yes to indicate that the DNS is trusted to securely canonicalize
|
||
|
+the name of the host being connected to. If
|
||
|
+.Dq no, the hostname entered on the
|
||
|
+command line will be passed untouched to the GSSAPI library.
|
||
|
+The default is
|
||
|
+.Dq no .
|
||
|
+This option only applies to protocol version 2 connections using GSSAPI.
|
||
|
.It Cm HashKnownHosts
|
||
|
Indicates that
|
||
|
.Xr ssh 1
|
||
|
diff --git a/sshconnect2.c b/sshconnect2.c
|
||
|
index fea50fab..aeff639b 100644
|
||
|
--- a/sshconnect2.c
|
||
|
+++ b/sshconnect2.c
|
||
|
@@ -776,6 +776,13 @@ userauth_gssapi(struct ssh *ssh)
|
||
|
OM_uint32 min;
|
||
|
int r, ok = 0;
|
||
|
gss_OID mech = NULL;
|
||
|
+ const char *gss_host;
|
||
|
+
|
||
|
+ if (options.gss_trust_dns) {
|
||
|
+ extern const char *auth_get_canonical_hostname(struct ssh *ssh, int use_dns);
|
||
|
+ gss_host = auth_get_canonical_hostname(ssh, 1);
|
||
|
+ } else
|
||
|
+ gss_host = authctxt->host;
|
||
|
|
||
|
/* Try one GSSAPI method at a time, rather than sending them all at
|
||
|
* once. */
|
||
|
@@ -790,7 +797,7 @@ userauth_gssapi(struct ssh *ssh)
|
||
|
elements[authctxt->mech_tried];
|
||
|
/* My DER encoding requires length<128 */
|
||
|
if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt,
|
||
|
- mech, authctxt->host)) {
|
||
|
+ mech, gss_host)) {
|
||
|
ok = 1; /* Mechanism works */
|
||
|
} else {
|
||
|
authctxt->mech_tried++;
|