CVE-2021-32792: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: mod_auth_openidc is an authentication/authorization module for the Apache 2.x HTTP server that functions as an OpenID Connect Relying Party, authenticating users against an OpenID Connect Provider. In mod_auth_openidc before version 2.4.9, there is an XSS vulnerability in when using `OIDCPreservePost On`.
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-07-26T17:15Z
Repository: https://github.com/zmartzone/mod_auth_openidc
Language: C
Files changed: src/mod_auth_openidc.c, src/mod_auth_openidc.h, src/util.c
Commit date: 2021-06-28 15:11:11 +0900
Commit message: Add a function to escape Javascript characters

Diff: diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
index 4f94743b..b5af3cc2 100644
--- a/src/mod_auth_openidc.c
+++ b/src/mod_auth_openidc.c
@@ -466,11 +466,11 @@ apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location,
 					"        %s"
 					"      }\n"
 					"    </script>\n", jmethod, json,
 					location ?
 							apr_psprintf(r->pool, "window.location='%s';\n",
-									location) :
+									oidc_util_javascript_escape(r->pool, location)) :
 									"");
 	if (location == NULL) {
 		if (javascript_method)
 			*javascript_method = apr_pstrdup(r->pool, jmethod);
 		if (javascript)
@@ -514,11 +514,11 @@ static int oidc_request_post_preserved_restore(request_rec *r,
 					"          document.forms[0].appendChild(input);\n"
 					"        }\n"
 					"        document.forms[0].action = \"%s\";\n"
 					"        document.forms[0].submit();\n"
 					"      }\n"
-					"    </script>\n", method, original_url);
+					"    </script>\n", method, oidc_util_javascript_escape(r->pool, original_url));
 
 	const char *body = "    <p>Restoring...</p>\n"
 			"    <form method=\"post\"></form>\n";
 
 	return oidc_util_html_send(r, "Restoring...", script, method, body,
@@ -1551,11 +1551,11 @@ static int oidc_session_redirect_parent_window_to_logout(request_rec *r,
 	oidc_debug(r, "enter");
 
 	char *java_script = apr_psprintf(r->pool,
 			"    <script type=\"text/javascript\">\n"
 			"      window.top.location.href = '%s?session=logout';\n"
-			"    </script>\n", oidc_get_redirect_uri(r, c));
+			"    </script>\n", oidc_util_javascript_escape(r->pool, oidc_get_redirect_uri(r, c)));
 
 	return oidc_util_html_send(r, "Redirecting...", java_script, NULL, NULL,
 			OK);
 }
 
diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
index deb5b740..8fa790b4 100644
--- a/src/mod_auth_openidc.h
+++ b/src/mod_auth_openidc.h
@@ -774,10 +774,11 @@ apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a, const
 apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str, const char *match);
 apr_byte_t oidc_json_object_get_string(apr_pool_t *pool, json_t *json, const char *name, char **value, const char *default_value);
 apr_byte_t oidc_json_object_get_int(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value);
 apr_byte_t oidc_json_object_get_bool(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value);
 char *oidc_util_html_escape(apr_pool_t *pool, const char *input);
+char *oidc_util_javascript_escape(apr_pool_t *pool, const char *input);
 void oidc_util_table_add_query_encoded_params(apr_pool_t *pool, apr_table_t *table, const char *params);
 apr_hash_t * oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1, const apr_array_header_t *k2);
 apr_hash_t * oidc_util_merge_key_sets_hash(apr_pool_t *pool, apr_hash_t *k1, apr_hash_t *k2);
 apr_byte_t oidc_util_regexp_substitute(apr_pool_t *pool, const char *input, const char *regexp, const char *replace, char **output, char **error_str);
 apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input, const char *regexp, char **output, char **error_str);
diff --git a/src/util.c b/src/util.c
index f43c398a..4c46156b 100644
--- a/src/util.c
+++ b/src/util.c
@@ -364,10 +364,91 @@ char* oidc_util_html_escape(apr_pool_t *pool, const char *s) {
 	}
 	r[j] = '\0';
 	return apr_pstrdup(pool, r);
 }
 
