Should Ncat --exec wait for EOF in both directions?

David Fifield <[email protected]> Mon, 16 Dec 2019 23:01:21 -0700
Newsgroups gmane.comp.security.nmap.devel
Message-ID <[email protected]>
--btzlbcjxu4miylqm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

One of the distinguishing features of Ncat has been that it doesn't stop
when only one direction of the connection is finished sending. That is,
when it gets an EOF on stdin, it does a shutdown(fd, SHUT_WR) on the
socket; and when it gets an EOF on the socket, it closes its stdout; but
it doesn't quit until both have happened. (There's a proposal to modify
this in client mode, https://seclists.org/nmap-dev/2017/q2/94, but afaik
it still works like I described.)

But it doesn't work that way with --exec and --sh-exec. The listen-mode
Ncat quits exchanging with the child process when there is an EOF on
*either* the incoming socket or the child's stdout. This means you
cannot, for example, have a server return the md5sum of what the client
sends to it:
	$ ncat -l -k 8000 --sh-exec 'md5sum'
	$ dd if=/dev/zero bs=1M count=1 status=none | ncat -v 127.0.0.1 8000
	Ncat: Version 7.70 ( https://nmap.org/ncat )
	Ncat: Connected to 127.0.0.1:8000.
	Ncat: 1048576 bytes sent, 0 bytes received in 0.02 seconds.

Notice the "0 bytes received" on the connect-mode Ncat. Maybe we need
something like the attached (barely tested) patch? With it, the md5sum
example works:
	$ ncat -l -k 8000 --sh-exec 'md5sum'
	$ dd if=/dev/zero bs=1M count=1 status=none | ncat -v 127.0.0.1 8000
	Ncat: Version 7.70 ( https://nmap.org/ncat )
	Ncat: Connected to 127.0.0.1:8000.
	b6d81b360a5672d80c27430f39153e2c  -
	Ncat: 1048576 bytes sent, 36 bytes received in 0.02 seconds.

subprocess_thread_func in ncat_exec_win.c would need similar treatment.

--btzlbcjxu4miylqm
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="ncat-exec-eof.patch"

diff --git a/ncat/ncat_posix.c b/ncat/ncat_posix.c
index 12207c066..3db875b19 100644
--- a/ncat/ncat_posix.c
+++ b/ncat/ncat_posix.c
@@ -190,6 +190,7 @@ void netexec(struct fdinfo *info, char *cmdexec)
     int child_stdout[2];
     int pid;
     int crlf_state;
+    int eof_fd, eof_child;
 
     char buf[DEFAULT_TCP_BUF_LEN];
     int maxfd;
@@ -268,13 +269,17 @@ void netexec(struct fdinfo *info, char *cmdexec)
        writes to the socket. We exit the loop on any read error (or EOF). On a
        write error we just close the opposite side of the conversation. */
     crlf_state = 0;
-    for (;;) {
+    eof_fd = 0;
+    eof_child = 0;
+    while (!eof_fd || !eof_child) {
         fd_set fds;
         int r, n_r;
 
         FD_ZERO(&fds);
-        FD_SET(info->fd, &fds);
-        FD_SET(child_stdout[0], &fds);
+        if (!eof_fd)
+            FD_SET(info->fd, &fds);
+        if (!eof_child)
+            FD_SET(child_stdout[0], &fds);
 
         r = fselect(maxfd + 1, &fds, NULL, NULL, NULL);
         if (r == -1) {
@@ -288,16 +293,23 @@ void netexec(struct fdinfo *info, char *cmdexec)
 
             do {
                 n_r = ncat_recv(info, buf, sizeof(buf), &pending);
-                if (n_r <= 0)
-                    goto loop_end;
+                if (n_r <= 0) {
+                    if (!o.noshutdown)
+                        close(child_stdin[1]);
+                    eof_fd = 1;
+                    break;
+                }
                 write_loop(child_stdin[1], buf, n_r);
             } while (pending);
         }
         if (FD_ISSET(child_stdout[0], &fds)) {
             char *crlf = NULL, *wbuf;
             n_r = read(child_stdout[0], buf, sizeof(buf));
-            if (n_r <= 0)
-                break;
+            if (n_r <= 0) {
+                if (!o.noshutdown)
+                    shutdown(info->fd, SHUT_WR);
+                eof_child = 1;
+            }
             wbuf = buf;
             if (o.crlf) {
                 if (fix_line_endings((char *) buf, &n_r, &crlf, &crlf_state))
@@ -308,7 +320,6 @@ void netexec(struct fdinfo *info, char *cmdexec)
                 free(crlf);
         }
     }
-loop_end:
 
 #ifdef HAVE_OPENSSL
     if (info->ssl != NULL) {

--btzlbcjxu4miylqm
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Sent through the dev mailing list
https://nmap.org/mailman/listinfo/dev
Archived at http://seclists.org/nmap-dev/
--btzlbcjxu4miylqm--