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/Scheduler/UIxAppointmentEditor.m, UI/Scheduler/UIxCalListingActions.m, UI/WebServerResources/SchedulerUI.js
Commit date: 2014-02-05 16:06:20 -0500
Commit message: Escape HTML in JSON of calendar module

Fixes #2598

Diff: diff --git a/NEWS b/NEWS
index c8e4a1746..cec4e572f 100644
--- a/NEWS
+++ b/NEWS
@@ -36,10 +36,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)
 
 2.1.1b (2013-12-04)
 -------------------
 
 Enhancements
diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m
index 3c4b92b76..2c371c5fe 100644
--- a/UI/Scheduler/UIxAppointmentEditor.m
+++ b/UI/Scheduler/UIxAppointmentEditor.m
@@ -29,10 +29,11 @@
 #import <NGObjWeb/SoSecurityManager.h>
 #import <NGObjWeb/WORequest.h>
 #import <NGObjWeb/WOResponse.h>
 #import <NGObjWeb/NSException+HTTP.h>
 #import <NGExtensions/NSCalendarDate+misc.h>
+#import <NGExtensions/NSString+misc.h>
 
 #import <NGCards/iCalAlarm.h>
 #import <NGCards/iCalCalendar.h>
 #import <NGCards/iCalEvent.h>
 #import <NGCards/iCalPerson.h>
@@ -516,22 +517,22 @@ - (void) _adjustRecurrentRules
     }
 
   created_by = [event createdBy];
 
   data = [NSDictionary dictionaryWithObjectsAndKeys:
-                       [componentCalendar displayName], @"calendar",
+                       [[componentCalendar displayName] stringByEscapingHTMLString], @"calendar",
                        [event tag], @"component",
                        [dateFormatter formattedDate: eventStartDate], @"startDate",
                        [dateFormatter formattedTime: eventStartDate], @"startTime",
                        [dateFormatter formattedDate: eventEndDate], @"endDate",
                        [dateFormatter formattedTime: eventEndDate], @"endTime",
                      //([event hasRecurrenceRules] ? @"1": @"0"), @"isRecurring",
                        ([event isAllDay] ? @"1": @"0"), @"isAllDay",
-                       [event summary], @"summary",
-                       [event location], @"location",
-		       created_by, @"created_by",
-                       [event comment], @"description",
+                       [[event summary] stringByEscapingHTMLString], @"summary",
+                       [[event location] stringByEscapingHTMLString], @"location",
+		       [created_by stringByEscapingHTMLString], @"created_by",
+                       [[event comment] stringByEscapingHTMLString], @"description",
                        nil];
   
   [result appendContentString: [data jsonRepresentation]];
 
   return result;
diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m
index cbb0b0087..5f99777a7 100644
--- a/UI/Scheduler/UIxCalListingActions.m
+++ b/UI/Scheduler/UIxCalListingActions.m
@@ -39,10 +39,11 @@
 #import <NGCards/iCalDateTime.h>
 
 #import <NGExtensions/NGCalendarDateRange.h>
 #import <NGExtensions/NSCalendarDate+misc.h>
 #import <NGExtensions/NSObject+Logs.h>
+#import <NGExtensions/NSString+misc.h>
 
 #import <SOGo/SOGoDateFormatter.h>
 #import <SOGo/SOGoPermissions.h>
 #import <SOGo/SOGoUser.h>
 #import <SOGo/SOGoUserDefaults.h>
@@ -53,11 +54,10 @@
 #import <SOGo/NSObject+Utilities.h>
 #import <Appointments/SOGoAppointmentFolder.h>
 #import <Appointments/SOGoAppointmentFolders.h>
 #import <Appointments/SOGoAppointmentObject.h>
 #import <Appointments/SOGoWebAppointmentFolder.h>
-#import <Appointments/SOGoFreeBusyObject.h>
 
 #import <UI/Common/WODirectAction+SOGo.h>
 
 #import "NSArray+Scheduler.h"
 
