CVE-2022-4560: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in Joget up to 7.0.31. It has been rated as problematic. This issue affects the function getInternalJsCssLib of the file wflow-core/src/main/java/org/joget/plugin/enterprise/UniversalTheme.java of the component wflow-core. The manipulation of the argument key leads to cross site scripting. The attack may be initiated remotely. Upgrading to version 7.0.32 is able to address this issue. The name of the patch is ecf8be8f6f0cb725c18536ddc726d42a11bdaa1b. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-215963.
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: 2022-12-16T17:15Z
Repository: https://github.com/jogetworkflow/jw-community
Language: Java
Files changed: wflow-consoleweb/src/main/java/org/joget/apps/app/controller/UserviewWebController.java, wflow-core/src/main/java/org/joget/apps/userview/service/UserviewService.java, wflow-core/src/main/java/org/joget/plugin/enterprise/UniversalTheme.java
Commit date: 2022-08-29 20:35:59 +0800
Commit message: Fixed: wflow-core - Userview - Fixed XSS issue on key parameter. @7.0-SNAPSHOT
Diff: diff --git a/wflow-consoleweb/src/main/java/org/joget/apps/app/controller/UserviewWebController.java b/wflow-consoleweb/src/main/java/org/joget/apps/app/controller/UserviewWebController.java
index 93be36b39..b27591695 100755
--- a/wflow-consoleweb/src/main/java/org/joget/apps/app/controller/UserviewWebController.java
+++ b/wflow-consoleweb/src/main/java/org/joget/apps/app/controller/UserviewWebController.java
@@ -65,11 +65,11 @@ public class UserviewWebController {
         } else if (embed == null) {
             embed = true;
         }
         
         //check for empty key
