CVE-2019-19249: Improper Input Validation (CWE-20)
value: Controllers/InvitationsController.cs in QueryTree before 3.0.99-beta mishandles invitations.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 9.8
Published: 2019-11-25T17:15Z
Repository: https://github.com/d4software/QueryTree
Language: C#
Files changed: Web/Controllers/InvitationsController.cs, Web/ViewModels/InvitationViewModel.cs, Web/Views/Invitations/Index.cshtml
Commit date: 2019-11-23 07:17:27 +0000
Commit message: Fixing security vunerability

Diff: diff --git a/Web/Controllers/InvitationsController.cs b/Web/Controllers/InvitationsController.cs
index 5de6760..747e579 100644
--- a/Web/Controllers/InvitationsController.cs
+++ b/Web/Controllers/InvitationsController.cs
@@ -59,11 +59,14 @@ namespace QueryTree.Controllers
 
             foreach(var orgGrp in invites
                 .Include(c => c.CreatedBy)
                 .GroupBy(c => c.OrganisationId))
             {
-                InvitationViewModel viewModel = new InvitationViewModel();
+                InvitationViewModel viewModel = new InvitationViewModel
+                {
+                    OrganisationInviteId = orgGrp.First().OrganisationInviteId
+                };
                 
                 Organisation organisation = db.Organisations.First(ba => ba.OrganisationId == orgGrp.Key);
                 viewModel.OrganisationId = organisation.OrganisationId;
 
                 viewModel.Invitees = orgGrp
@@ -117,26 +120,24 @@ namespace QueryTree.Controllers
             if (CurrentUser == null)
             {
                 return NotFound("Could not find user");
             }
 
-            var organisation = db.Organisations.FirstOrDefault(ba => ba.OrganisationId == id);
+            var invite = db.OrganisationInvites.FirstOrDefault(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.AcceptedOn == null && uc.RejectedOn == null && uc.OrganisationInviteId == id);
 
-            if (organisation == null)
+            if (invite == null)
             {
-                return NotFound("Organisation not found");
+                return NotFound("Invite not found");
             }
 
-            // remove other invitations to other organisations
-            db.OrganisationInvites.RemoveWhere(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.OrganisationId != id);
+            var organisation = db.Organisations.FirstOrDefault(ba => ba.OrganisationId == invite.OrganisationId);
 
-            db.SaveChanges();
+            if (organisation == null)
+            {
+                return NotFound("Organisation not found");
+            }
 
-            var invitationsToAccept = db.OrganisationInvites
-                .Where(uc => uc.InviteEmail == CurrentUser.Email && uc.OrganisationId == id)
-                .ToList();
-            
             List<DatabaseConnection> leave = new List<DatabaseConnection>();
             List<DatabaseConnection> migrate = new List<DatabaseConnection>();
             
             // Migrate or cut connections with databases as necessary.
             List<DatabaseConnection> databases = db.DatabaseConnections
@@ -171,15 +172,20 @@ namespace QueryTree.Controllers
                 database.Organisation = organisation;
             }
             
             CurrentUser.OrganisationId = organisation.OrganisationId;
 
-            foreach (var invite in invitationsToAccept)
+            invite.AcceptedOn = DateTime.Now;
+            
+            // reject other invitations to other organisations
+            var invitesToReject = db.OrganisationInvites.Where(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.OrganisationInviteId != invite.OrganisationInviteId);
+        
+            foreach (var inviteToReject in invitesToReject)
             {
-                invite.AcceptedOn = DateTime.Now;
+                inviteToReject.RejectedOn = DateTime.Now;
             }
-        
+
             db.SaveChanges();
 
             return RedirectToAction("Index", "Home");
         }
         
@@ -188,11 +194,17 @@ namespace QueryTree.Controllers
             if (CurrentUser == null)
             {
                 return NotFound("Could not find user");
             }
             
-            var invite = db.OrganisationInvites.First(i => i.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && i.OrganisationId == id && i.AcceptedOn == null && i.RejectedOn == null);
+            var invite = db.OrganisationInvites.FirstOrDefault(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.AcceptedOn == null && uc.RejectedOn == null && uc.OrganisationInviteId == id);
+
+            if (invite == null)
+            {
+                return NotFound("Invite not found");
+            }
+
             invite.RejectedOn = DateTime.Now;
 
             db.SaveChanges();
 
             return RedirectToAction("Index", "Home");