@@ -308,16 +308,18 @@ - (NSArray *) _fetchFields: (NSArray *) fields
 	forComponentOfType: (NSString *) component
 {
   NSEnumerator *folders, *currentInfos;
   SOGoAppointmentFolder *currentFolder;
   NSMutableDictionary *newInfo;
-  NSMutableArray *infos;
+  NSMutableArray *infos, *newInfoForComponent;
   NSNull *marker;
   SOGoAppointmentFolders *clientObject;
   SOGoUser *ownerUser;
   NSString *owner, *role, *calendarName;
   BOOL isErasable, folderIsRemote;
+  id currentInfo;
+  int i, count;
 
   infos = [NSMutableArray array];
   marker = [NSNull null];
   clientObject = [self clientObject];
 
@@ -393,12 +395,21 @@ - (NSArray *) _fetchFields: (NSArray *) fields
               if (![[newInfo objectForKey: @"c_title"] length])
                 [self _fixComponentTitle: newInfo withType: component];
 	      // Possible improvement: only call _fixDates if event is recurrent
 	      // or the view range span a daylight saving time change
               [self _fixDates: newInfo];
-              [infos addObject: [newInfo objectsForKeys: fields
-                                         notFoundMarker: marker]];
+              newInfoForComponent = [NSMutableArray arrayWithArray: [newInfo objectsForKeys: fields
+                                                                             notFoundMarker: marker]];
+              // Escape HTML
+              count = [newInfoForComponent count];
+              for (i = 0; i < count; i++)
+                {
+                  currentInfo = [newInfoForComponent objectAtIndex: i];
+                  if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)])
+                    [newInfoForComponent replaceObjectAtIndex: i withObject: [currentInfo stringByEscapingHTMLString]];
+                }
+              [infos addObject: newInfoForComponent];
             }
         }
     }
 
   return infos;
diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js
index 1b56a22da..820f235b0 100644
--- a/UI/WebServerResources/SchedulerUI.js
+++ b/UI/WebServerResources/SchedulerUI.js
@@ -1781,11 +1781,11 @@ function newBaseEventDIV(eventRep, event, eventText) {
     var textDiv = createElement("div");
     innerDiv.appendChild(textDiv);
     textDiv.addClassName("text");
     var iconSpan = createElement("span", null, "icons");
     textDiv.appendChild(iconSpan);
-    textDiv.appendChild(document.createTextNode(eventText.replace(/(\\r)?\\n/g, "<BR/>")));
+    textDiv.update(eventText.replace(/(\\r)?\\n/g, "<BR/>"));
 
     // Add alarm and classification icons
     if (event[9] == 1)
         createElement("img", null, null, {src: ResourcesURL + "/private.png"}, null, iconSpan);
     else if (event[9] == 2)

Vulnerable code:                        [componentCalendar displayName], @"calendar",
                       [event summary], @"summary",
                       [event location], @"location",
		       created_by, @"created_by",
                       [event comment], @"description",
#import <Appointments/SOGoFreeBusyObject.h>
  NSMutableArray *infos;
              [infos addObject: [newInfo objectsForKeys: fields
                                         notFoundMarker: marker]];
    textDiv.appendChild(document.createTextNode(eventText.replace(/(\\r)?\\n/g, "<BR/>")));
Fixed code:  - encode HTML entities in JSON data returned by Calendar module (#2598)
#import <NGExtensions/NSString+misc.h>
                       [[componentCalendar displayName] stringByEscapingHTMLString], @"calendar",
                       [[event summary] stringByEscapingHTMLString], @"summary",
                       [[event location] stringByEscapingHTMLString], @"location",
		       [created_by stringByEscapingHTMLString], @"created_by",
                       [[event comment] stringByEscapingHTMLString], @"description",
#import <NGExtensions/NSString+misc.h>
  NSMutableArray *infos, *newInfoForComponent;
  id currentInfo;
  int i, count;
              newInfoForComponent = [NSMutableArray arrayWithArray: [newInfo objectsForKeys: fields
                                                                             notFoundMarker: marker]];
              // Escape HTML
              count = [newInfoForComponent count];
              for (i = 0; i < count; i++)
                {
                  currentInfo = [newInfoForComponent objectAtIndex: i];
                  if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)])
                    [newInfoForComponent replaceObjectAtIndex: i withObject: [currentInfo stringByEscapingHTMLString]];
                }
              [infos addObject: newInfoForComponent];
    textDiv.update(eventText.replace(/(\\r)?\\n/g, "<BR/>"));