CVE-2019-18641 (published 2020-03-20T21:15Z)

Rock RMS before 1.8.6 mishandles vCard access control within the People/GetVCard/REST controller.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/SparkDevNetwork/Rock
commit_details: repo_url: https://github.com/SparkDevNetwork/Rock
author: Shivam Kishor
author_date: 2019-01-12 12:29:05+05:30
msg: + Changed vCard to be secured by adjustments to the People/GetVCard/ REST controller.
repository: repo_url: https://github.com/SparkDevNetwork/Rock
repo_name: SparkDevNetwork/Rock
description: An open source CMS, Relationship Management System (RMS) and Church Management System (ChMS) all rolled into one.
repo_language: C#
owner: SparkDevNetwork
file_changes: filename: PeopleController.Partial.cs
change_type: ModificationType.MODIFY
diff: @@ -21,6 +21,7 @@ using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Text;
+using System.Text.RegularExpressions;
 using System.Web;
 using System.Web.Http;
 using System.Web.Http.OData;
@@ -876,6 +877,49 @@ namespace Rock.Rest.Controllers
         }
 
         #endregion
+
+        #region VCard
+
+        /// <summary>
+        /// Returns VCard for person.
+        /// </summary>
+        /// <param name="personGuid">The person Guid.</param>
+        /// <returns></returns>
+        [HttpGet]
+        [Authenticate, Secured]
+        [System.Web.Http.Route( "api/People/VCard/{personGuid}" )]
+        public HttpResponseMessage GetVCard( Guid personGuid )
+        {
+            var rockContext = ( Rock.Data.RockContext ) Service.Context;
+
+            var person = new PersonService( rockContext ).Get( personGuid );
+            if ( person == null )
+            {
+                throw new HttpResponseException( new System.Net.Http.HttpResponseMessage( HttpStatusCode.NotFound ) );
+            }
+
+            string fileName = person.FullName + ".vcf";
+            HttpResponseMessage result = new HttpResponseMessage( HttpStatusCode.OK );
+
+            var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields( null, GetPerson() );
+            mergeFields.Add( "Person", person );
+            string vCard = GlobalAttributesCache.Value( "VCardFormat" ).ResolveMergeFields( mergeFields ).Trim();
+
+            // remove empty lines (the vcard spec is very picky)
+            vCard = Regex.Replace( vCard, @"^\s+$[\r\n]*", "", RegexOptions.Multiline );
+
+            var inputEncoding = Encoding.Default;
+            var outputEncoding = Encoding.GetEncoding( 28591 );
+            var cardBytes = inputEncoding.GetBytes( vCard );
+            var outputBytes = Encoding.Convert( inputEncoding, outputEncoding, cardBytes );
+            result.Content = new ByteArrayContent( outputBytes );
+            result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue( "text/vcard" );
+            result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue( "attachment" );
+            result.Content.Headers.ContentDisposition.FileName = fileName;
+            return result;
+        }
+
+        #endregion
     }
 
     /// <summary>