+/*
+ * JavaScript escape a string
+ */
+char* oidc_util_javascript_escape(apr_pool_t *pool, const char *s) {
+    const char *cp;
+    char *output;
+    size_t outputlen;
+    int i;
+
+    if (s == NULL) {
+        return NULL;
+    }
+
+    outputlen = 0;
+    for (cp = s; *cp; cp++) {
+        switch (*cp) {
+        case '\'':
+        case '"':
+        case '\\':
+        case '/':
+        case 0x0D:
+        case 0x0A:
+            outputlen += 2;
+            break;
+        case '<':
+        case '>':
+            outputlen += 4;
+            break;
+        default:
+            outputlen += 1;
+            break;
+        }
+    }
+
+    i = 0;
+    output = apr_palloc(pool, outputlen + 1);
+    for (cp = s; *cp; cp++) {
+        switch (*cp) {
+        case '\'':
+            (void)strcpy(&output[i], "\\'");
+            i += 2;
+            break;
+        case '"':
+            (void)strcpy(&output[i], "\\\"");
+            i += 2;
+            break;
+        case '\\':
+            (void)strcpy(&output[i], "\\\\");
+            i += 2;
+            break;
+        case '/':
+            (void)strcpy(&output[i], "\\/");
+            i += 2;
+            break;
+        case 0x0D:
+            (void)strcpy(&output[i], "\\r");
+            i += 2;
+            break;
+        case 0x0A:
+            (void)strcpy(&output[i], "\\n");
+            i += 2;
+            break;
+        case '<':
+            (void)strcpy(&output[i], "\\x3c");
+            i += 4;
+            break;
+        case '>':
+            (void)strcpy(&output[i], "\\x3e");
+            i += 4;
+            break;
+        default:
+            output[i] = *cp;
+            i += 1;
+            break;
+        }
+    }
+    output[i] = '\0';
+    return output;
+}
+
+
 /*
  * get the URL scheme that is currently being accessed
  */
 static const char* oidc_get_current_url_scheme(const request_rec *r) {
 	/* first see if there's a proxy/load-balancer in front of us */

Vulnerable code: 									location) :
					"    </script>\n", method, original_url);
			"    </script>\n", oidc_get_redirect_uri(r, c));
Fixed code: 									oidc_util_javascript_escape(r->pool, location)) :
					"    </script>\n", method, oidc_util_javascript_escape(r->pool, original_url));
			"    </script>\n", oidc_util_javascript_escape(r->pool, oidc_get_redirect_uri(r, c)));
char *oidc_util_javascript_escape(apr_pool_t *pool, const char *input);
/*
 * JavaScript escape a string
 */
char* oidc_util_javascript_escape(apr_pool_t *pool, const char *s) {
    const char *cp;
    char *output;
    size_t outputlen;
    int i;

    if (s == NULL) {
        return NULL;
    }

    outputlen = 0;
    for (cp = s; *cp; cp++) {
        switch (*cp) {
        case '\'':
        case '"':
        case '\\':
        case '/':
        case 0x0D:
        case 0x0A:
            outputlen += 2;
            break;
        case '<':
        case '>':
            outputlen += 4;
            break;
        default:
            outputlen += 1;
            break;
        }
    }

    i = 0;
    output = apr_palloc(pool, outputlen + 1);
    for (cp = s; *cp; cp++) {
        switch (*cp) {
        case '\'':
            (void)strcpy(&output[i], "\\'");
            i += 2;
            break;
        case '"':
            (void)strcpy(&output[i], "\\\"");
            i += 2;
            break;
        case '\\':
            (void)strcpy(&output[i], "\\\\");
            i += 2;
            break;
        case '/':
            (void)strcpy(&output[i], "\\/");
            i += 2;
            break;
        case 0x0D:
            (void)strcpy(&output[i], "\\r");
            i += 2;
            break;
        case 0x0A:
            (void)strcpy(&output[i], "\\n");
            i += 2;
            break;
        case '<':
            (void)strcpy(&output[i], "\\x3c");
            i += 4;
            break;
        case '>':
            (void)strcpy(&output[i], "\\x3e");
            i += 4;
            break;
        default:
            output[i] = *cp;
            i += 1;
            break;
        }
    }
    output[i] = '\0';
    return output;
}