CVE-2014-9905: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Multiple cross-site scripting (XSS) vulnerabilities in the Web Calendar in SOGo before 2.2.0 allow remote attackers to inject arbitrary web script or HTML via the (1) title of an appointment or (2) contact fields.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2017-02-17T17:59Z
Repository: https://github.com/inverse-inc/sogo
Language: Objective-C
Files changed: NEWS, UI/Contacts/UIxContactView.m, UI/Contacts/UIxContactsListActions.m, UI/WebServerResources/ContactsUI.js
Commit date: 2014-02-07 15:52:43 -0500
Commit message: Escape HTML in JSON of contacts module

Diff: diff --git a/NEWS b/NEWS
index 9a2ffc2d2..cd582679f 100644
--- a/NEWS
+++ b/NEWS
@@ -38,11 +38,11 @@ Bug fixes
  - fixed handling of the '=' character in cards/events/tasks (#2505)
  - simplify searches in the address book (#2187)
  - warn user when dnd failed because of a resource conflict (#1613)
  - respect the maximum number of bookings when viewing the freebusy information of a resource (#2560)
  - encode HTML entities when forwarding an HTML message inline in plain text composition mode (#2411)
- - encode HTML entities in JSON data returned by Calendar module (#2598)
+ - encode HTML entities in JSON data (#2598)
  - fixed handling of ACLs on shared calendars with multiple groups (#1854)
  - fixed HTML formatting of appointment notifications for Outlook (#2233)
  - replace slashes by dashes in filenames of attachments to avoid a 404 return code (#2537)
 
 2.1.1b (2013-12-04)
diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m
index d7e282762..486fff107 100644
--- a/UI/Contacts/UIxContactView.m
+++ b/UI/Contacts/UIxContactView.m
@@ -27,10 +27,11 @@
 #import <NGCards/NGVCard.h>
 #import <NGCards/NGVCardPhoto.h>
 #import <NGCards/CardElement.h>
 #import <NGCards/NSArray+NGCards.h>
 #import <NGExtensions/NSString+Ext.h>
+#import <NGExtensions/NSString+misc.h>
 
 #import <SOGo/NSCalendarDate+SOGo.h>
 #import <SOGo/SOGoDateFormatter.h>
 #import <SOGo/SOGoUser.h>
 
@@ -69,11 +70,11 @@ - (NSString *) _cardStringWithLabel: (NSString *) label
                                 url: (NSString *) url
 {
   NSMutableString *cardString;
 
   cardString = [NSMutableString stringWithCapacity: 80];
-  value = [value stringByReplacingString: @"\r" withString: @""];
+  value = [[value stringByReplacingString: @"\r" withString: @""] stringByEscapingHTMLString];
   if ([value length] > 0)
     {
       if ([url length] > 0)
         value = [NSString stringWithFormat: @"<a href=\"%@:%@\">%@</a>", url, value, value];
 
diff --git a/UI/Contacts/UIxContactsListActions.m b/UI/Contacts/UIxContactsListActions.m
index 1b9ab62f4..c3438d46d 100644
--- a/UI/Contacts/UIxContactsListActions.m
+++ b/UI/Contacts/UIxContactsListActions.m
@@ -125,15 +125,36 @@ - (NSArray *) contactInfos
  * @return a JSON array of dictionaries representing the contacts.
  */
 - (id <WOActionResults>) contactsListAction
 {
   id <WOActionResults> result;
+  id currentInfo;
   NSArray *contactsList;
+  NSEnumerator *contactsListEnumerator, *keysEnumerator;
+  NSMutableArray *newContactsList;
+  NSMutableDictionary *currentContactDictionary;
+  NSString *key;
 
   contactsList = [self contactInfos];
+  contactsListEnumerator = [contactsList objectEnumerator];
+  newContactsList = [NSMutableArray arrayWithCapacity: [contactsList count]];
+
+  // Escape HTML
+  while ((currentContactDictionary = [contactsListEnumerator nextObject]))
+    {
+      keysEnumerator = [currentContactDictionary keyEnumerator];
+      while ((key = [keysEnumerator nextObject]))
+        {
+          currentInfo = [currentContactDictionary objectForKey: key];
+          if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)])
+            [currentContactDictionary setObject: [currentInfo stringByEscapingHTMLString] forKey: key];
+        }
+      [newContactsList addObject: currentContactDictionary];
+    }
+
   result = [self responseWithStatus: 200
-			  andString: [contactsList jsonRepresentation]];
+			  andString: [newContactsList jsonRepresentation]];
 
   return result;
 }
 
 - (id <WOActionResults>) contactSearchAction
diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js
index ad106b261..93360260a 100644
--- a/UI/WebServerResources/ContactsUI.js
+++ b/UI/WebServerResources/ContactsUI.js
@@ -101,35 +101,35 @@ function contactsListCallback(http) {
                                              null,
                                              ( "displayName" ),
                                              null,
                                              null,
                                              row);
-                    cell.appendChild(document.createTextNode(contact["c_cn"]));
+                    cell.update(contact["c_cn"]);
                     cell.title = contact["c_cn"];
 
                     cell = document.createElement("td");
                     row.appendChild(cell);
                     if (contact["c_mail"]) {
-                        cell.appendChild(document.createTextNode(contact["c_mail"]));
+                        cell.update(contact["c_mail"]);
                         cell.title = contact["c_mail"];
                     }
 
                     if (fullView) {
                         cell = document.createElement("td");
                         row.appendChild(cell);
                         if (contact["c_screenname"])
-                            cell.appendChild(document.createTextNode(contact["c_screenname"]));
+                            cell.update(contact["c_screenname"]);
 
                         cell = document.createElement("td");
                         row.appendChild(cell);
                         if (contact["c_o"])
-                            cell.appendChild(document.createTextNode(contact["c_o"]));
+                            cell.update(contact["c_o"]);
 
                         cell = document.createElement("td");
                         row.appendChild(cell);
                         if (contact["c_telephonenumber"])
-                            cell.appendChild(document.createTextNode(contact["c_telephonenumber"]));
+                            cell.update(contact["c_telephonenumber"]);
                     }
                 }
             }
 
             // Remove unnecessary rows

Vulnerable code:  - encode HTML entities in JSON data returned by Calendar module (#2598)
  value = [value stringByReplacingString: @"\r" withString: @""];
			  andString: [contactsList jsonRepresentation]];
                    cell.appendChild(document.createTextNode(contact["c_cn"]));
                        cell.appendChild(document.createTextNode(contact["c_mail"]));
                            cell.appendChild(document.createTextNode(contact["c_screenname"]));
                            cell.appendChild(document.createTextNode(contact["c_o"]));
                            cell.appendChild(document.createTextNode(contact["c_telephonenumber"]));
Fixed code:  - encode HTML entities in JSON data (#2598)
#import <NGExtensions/NSString+misc.h>
  value = [[value stringByReplacingString: @"\r" withString: @""] stringByEscapingHTMLString];
  id currentInfo;
  NSEnumerator *contactsListEnumerator, *keysEnumerator;
  NSMutableArray *newContactsList;
  NSMutableDictionary *currentContactDictionary;
  NSString *key;
  contactsListEnumerator = [contactsList objectEnumerator];
  newContactsList = [NSMutableArray arrayWithCapacity: [contactsList count]];

  // Escape HTML
  while ((currentContactDictionary = [contactsListEnumerator nextObject]))
    {
      keysEnumerator = [currentContactDictionary keyEnumerator];
      while ((key = [keysEnumerator nextObject]))
        {
          currentInfo = [currentContactDictionary objectForKey: key];
          if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)])
            [currentContactDictionary setObject: [currentInfo stringByEscapingHTMLString] forKey: key];
        }
      [newContactsList addObject: currentContactDictionary];
    }

			  andString: [newContactsList jsonRepresentation]];
                    cell.update(contact["c_cn"]);
                        cell.update(contact["c_mail"]);
                            cell.update(contact["c_screenname"]);
                            cell.update(contact["c_o"]);
                            cell.update(contact["c_telephonenumber"]);