CVE-2021-43810: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Admidio is a free open source user management system for websites of organizations and groups. A cross-site scripting vulnerability is present in Admidio prior to version 4.0.12. The Reflected XSS vulnerability occurs because redirect.php does not properly validate the value of the url parameter. Through this vulnerability, an attacker is capable to execute malicious scripts. This issue is patched in version 4.0.12.
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-12-07T22:15Z
Repository: https://github.com/Admidio/admidio
Language: PHP
Files changed: adm_program/system/bootstrap/function.php, adm_program/system/classes/StringUtils.php, adm_program/system/redirect.php
Commit date: 2021-12-06 18:21:41 +0100
Commit message: Cross-site Scripting (XSS) when redirect an url

Diff: diff --git a/adm_program/system/bootstrap/function.php b/adm_program/system/bootstrap/function.php
index 6ff97828c..589d390f1 100644
--- a/adm_program/system/bootstrap/function.php
+++ b/adm_program/system/bootstrap/function.php
@@ -269,11 +269,11 @@ function admFuncProcessableImageSize()
  * You can set several flags (like required value, datatype …) that should be checked.
  *
  * @param array<string,mixed> $array        The array with the element that should be checked
  * @param string              $variableName Name of the array element that should be checked
  * @param string              $datatype     The datatype like **string**, **numeric**, **int**, **float**, **bool**, **boolean**, **html**,
- *                                          **date**, **file** or **folder** that is expected and which will be checked.
+ *                                          **url**, **date**, **file** or **folder** that is expected and which will be checked.
  *                                          Datatype **date** expects a date that has the Admidio default format from the
  *                                          preferences or the english date format **Y-m-d**
  * @param array<string,mixed> $options      (optional) An array with the following possible entries:
  *                                          - defaultValue : A value that will be set if the variable has no value
  *                                          - **requireValue** : If set to **true** than a value is required otherwise the function
@@ -338,11 +338,12 @@ function admFuncVariableIsValid(array $array, $variableName, $datatype, array $o
     if ($optionsAll['validValues'] !== null && !in_array($value, $optionsAll['validValues'], true)) {
         $errorMessage = $gL10n->get('SYS_INVALID_PAGE_VIEW');
     }
 
     switch ($datatype) {
-        case 'file':
+        case 'file': // fallthrough
+        case 'folder':
             try {
                 if ($value !== '') {
                     StringUtils::strIsValidFileName($value, false);
                 }
             } catch (AdmException $e) {
@@ -397,10 +398,16 @@ function admFuncVariableIsValid(array $array, $variableName, $datatype, array $o
 
         case 'html':
             // check html string vor invalid tags and scripts
             $value = Htmlawed::filter(stripslashes($value), array('safe' => 1));
             break;
+
+        case 'url':
+            if (!StringUtils::strValidCharacters($value, 'url')) {
+                $errorMessage = $gL10n->get('SYS_INVALID_PAGE_VIEW');
+            }
+            break;
     }
 
     // wurde kein Fehler entdeckt, dann den Inhalt der Variablen zurueckgeben
     if ($errorMessage === '') {
         return $value;
diff --git a/adm_program/system/classes/StringUtils.php b/adm_program/system/classes/StringUtils.php
index 79e0e7fc4..8090567ab 100644
--- a/adm_program/system/classes/StringUtils.php
+++ b/adm_program/system/classes/StringUtils.php
@@ -167,11 +167,12 @@ final class StringUtils
                 break;
             case 'folder':
                 $validRegex = '=^[^/?*;:~<>|\"\\\\]+$=';
                 break;
             case 'url':
-                $validRegex = '/^[\wáàâåäæçéèêîñóòôöõøœúùûüß$&!?() \/%=#:~.@+-]+$/i';
+                //$validRegex = '/^[\wáàâåäæçéèêîñóòôöõøœúùûüß$&!?() \/%=#:~.@+-]+$/i';
+                $validRegex = '/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i';
                 break;
             case 'phone':
                 $validRegex = '/^[\d() \/+-]+$/i';
                 break;
             default:
diff --git a/adm_program/system/redirect.php b/adm_program/system/redirect.php
index 37c48f5b2..c9657aaea 100644
--- a/adm_program/system/redirect.php
+++ b/adm_program/system/redirect.php
@@ -17,11 +17,11 @@
  *****************************************************************************/
 
 require_once(__DIR__ . '/common.php');
 
 // Initialize and check the parameters
-$getUrl = admFuncVariableIsValid($_GET, 'url', 'string', array('requireValue' => true));
+$getUrl = admFuncVariableIsValid($_GET, 'url', 'url', array('requireValue' => true));
 
 if (filter_var($getUrl, FILTER_VALIDATE_URL) === false) {
     $gMessage->show($gL10n->get('SYS_REDIRECT_URL_INVALID'));
     // => EXIT
 }

Vulnerable code:  *                                          **date**, **file** or **folder** that is expected and which will be checked.
        case 'file':
                $validRegex = '/^[\wáàâåäæçéèêîñóòôöõøœúùûüß$&!?() \/%=#:~.@+-]+$/i';
$getUrl = admFuncVariableIsValid($_GET, 'url', 'string', array('requireValue' => true));
Fixed code:  *                                          **url**, **date**, **file** or **folder** that is expected and which will be checked.
        case 'file': // fallthrough
        case 'folder':

        case 'url':
            if (!StringUtils::strValidCharacters($value, 'url')) {
                $errorMessage = $gL10n->get('SYS_INVALID_PAGE_VIEW');
            }
            break;
                //$validRegex = '/^[\wáàâåäæçéèêîñóòôöõøœúùûüß$&!?() \/%=#:~.@+-]+$/i';
                $validRegex = '/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i';
$getUrl = admFuncVariableIsValid($_GET, 'url', 'url', array('requireValue' => true));