CVE-2021-25959: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In OpenCRX, versions v4.0.0 through v5.1.0 are vulnerable to reflected Cross-site Scripting (XSS), due to unsanitized parameters in the password reset functionality. This allows execution of external javascript files on any user of the openCRX instance.
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: 2021-09-29T14:15Z
Repository: https://github.com/opencrx/opencrx
Language: Java
Files changed: build.gradle.kts, core/src/data/org.opencrx/PasswordResetConfirm.jsp, core/src/main/java/org/opencrx/kernel/tools/CopyDb.java
Commit date: 2021-07-27 21:40:57 +0200
Commit message: Fixes #94

Diff: diff --git a/build.gradle.kts b/build.gradle.kts
index 4d04b1d..0ab7fe0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -52,11 +52,11 @@ plugins {
 	java
 }
 
 allprojects {
     group = "org.opencrx"
-    version = "5.2-20210709"
+    version = "5.2-20210727"
 }
 
 java {
     sourceCompatibility = JavaVersion.VERSION_11
     targetCompatibility = JavaVersion.VERSION_11
diff --git a/core/src/data/org.opencrx/PasswordResetConfirm.jsp b/core/src/data/org.opencrx/PasswordResetConfirm.jsp
index 13bfca7..87275f6 100644
--- a/core/src/data/org.opencrx/PasswordResetConfirm.jsp
+++ b/core/src/data/org.opencrx/PasswordResetConfirm.jsp
@@ -9,11 +9,11 @@
  * ====================================================================
  *
  * This software is published under the BSD license
  * as listed below.
  *
- * Copyright (c) 2015 CRIXP Corp., Switzerland
+ * Copyright (c) 2015-2021 CRIXP Corp., Switzerland
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -58,34 +58,40 @@ java.util.*,
 java.net.*,
 java.util.Enumeration,
 java.io.PrintWriter,
 org.w3c.spi2.*,
 org.openmdx.portal.servlet.*,
+org.openmdx.kernel.log.*,
 org.openmdx.base.naming.*
 "%>
 <%
 	Boolean success = null;
 	String resetToken = request.getParameter("t");
 	String providerName = request.getParameter("p");
 	String segmentName = request.getParameter("s");
 	String id = request.getParameter("id");
 	String password1 = request.getParameter("password1");
 	String password2 = request.getParameter("password2");
+	// Get user home with supplied parameters
+	org.opencrx.kernel.home1.jmi1.UserHome userHome = null;
+	try {
+		javax.jdo.PersistenceManagerFactory pmf = org.opencrx.kernel.utils.Utils.getPersistenceManagerFactory();
+		javax.jdo.PersistenceManager pm = pmf.getPersistenceManager(id, null);
+		Path userHomePath = new Path("xri://@openmdx*org.opencrx.kernel.home1").getDescendant("provider", providerName, "segment", segmentName, "userHome", id);
+		userHome = (org.opencrx.kernel.home1.jmi1.UserHome)pm.getObjectById(userHomePath);
+	} catch(Exception e) {
+		// Silently catch exception and log as warning
+		SysLog.warning(String.format("Invalid password reset request. p=%s, s=%s, id=%s. Rejecting.", providerName, segmentName, id));
+	}
 	if(
+		userHome != null &&
 		resetToken != null && !resetToken.isEmpty() &&
-		providerName != null && !providerName.isEmpty() &&
-		segmentName != null && !segmentName.isEmpty() &&
-		id != null && !id.isEmpty() &&
 		password1 != null && !password1.isEmpty() &&
 		password2 != null && !password2.isEmpty()
 	) {
-		javax.jdo.PersistenceManagerFactory pmf = org.opencrx.kernel.utils.Utils.getPersistenceManagerFactory();
-		javax.jdo.PersistenceManager pm = pmf.getPersistenceManager(id, null);
+		javax.jdo.PersistenceManager pm = javax.jdo.JDOHelper.getPersistenceManager(userHome);
 		try {
-			org.opencrx.kernel.home1.jmi1.UserHome userHome = (org.opencrx.kernel.home1.jmi1.UserHome)pm.getObjectById(
-				new Path("xri://@openmdx*org.opencrx.kernel.home1").getDescendant("provider", providerName, "segment", segmentName, "userHome", id)
-			);
 			pm.currentTransaction().begin();
 			org.opencrx.kernel.home1.jmi1.ChangePasswordParams params = Structures.create(
 				org.opencrx.kernel.home1.jmi1.ChangePasswordParams.class, 
 	           	Datatypes.member(org.opencrx.kernel.home1.jmi1.ChangePasswordParams.Member.oldPassword, org.opencrx.kernel.backend.UserHomes.RESET_PASSWORD_PREFIX + resetToken),
 	           	Datatypes.member(org.opencrx.kernel.home1.jmi1.ChangePasswordParams.Member.newPassword, password1),
@@ -152,14 +158,12 @@ org.openmdx.base.naming.*
 			} else if(Boolean.FALSE.equals(success)) {
 %>
 				<h2>Unable to reset password</h2>
 <%
 			} else if(
-				resetToken != null && !resetToken.isEmpty() &&
-				providerName != null && !providerName.isEmpty() &&
-				segmentName != null && !segmentName.isEmpty() &&
-				id != null && !id.isEmpty()
+				userHome != null &&
+				resetToken != null && !resetToken.isEmpty()
 			) {
 %>
 			    <form role="form" class="form-signin" style="max-width:400px;margin:0 auto;" method="POST" action="PasswordResetConfirm.jsp" accept-charset="UTF-8">
       				<h2 class="form-signin-heading">Reset password for <%= id %>@<%= providerName + "/" + segmentName %></h2>					
 			    	<input type="hidden" name="t" value="<%= resetToken %>" />
diff --git a/core/src/main/java/org/opencrx/kernel/tools/CopyDb.java b/core/src/main/java/org/opencrx/kernel/tools/CopyDb.java
index 6a357ed..bed6106 100644
--- a/core/src/main/java/org/opencrx/kernel/tools/CopyDb.java
+++ b/core/src/main/java/org/opencrx/kernel/tools/CopyDb.java
@@ -250,10 +250,11 @@ public class CopyDb {
 			PreparedStatement s = connTarget.prepareStatement(currentStatement = "DELETE FROM " + dbObject + (useSuffix ? "_" : ""));
 			s.executeUpdate();
 			s.close();
 			// Read all rows from source
 			s = connSource.prepareStatement(currentStatement = "SELECT * FROM " + dbObject + (useSuffix ? "_" : ""));
+			s.setFetchSize(100);
 			ResultSet rs = s.executeQuery();
 			if(rs != null) {
 				ResultSetMetaData rsm = rs.getMetaData();
 				FastResultSet frs = new FastResultSet(rs);
 				int nRows = 0;
@@ -545,11 +546,11 @@ public class CopyDb {
 			Class.forName(jdbcDriverSource);
 			Properties props = new Properties();
 			props.put("user", usernameSource);
 			props.put("password", passwordSource);
 			Connection connSource = DriverManager.getConnection(jdbcUrlSource, props);
-			connSource.setAutoCommit(true);
+			connSource.setAutoCommit(false);
 			// Target connection
 			Class.forName(jdbcDriverTarget);
 			props = new Properties();
 			props.put("user", usernameTarget);
 			props.put("password", passwordTarget);

Vulnerable code:     version = "5.2-20210709"
 * Copyright (c) 2015 CRIXP Corp., Switzerland
		providerName != null && !providerName.isEmpty() &&
		segmentName != null && !segmentName.isEmpty() &&
		id != null && !id.isEmpty() &&
		javax.jdo.PersistenceManagerFactory pmf = org.opencrx.kernel.utils.Utils.getPersistenceManagerFactory();
		javax.jdo.PersistenceManager pm = pmf.getPersistenceManager(id, null);
			org.opencrx.kernel.home1.jmi1.UserHome userHome = (org.opencrx.kernel.home1.jmi1.UserHome)pm.getObjectById(
				new Path("xri://@openmdx*org.opencrx.kernel.home1").getDescendant("provider", providerName, "segment", segmentName, "userHome", id)
			);
				resetToken != null && !resetToken.isEmpty() &&
				providerName != null && !providerName.isEmpty() &&
				segmentName != null && !segmentName.isEmpty() &&
				id != null && !id.isEmpty()
			connSource.setAutoCommit(true);
Fixed code:     version = "5.2-20210727"
 * Copyright (c) 2015-2021 CRIXP Corp., Switzerland
org.openmdx.kernel.log.*,
	// Get user home with supplied parameters
	org.opencrx.kernel.home1.jmi1.UserHome userHome = null;
	try {
		javax.jdo.PersistenceManagerFactory pmf = org.opencrx.kernel.utils.Utils.getPersistenceManagerFactory();
		javax.jdo.PersistenceManager pm = pmf.getPersistenceManager(id, null);
		Path userHomePath = new Path("xri://@openmdx*org.opencrx.kernel.home1").getDescendant("provider", providerName, "segment", segmentName, "userHome", id);
		userHome = (org.opencrx.kernel.home1.jmi1.UserHome)pm.getObjectById(userHomePath);
	} catch(Exception e) {
		// Silently catch exception and log as warning
		SysLog.warning(String.format("Invalid password reset request. p=%s, s=%s, id=%s. Rejecting.", providerName, segmentName, id));
	}
		userHome != null &&
		javax.jdo.PersistenceManager pm = javax.jdo.JDOHelper.getPersistenceManager(userHome);
				userHome != null &&
				resetToken != null && !resetToken.isEmpty()
			s.setFetchSize(100);
			connSource.setAutoCommit(false);