-        if (key != null && key.equals(Userview.USERVIEW_KEY_EMPTY_VALUE)) {
+        if (key == null || (key != null && key.equals(Userview.USERVIEW_KEY_EMPTY_VALUE))) {
             key = "";
         }
         
         // retrieve app and userview
         AppDefinition appDef = appService.getPublishedAppDefinition(appId);
diff --git a/wflow-core/src/main/java/org/joget/apps/userview/service/UserviewService.java b/wflow-core/src/main/java/org/joget/apps/userview/service/UserviewService.java
index 4228b78b6..1ec49ec3e 100755
--- a/wflow-core/src/main/java/org/joget/apps/userview/service/UserviewService.java
+++ b/wflow-core/src/main/java/org/joget/apps/userview/service/UserviewService.java
@@ -170,10 +170,11 @@ public class UserviewService {
         requestParameters.put("contextPath", contextPath);
         requestParameters.put("isPreview", Boolean.toString(preview));
         requestParameters.put("embed", Boolean.toString(embed));
         requestParameters.put("appId", appDef.getAppId());
         requestParameters.put("appVersion", appDef.getVersion().toString());
+        requestParameters.put("key", (key != null)?key:"");
 
         String appId = appDef.getId();
         String appVersion = appDef.getVersion().toString();
         Userview userview = new Userview();
         userview.setParams(requestParameters);
diff --git a/wflow-core/src/main/java/org/joget/plugin/enterprise/UniversalTheme.java b/wflow-core/src/main/java/org/joget/plugin/enterprise/UniversalTheme.java
index 46b5f02d0..d30e0472f 100644
--- a/wflow-core/src/main/java/org/joget/plugin/enterprise/UniversalTheme.java
+++ b/wflow-core/src/main/java/org/joget/plugin/enterprise/UniversalTheme.java
@@ -372,23 +372,23 @@ public class UniversalTheme extends UserviewV5Theme implements UserviewPwaTheme,
                 String buildNumber = ResourceBundleUtil.getMessage("build.number");
                 
                 String serviceWorkerUrl = data.get("context_path") + "/web/userview/" + appId + "/" + userviewId + "/"+key+"/serviceworker";
                 jsCssLink += "<script>$(function() {"
                         + "var initPwaUtil = function(){"
-                        + "PwaUtil.contextPath = '" + data.get("context_path") + "';"
-                        + "PwaUtil.userviewKey = '" + key + "';"
-                        + "PwaUtil.homePageLink = '" + data.get("home_page_link") + "';"
-                        + "PwaUtil.serviceWorkerPath = '" + serviceWorkerUrl + "';"
-                        + "PwaUtil.subscriptionApiPath = '" + data.get("context_path") + "/web/console/profile/subscription';"
+                        + "PwaUtil.contextPath = '" + StringUtil.escapeString(data.get("context_path").toString(), StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.userviewKey = '" + StringUtil.escapeString(key, StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.homePageLink = '" + StringUtil.escapeString(data.get("home_page_link").toString(), StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.serviceWorkerPath = '" + StringUtil.escapeString(serviceWorkerUrl, StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.subscriptionApiPath = '" + StringUtil.escapeString(data.get("context_path").toString(), StringUtil.TYPE_JAVASCIPT, null) + "/web/console/profile/subscription';"
                         + "PwaUtil.pushEnabled = " + pushEnabled + ";"
-                        + "PwaUtil.currentUsername = '" + workflowUserManager.getCurrentUsername() + "';"
-                        + "PwaUtil.onlineNotificationMessage = '" + pwaOnlineNotificationMessage + "';"
-                        + "PwaUtil.offlineNotificationMessage = '" + pwaOfflineNotificationMessage + "';"
-                        + "PwaUtil.loginPromptMessage = '" + pwaLoginPromptMessage + "';"
-                        + "PwaUtil.syncingMessage = '" + pwaSyncingMessage + "';"
-                        + "PwaUtil.syncFailedMessage = '" + pwaSyncFailedMessage + "';"
-                        + "PwaUtil.syncSuccessMessage = '" + pwaSyncSuccessMessage + "';"
+                        + "PwaUtil.currentUsername = '" + StringUtil.escapeString(workflowUserManager.getCurrentUsername(), StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.onlineNotificationMessage = '" + StringUtil.escapeString(pwaOnlineNotificationMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.offlineNotificationMessage = '" + StringUtil.escapeString(pwaOfflineNotificationMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.loginPromptMessage = '" + StringUtil.escapeString(pwaLoginPromptMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.syncingMessage = '" + StringUtil.escapeString(pwaSyncingMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
+                        + "PwaUtil.syncFailedMessage = '" + StringUtil.escapeString(pwaSyncFailedMessage, StringUtil.TYPE_JAVA, null) + "';"
+                        + "PwaUtil.syncSuccessMessage = '" + StringUtil.escapeString(pwaSyncSuccessMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
                         + "PwaUtil.isEmbedded = " + isEmbedded + ";"
                         + "PwaUtil.register();"
                         + "PwaUtil.init();"
                         + "};"
                         + "if (typeof PwaUtil !== \"undefined\") {initPwaUtil();} else { $(document).on(\"PwaUtil.ready\", function(){ initPwaUtil(); });}"

Vulnerable code:         if (key != null && key.equals(Userview.USERVIEW_KEY_EMPTY_VALUE)) {
                        + "PwaUtil.contextPath = '" + data.get("context_path") + "';"
                        + "PwaUtil.userviewKey = '" + key + "';"
                        + "PwaUtil.homePageLink = '" + data.get("home_page_link") + "';"
                        + "PwaUtil.serviceWorkerPath = '" + serviceWorkerUrl + "';"
                        + "PwaUtil.subscriptionApiPath = '" + data.get("context_path") + "/web/console/profile/subscription';"
                        + "PwaUtil.currentUsername = '" + workflowUserManager.getCurrentUsername() + "';"
                        + "PwaUtil.onlineNotificationMessage = '" + pwaOnlineNotificationMessage + "';"
                        + "PwaUtil.offlineNotificationMessage = '" + pwaOfflineNotificationMessage + "';"
                        + "PwaUtil.loginPromptMessage = '" + pwaLoginPromptMessage + "';"
                        + "PwaUtil.syncingMessage = '" + pwaSyncingMessage + "';"
                        + "PwaUtil.syncFailedMessage = '" + pwaSyncFailedMessage + "';"
                        + "PwaUtil.syncSuccessMessage = '" + pwaSyncSuccessMessage + "';"
Fixed code:         if (key == null || (key != null && key.equals(Userview.USERVIEW_KEY_EMPTY_VALUE))) {
        requestParameters.put("key", (key != null)?key:"");
                        + "PwaUtil.contextPath = '" + StringUtil.escapeString(data.get("context_path").toString(), StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.userviewKey = '" + StringUtil.escapeString(key, StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.homePageLink = '" + StringUtil.escapeString(data.get("home_page_link").toString(), StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.serviceWorkerPath = '" + StringUtil.escapeString(serviceWorkerUrl, StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.subscriptionApiPath = '" + StringUtil.escapeString(data.get("context_path").toString(), StringUtil.TYPE_JAVASCIPT, null) + "/web/console/profile/subscription';"
                        + "PwaUtil.currentUsername = '" + StringUtil.escapeString(workflowUserManager.getCurrentUsername(), StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.onlineNotificationMessage = '" + StringUtil.escapeString(pwaOnlineNotificationMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.offlineNotificationMessage = '" + StringUtil.escapeString(pwaOfflineNotificationMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.loginPromptMessage = '" + StringUtil.escapeString(pwaLoginPromptMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.syncingMessage = '" + StringUtil.escapeString(pwaSyncingMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"
                        + "PwaUtil.syncFailedMessage = '" + StringUtil.escapeString(pwaSyncFailedMessage, StringUtil.TYPE_JAVA, null) + "';"
                        + "PwaUtil.syncSuccessMessage = '" + StringUtil.escapeString(pwaSyncSuccessMessage, StringUtil.TYPE_JAVASCIPT, null) + "';"