programming_language: C#
filename: GetVCard.ashx.cs
change_type: ModificationType.DELETE
diff: @@ -1,156 +0,0 @@
-﻿// <copyright>
-// Copyright by the Spark Development Network
-//
-// Licensed under the Rock Community License (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.rockrms.com/license
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-// </copyright>
-//
-using System;
-using System.IO;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using System.Web;
-using Rock;
-using Rock.Data;
-using Rock.Model;
-using Rock.Security;
-using Rock.Web.Cache;
-
-namespace RockWeb
-{
-    /// <summary>
-    /// Handles retrieving file data from storage
-    /// </summary>
-    public class GetVCard : IHttpHandler
-    {
-        /// <summary>
-        /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler" /> interface.
-        /// </summary>
-        /// <param name="context">An <see cref="T:System.Web.HttpContext" /> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
-        public void ProcessRequest( HttpContext context )
-        {
-            var rockContext = new RockContext();
-
-            var currentUser = new UserLoginService( rockContext ).GetByUserName( UserLogin.GetCurrentUserName() );
-            Person currentPerson = currentUser != null ? currentUser.Person : null;
-
-            Person person = GetPerson( context, rockContext );
-            if ( person == null )
-            {
-                SendNotFound( context );
-                return;
-            }
-            else
-            {
-                if ( !person.IsAuthorized( Authorization.VIEW, currentPerson ) )
-                {
-                    SendNotAuthorized( context );
-                    return;
-                }
-            }
-
-            var response = context.Response;
-            response.ContentType = "text/vcard";
-
-            // http://stackoverflow.com/questions/93551/how-to-encode-the-filename-parameter-of-content-disposition-header-in-http
-            string contentDisposition;
-            string fileName = person.FullName + ".vcf";
-            if ( context.Request.Browser.Browser == "IE" )
-            {
-                contentDisposition = "attachment; filename=" + Uri.EscapeDataString( fileName );
-            }
-            else if ( context.Request.Browser.Browser == "Safari" )
-            {
-                contentDisposition = "attachment; filename=" + fileName;
-            }
-            else
-            {
-                contentDisposition = "attachment; filename*=UTF-8''" + Uri.EscapeDataString( fileName );
-            }
-            response.AddHeader( "Content-Disposition", contentDisposition );
-
-            var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields( null, currentPerson );
-            mergeFields.Add( "Person", person );
-            string vCard = GlobalAttributesCache.Value( "VCardFormat" ).ResolveMergeFields( mergeFields ).Trim();
-
-            // remove empty lines (the vcard spec is very picky)
-            vCard = Regex.Replace( vCard, @"^\s+$[\r\n]*", "", RegexOptions.Multiline );
-
-            var inputEncoding = Encoding.Default;
-            var outputEncoding = Encoding.GetEncoding( 28591 );
-            var cardBytes = inputEncoding.GetBytes( vCard );
-            var outputBytes = Encoding.Convert( inputEncoding, outputEncoding, cardBytes );
-
-            response.OutputStream.Write( outputBytes, 0, outputBytes.Length );
-        }
-
-        /// <summary>
-        /// Gets the person.
-        /// </summary>
-        /// <param name="context">The context.</param>
-        /// <param name="rockContext">The rock context.</param>
-        /// <returns></returns>
-        private Person GetPerson( HttpContext context, RockContext rockContext )
-        {
-            string personParam = context.Request.QueryString["Person"];
-
-            int? personId = personParam.AsIntegerOrNull();
-            if ( personId.HasValue )
-            {
-                return new PersonService( rockContext ).Get( personId.Value );
-            }
-
-            Guid? personGuid = personParam.AsGuidOrNull();
-            if ( personGuid.HasValue )
-            {
-                return new PersonService( rockContext ).Get( personGuid.Value );
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// Sends 404 status.
-        /// </summary>
-        /// <param name="context">The context.</param>
-        private void SendNotFound( HttpContext context )
-        {
-            context.Response.StatusCode = System.Net.HttpStatusCode.NotFound.ConvertToInt();
-            context.Response.StatusDescription = "The requested person could not be found.";
-            context.ApplicationInstance.CompleteRequest();
-        }
-
-        /// <summary>
-        /// Sends a 403 (forbidden)
-        /// </summary>
-        /// <param name="context">The context.</param>
-        private void SendNotAuthorized( HttpContext context )
-        {
-            context.Response.StatusCode = System.Net.HttpStatusCode.Forbidden.ConvertToInt();
-            context.Response.StatusDescription = "Not authorized to view person";
-            context.ApplicationInstance.CompleteRequest();
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler" /> instance.
-        /// </summary>
-        /// <returns>true if the <see cref="T:System.Web.IHttpHandler" /> instance is reusable; otherwise, false.</returns>
-        public bool IsReusable
-        {
-            get
-            {
-                return false;
-            }
-        }
-    }
-}
\ No newline at end of file

programming_language: unknown
filename: Bio.ascx.cs
change_type: ModificationType.MODIFY
diff: @@ -175,7 +175,7 @@ Because the contents of this setting will be rendered inside a &lt;ul&gt; elemen
                         FollowingsHelper.SetFollowing( Person.PrimaryAlias, pnlFollow, this.CurrentPerson );
                     }
 
-                    hlVCard.NavigateUrl = ResolveRockUrl( string.Format( "~/GetVCard.ashx?Person={0}", Person.Id ) );
+                    hlVCard.NavigateUrl = ResolveUrl( string.Format( "~/api/People/VCard/{0}", Person.Guid ) );
 
                     var socialCategoryGuid = GetAttributeValue( "SocialMediaCategory" ).AsGuidOrNull();
                     if ( socialCategoryGuid.HasValue )

programming_language: C#
filename: GetVCard.ashx
change_type: ModificationType.DELETE
diff: @@ -1 +0,0 @@
-﻿<%@ WebHandler Language="C#" CodeBehind="~/App_Code/GetVCard.ashx.cs" Class="RockWeb.GetVCard" %>

programming_language: unknown