diff --git a/Web/ViewModels/InvitationViewModel.cs b/Web/ViewModels/InvitationViewModel.cs
index a9cb582..7c969e6 100644
--- a/Web/ViewModels/InvitationViewModel.cs
+++ b/Web/ViewModels/InvitationViewModel.cs
@@ -56,10 +56,12 @@ namespace QueryTree.ViewModels
             }
         }
 
         public int OrganisationId { get; set; }
 
+        public int OrganisationInviteId { get; set; }
+
         public bool IsOrganisationAdmin { get; set; }
 
         public List<string> DatabasesMerged { get; set; }
         public List<string> DatabasesLost { get; set; }
     }
diff --git a/Web/Views/Invitations/Index.cshtml b/Web/Views/Invitations/Index.cshtml
index 1f16449..5d66acf 100644
--- a/Web/Views/Invitations/Index.cshtml
+++ b/Web/Views/Invitations/Index.cshtml
@@ -42,15 +42,15 @@
                             }
                         </ul>
                     }
 					<div class="row">
 						<div class="col-md-6">
-							@Html.ActionLink("Accept", "Accept", new { id = item.OrganisationId }, new { @class = "btn btn-lg btn-success" })
+							@Html.ActionLink("Accept", "Accept", new { id = item.OrganisationInviteId }, new { @class = "btn btn-lg btn-success" })
 						</div>
 
 						<div class="col-md-6">
-							@Html.ActionLink("Reject", "Reject", new { id = item.OrganisationId }, new { @class = "btn btn-lg btn-warning rhs-delete" })
+							@Html.ActionLink("Reject", "Reject", new { id = item.OrganisationInviteId }, new { @class = "btn btn-lg btn-warning rhs-delete" })
 						</div>
 					</div>
 				</div>
 			</div>
 		</div>

Vulnerable code:                 InvitationViewModel viewModel = new InvitationViewModel();
            var organisation = db.Organisations.FirstOrDefault(ba => ba.OrganisationId == id);
            if (organisation == null)
                return NotFound("Organisation not found");
            // remove other invitations to other organisations
            db.OrganisationInvites.RemoveWhere(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.OrganisationId != id);
            db.SaveChanges();
            var invitationsToAccept = db.OrganisationInvites
                .Where(uc => uc.InviteEmail == CurrentUser.Email && uc.OrganisationId == id)
                .ToList();
            
            foreach (var invite in invitationsToAccept)
                invite.AcceptedOn = DateTime.Now;
        
            var invite = db.OrganisationInvites.First(i => i.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && i.OrganisationId == id && i.AcceptedOn == null && i.RejectedOn == null);
							@Html.ActionLink("Accept", "Accept", new { id = item.OrganisationId }, new { @class = "btn btn-lg btn-success" })
							@Html.ActionLink("Reject", "Reject", new { id = item.OrganisationId }, new { @class = "btn btn-lg btn-warning rhs-delete" })
Fixed code:                 InvitationViewModel viewModel = new InvitationViewModel
                {
                    OrganisationInviteId = orgGrp.First().OrganisationInviteId
                };
            var invite = db.OrganisationInvites.FirstOrDefault(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.AcceptedOn == null && uc.RejectedOn == null && uc.OrganisationInviteId == id);
            if (invite == null)
                return NotFound("Invite not found");
            var organisation = db.Organisations.FirstOrDefault(ba => ba.OrganisationId == invite.OrganisationId);
            if (organisation == null)
            {
                return NotFound("Organisation not found");
            }
            invite.AcceptedOn = DateTime.Now;
            
            // reject other invitations to other organisations
            var invitesToReject = db.OrganisationInvites.Where(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.OrganisationInviteId != invite.OrganisationInviteId);
        
            foreach (var inviteToReject in invitesToReject)
                inviteToReject.RejectedOn = DateTime.Now;

            var invite = db.OrganisationInvites.FirstOrDefault(uc => uc.InviteEmail.ToLower() == CurrentUser.Email.ToLower() && uc.AcceptedOn == null && uc.RejectedOn == null && uc.OrganisationInviteId == id);

            if (invite == null)
            {
                return NotFound("Invite not found");
            }

        public int OrganisationInviteId { get; set; }

							@Html.ActionLink("Accept", "Accept", new { id = item.OrganisationInviteId }, new { @class = "btn btn-lg btn-success" })
							@Html.ActionLink("Reject", "Reject", new { id = item.OrganisationInviteId }, new { @class = "btn btn-lg btn-warning rhs-delete" })