diff --git a/CHANGELOG b/CHANGELOG index e5edcccaf..94eda62a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ #Nmap Changelog ($Id$); -*-text-*- +o [ncat][GH#1214][GH#1230][GH#1439] New ncat option provides control over + whether proxy destinations are resolved by the remote proxy server or + locally, by Ncat itself. See option --proxy-dns. [nnposter] + o [NSE][GH#1478] Updated script ftp-syst to prevent potential endless looping. [nnposter] diff --git a/ncat/docs/ncat.usage.txt b/ncat/docs/ncat.usage.txt index 88627850a..73f4b55e3 100644 --- a/ncat/docs/ncat.usage.txt +++ b/ncat/docs/ncat.usage.txt @@ -42,6 +42,7 @@ Options taking a time assume seconds. Append 'ms' for milliseconds, --proxy Specify address of host to proxy through --proxy-type Specify proxy type ("http", "socks4", "socks5") --proxy-auth Authenticate with HTTP or SOCKS proxy server + --proxy-dns Specify where to resolve proxy destination --ssl Connect or listen with SSL --ssl-cert Specify SSL certificate file (PEM) for listening --ssl-key Specify SSL private key (PEM) for listening diff --git a/ncat/docs/ncat.xml b/ncat/docs/ncat.xml index 58082397a..0f6e3b3f7 100644 --- a/ncat/docs/ncat.xml +++ b/ncat/docs/ncat.xml @@ -468,6 +468,38 @@ , it should be a username only. + + + + (Specify where to resolve proxy destination) + (Ncat option) + + + In connect mode, it provides control over whether proxy + destination hostnames are resolved by the remote proxy server or + locally, by Ncat itself. + Possible values for type are: + + local - Hostnames are resolved locally on + the Ncat host. Ncat exits with error if the hostname cannot be + resolved. + + remote - Hostnames are passed directly onto + the remote proxy server. This is the default behavior. + + both - Hostname resolution is first + attempted on the Ncat host. Unresolvable hostnames are passed onto + the remote proxy server. + + none - Hostname resolution is completely + disabled. Only a literal IPv4 or IPv6 address can be used as + the proxy destination. + + Local hostname resolution generally respects IP version + specified with options or , + except for SOCKS4, which is incompatible with IPv6. + + @@ -817,7 +849,9 @@ Completely disable hostname resolution across all Ncat options, such as the destination, source address, source routing hops, and - the proxy. All addresses must be specified numerically. + the proxy. All addresses must be specified numerically. + (Note that resolution of proxy destinations is controlled separately + via option .) diff --git a/ncat/ncat_connect.c b/ncat/ncat_connect.c index f80d7e8ea..facd803de 100644 --- a/ncat/ncat_connect.c +++ b/ncat/ncat_connect.c @@ -438,6 +438,15 @@ static int do_proxy_http(void) size_t len; int sd, code; int n; + char *target; + union sockaddr_u addr; + size_t sslen; + void *addrbuf; + char addrstr[INET6_ADDRSTRLEN]; + + request = NULL; + status_line = NULL; + header = NULL; sd = do_connect(SOCK_STREAM); if (sd == -1) { @@ -445,12 +454,35 @@ static int do_proxy_http(void) return -1; } - request = NULL; - status_line = NULL; - header = NULL; + if (proxyresolve(o.target, 0, &addr.storage, &sslen, o.af)) { + /* target resolution has failed, possibly because it is disabled */ + if (!(o.proxydns & PROXYDNS_REMOTE)) { + loguser("Error: Failed to resolve host %s locally.\n", o.target); + goto bail; + } + if (o.verbose) + loguser("Host %s will be resolved by the proxy.\n", o.target); + target = o.target; + } else { + /* addr is now populated with either sockaddr_in or sockaddr_in6 */ + switch (addr.sockaddr.sa_family) { + case AF_INET: + addrbuf = &addr.in.sin_addr; + break; + case AF_INET6: + addrbuf = &addr.in6.sin6_addr; + break; + default: + ncat_assert(0); + } + inet_ntop(addr.sockaddr.sa_family, addrbuf, addrstr, sizeof(addrstr)); + target = addrstr; + if (o.verbose && getaddrfamily(o.target) == -1) + loguser("Host %s locally resolved to %s.\n", o.target, target); + } /* First try a request with no authentication. */ - request = http_connect_request(o.target,o.portno, &n); + request = http_connect_request(target, o.portno, &n); if (send(sd, request, n, 0) < 0) { loguser("Error sending proxy request: %s.\n", socket_strerror(socket_errno())); goto bail; @@ -501,7 +533,7 @@ static int do_proxy_http(void) goto bail; } - request = http_connect_request_auth(o.target,o.portno, &n, &challenge); + request = http_connect_request_auth(target, o.portno, &n, &challenge); if (request == NULL) { loguser("Error building Proxy-Authorization header.\n"); http_challenge_free(&challenge); @@ -569,9 +601,18 @@ bail: static int do_proxy_socks4(void) { struct socket_buffer stateful_buf; - struct socks4_data socks4msg; char socksbuf[8]; - int sd,len = 9; + struct socks4_data socks4msg; + size_t datalen; + char *username = o.proxy_auth != NULL ? o.proxy_auth : ""; + union sockaddr_u addr; + size_t sslen; + int sd; + + if (getaddrfamily(o.target) == 2) { + loguser("Error: IPv6 addresses are not supported with Socks4.\n"); + return -1; + } sd = do_connect(SOCK_STREAM); if (sd == -1) { @@ -591,46 +632,40 @@ static int do_proxy_socks4(void) socks4msg.type = SOCKS_CONNECT; socks4msg.port = htons(o.portno); - switch(getaddrfamily(o.target)) { - case 1: // IPv4 address family - socks4msg.address = inet_addr(o.target); + if (strlen(username) >= sizeof(socks4msg.data)) { + loguser("Error: username is too long.\n"); + close(sd); + return -1; + } + strcpy(socks4msg.data, username); + datalen = strlen(username) + 1; - if (o.proxy_auth){ - memcpy(socks4msg.data, o.proxy_auth, strlen(o.proxy_auth)); - len += strlen(o.proxy_auth); - } - break; - - case 2: // IPv6 address family - - loguser("Error: IPv6 addresses are not supported with Socks4.\n"); + if (proxyresolve(o.target, 0, &addr.storage, &sslen, AF_INET)) { + /* target resolution has failed, possibly because it is disabled */ + if (!(o.proxydns & PROXYDNS_REMOTE)) { + loguser("Error: Failed to resolve host %s locally.\n", o.target); close(sd); return -1; - - case -1: // fqdn - - socks4msg.address = inet_addr("0.0.0.1"); - - if (strlen(o.target) > SOCKS_BUFF_SIZE-2) { - loguser("Error: host name is too long.\n"); - close(sd); - return -1; - } - - if (o.proxy_auth){ - if (strlen(o.target)+strlen(o.proxy_auth) > SOCKS_BUFF_SIZE-2) { - loguser("Error: host name and username are too long.\n"); - close(sd); - return -1; - } - Strncpy(socks4msg.data,o.proxy_auth,sizeof(socks4msg.data)); - len += strlen(o.proxy_auth); - } - memcpy(socks4msg.data+(len-8), o.target, strlen(o.target)); - len += strlen(o.target)+1; + } + if (o.verbose) + loguser("Host %s will be resolved by the proxy.\n", o.target); + socks4msg.address = inet_addr("0.0.0.1"); + if (datalen + strlen(o.target) >= sizeof(socks4msg.data)) { + loguser("Error: host name is too long.\n"); + close(sd); + return -1; + } + strcpy(socks4msg.data + datalen, o.target); + datalen += strlen(o.target) + 1; + } else { + /* addr is now populated with sockaddr_in */ + socks4msg.address = addr.in.sin_addr.s_addr; + if (o.verbose && getaddrfamily(o.target) == -1) + loguser("Host %s locally resolved to %s.\n", o.target, + inet_ntoa(addr.in.sin_addr)); } - if (send(sd, (char *) &socks4msg, len, 0) < 0) { + if (send(sd, (char *)&socks4msg, offsetof(struct socks4_data, data) + datalen, 0) < 0) { loguser("Error: sending proxy request: %s.\n", socket_strerror(socket_errno())); close(sd); return -1; @@ -659,11 +694,8 @@ static int do_proxy_socks4(void) */ static int do_proxy_socks5(void) { - struct socket_buffer stateful_buf; struct socks5_connect socks5msg; - uint32_t inetaddr; - char inet6addr[16]; uint16_t proxyport = htons(o.portno); char socksbuf[8]; int sd; @@ -672,6 +704,11 @@ static int do_proxy_socks5(void) struct socks5_auth socks5auth; char *uptr, *pptr; size_t authlen, ulen, plen; + union sockaddr_u addr; + size_t sslen; + void *addrbuf; + size_t addrlen; + char addrstr[INET6_ADDRSTRLEN]; sd = do_connect(SOCK_STREAM); if (sd == -1) { @@ -814,38 +851,47 @@ static int do_proxy_socks5(void) socks5msg2.cmd = SOCKS_CONNECT; socks5msg2.rsv = 0; - switch(getaddrfamily(o.target)) { - - case 1: // IPv4 address family - socks5msg2.atyp = SOCKS5_ATYP_IPv4; - inetaddr = inet_addr(o.target); - memcpy(socks5msg2.dst, &inetaddr, 4); - dstlen = 4; - break; - - case 2: // IPv6 address family - socks5msg2.atyp = SOCKS5_ATYP_IPv6; - inet_pton(AF_INET6,o.target,&inet6addr); - memcpy(socks5msg2.dst, inet6addr,16); - dstlen = 16; - break; - - case -1: // FQDN - socks5msg2.atyp = SOCKS5_ATYP_NAME; - targetlen=strlen(o.target); - if (targetlen > SOCKS5_DST_MAXLEN){ - loguser("Error: hostname length exceeds %d.\n", SOCKS5_DST_MAXLEN); - close(sd); - return -1; - } - dstlen = 0; - socks5msg2.dst[dstlen++] = targetlen; - memcpy(socks5msg2.dst + dstlen, o.target, targetlen); - dstlen += targetlen; - break; - - default: // this shall not happen - ncat_assert(0); + if (proxyresolve(o.target, 0, &addr.storage, &sslen, o.af)) { + /* target resolution has failed, possibly because it is disabled */ + if (!(o.proxydns & PROXYDNS_REMOTE)) { + loguser("Error: Failed to resolve host %s locally.\n", o.target); + close(sd); + return -1; + } + if (o.verbose) + loguser("Host %s will be resolved by the proxy.\n", o.target); + socks5msg2.atyp = SOCKS5_ATYP_NAME; + targetlen=strlen(o.target); + if (targetlen > SOCKS5_DST_MAXLEN){ + loguser("Error: hostname length exceeds %d.\n", SOCKS5_DST_MAXLEN); + close(sd); + return -1; + } + dstlen = 0; + socks5msg2.dst[dstlen++] = targetlen; + memcpy(socks5msg2.dst + dstlen, o.target, targetlen); + dstlen += targetlen; + } else { + /* addr is now populated with either sockaddr_in or sockaddr_in6 */ + switch (addr.sockaddr.sa_family) { + case AF_INET: + socks5msg2.atyp = SOCKS5_ATYP_IPv4; + addrbuf = &addr.in.sin_addr; + addrlen = 4; + break; + case AF_INET6: + socks5msg2.atyp = SOCKS5_ATYP_IPv6; + addrbuf = &addr.in6.sin6_addr; + addrlen = 16; + break; + default: + ncat_assert(0); + } + memcpy(socks5msg2.dst, addrbuf, addrlen); + dstlen = addrlen; + if (o.verbose && getaddrfamily(o.target) == -1) + loguser("Host %s locally resolved to %s.\n", o.target, + inet_ntop(addr.sockaddr.sa_family, addrbuf, addrstr, sizeof(addrstr))); } memcpy(socks5msg2.dst + dstlen, &proxyport, 2); diff --git a/ncat/ncat_core.c b/ncat/ncat_core.c index 27988a0e5..982fda0d5 100644 --- a/ncat/ncat_core.c +++ b/ncat/ncat_core.c @@ -207,6 +207,7 @@ void options_init(void) o.proxy_auth = NULL; o.proxytype = NULL; o.proxyaddr = NULL; + o.proxydns = PROXYDNS_REMOTE; o.zerobyte = 0; #ifdef HAVE_OPENSSL @@ -297,6 +298,32 @@ int resolve(const char *hostname, unsigned short port, return result; } +/* Resolves the given hostname or IP address with getaddrinfo, and stores the + first result (if any) in *ss and *sslen. The value of port will be set in the + appropriate place in *ss; set to 0 if you don't care. af may be AF_UNSPEC, in + which case getaddrinfo may return e.g. both IPv4 and IPv6 results; which one + is first depends on the system configuration. Returns 0 on success, or a + getaddrinfo return code (suitable for passing to gai_strerror) on failure. + *ss and *sslen are always defined when this function returns 0. + + Resolve the hostname with DNS only if global o.proxydns includes PROXYDNS_LOCAL. */ +int proxyresolve(const char *hostname, unsigned short port, + struct sockaddr_storage *ss, size_t *sslen, int af) +{ + int flags; + struct sockaddr_list sl; + int result; + + flags = 0; + if (!(o.proxydns & PROXYDNS_LOCAL)) + flags |= AI_NUMERICHOST; + + result = resolve_internal(hostname, port, &sl, af, flags, 0); + *ss = sl.addr.storage; + *sslen = sl.addrlen; + return result; +} + /* Resolves the given hostname or IP address with getaddrinfo, and stores all results into a linked list. The rest of the behavior is same as resolve(). */ diff --git a/ncat/ncat_core.h b/ncat/ncat_core.h index 3ff47ee5c..b8ce93746 100644 --- a/ncat/ncat_core.h +++ b/ncat/ncat_core.h @@ -160,6 +160,10 @@ enum exec_mode { EXEC_LUA, }; +/* Proxy DNS resolution options (mask bits) */ +#define PROXYDNS_LOCAL 1 +#define PROXYDNS_REMOTE 2 + struct options { unsigned short portno; @@ -211,6 +215,7 @@ struct options { char *proxy_auth; char *proxytype; char *proxyaddr; + int proxydns; int ssl; char *sslcert; @@ -242,6 +247,18 @@ void options_init(void); int resolve(const char *hostname, unsigned short port, struct sockaddr_storage *ss, size_t *sslen, int af); +/* Resolves the given hostname or IP address with getaddrinfo, and stores the + first result (if any) in *ss and *sslen. The value of port will be set in the + appropriate place in *ss; set to 0 if you don't care. af may be AF_UNSPEC, in + which case getaddrinfo may return e.g. both IPv4 and IPv6 results; which one + is first depends on the system configuration. Returns 0 on success, or a + getaddrinfo return code (suitable for passing to gai_strerror) on failure. + *ss and *sslen are always defined when this function returns 0. + + Resolve the hostname with DNS only if global o.proxydns includes PROXYDNS_LOCAL. */ +int proxyresolve(const char *hostname, unsigned short port, + struct sockaddr_storage *ss, size_t *sslen, int af); + /* Resolves the given hostname or IP address with getaddrinfo, and stores all results into a linked list. The rest of behavior is same as resolve(). */ diff --git a/ncat/ncat_main.c b/ncat/ncat_main.c index 96af0a942..d557ccc35 100644 --- a/ncat/ncat_main.c +++ b/ncat/ncat_main.c @@ -327,6 +327,7 @@ int main(int argc, char *argv[]) {"proxy", required_argument, NULL, 0}, {"proxy-type", required_argument, NULL, 0}, {"proxy-auth", required_argument, NULL, 0}, + {"proxy-dns", required_argument, NULL, 0}, {"nsock-engine", required_argument, NULL, 0}, {"test", no_argument, NULL, 0}, {"ssl", no_argument, &o.ssl, 1}, @@ -490,6 +491,17 @@ int main(int argc, char *argv[]) if (o.proxy_auth) bye("You can't specify more than one --proxy-auth."); o.proxy_auth = optarg; + } else if (strcmp(long_options[option_index].name, "proxy-dns") == 0) { + if (strcmp(optarg, "none") == 0) + o.proxydns = 0; + else if (strcmp(optarg, "local") == 0) + o.proxydns = PROXYDNS_LOCAL; + else if (strcmp(optarg, "remote") == 0) + o.proxydns = PROXYDNS_REMOTE; + else if (strcmp(optarg, "both") == 0) + o.proxydns = PROXYDNS_LOCAL | PROXYDNS_REMOTE; + else + bye("Invalid proxy DNS type."); } else if (strcmp(long_options[option_index].name, "nsock-engine") == 0) { if (nsock_set_default_engine(optarg) < 0) bye("Unknown or non-available engine: %s.", optarg); @@ -647,6 +659,7 @@ int main(int argc, char *argv[]) " --proxy Specify address of host to proxy through\n" " --proxy-type Specify proxy type (\"http\", \"socks4\", \"socks5\")\n" " --proxy-auth Authenticate with HTTP or SOCKS proxy server\n" +" --proxy-dns Specify where to resolve proxy destination\n" #ifdef HAVE_OPENSSL " --ssl Connect or listen with SSL\n"