CVE-2023-5564: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository froxlor/froxlor prior to 2.1.0-dev1.
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: 4.8
Published: 2023-10-13T01:15Z
Repository: https://github.com/froxlor/froxlor
Language: PHP
Files changed: composer.json, composer.lock, lib/Froxlor/System/Markdown.php, lib/Froxlor/UI/Callbacks/Customer.php, lib/Froxlor/UI/Callbacks/Text.php, lib/Froxlor/UI/Panel/FroxlorTwig.php, lng/de.lng.php, lng/en.lng.php, templates/Froxlor/user/index.html.twig
Commit date: 2023-10-02 15:40:50 +0200
Commit message: enable markdown syntax in custom_notes field

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

Diff: diff --git a/composer.json b/composer.json
index 5ec331e067..f3d884fb7b 100644
--- a/composer.json
+++ b/composer.json
@@ -51,14 +51,14 @@
 		"monolog/monolog": "^1.24",
 		"robthree/twofactorauth": "^1.6",
 		"froxlor/idna-convert-legacy": "^2.1",
 		"voku/anti-xss": "^4.1",
 		"twig/twig": "^3.3",
-		"erusev/parsedown": "^1.7",
 		"symfony/console": "^5.4",
 		"pear/net_dns2": "^1.5",
-		"amnuts/opcache-gui": "^3.4"
+		"amnuts/opcache-gui": "^3.4",
+		"league/commonmark": "^2.4"
     },
 	"require-dev": {
 		"phpunit/phpunit": "^9",
 		"ext-pcntl": "*",
 		"phpcompatibility/php-compatibility": "*",
diff --git a/composer.lock b/composer.lock
index a24fe7bdd8..c63fbb7a5f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2,11 +2,11 @@
     "_readme": [
         "This file locks the dependencies of your project to a known state",
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "1f9acc318c920b38ee8141e74298655a",
+    "content-hash": "cf7a156f58ab3f86204a4b4d263621e2",
     "packages": [
         {
             "name": "amnuts/opcache-gui",
             "version": "3.5.1",
             "source": {
@@ -67,58 +67,83 @@
                 }
             ],
             "time": "2023-08-25T18:02:59+00:00"
         },
         {
-            "name": "erusev/parsedown",
-            "version": "1.7.4",
+            "name": "dflydev/dot-access-data",
+            "version": "v3.0.2",
             "source": {
                 "type": "git",
-                "url": "https://github.com/erusev/parsedown.git",
-                "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
+                "url": "https://github.com/dflydev/dflydev-dot-access-data.git",
+                "reference": "f41715465d65213d644d3141a6a93081be5d3549"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
-                "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
+                "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549",
+                "reference": "f41715465d65213d644d3141a6a93081be5d3549",
                 "shasum": ""
             },
             "require": {
-                "ext-mbstring": "*",
-                "php": ">=5.3.0"
+                "php": "^7.1 || ^8.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35"
+                "phpstan/phpstan": "^0.12.42",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
+                "scrutinizer/ocular": "1.6.0",
+                "squizlabs/php_codesniffer": "^3.5",
+                "vimeo/psalm": "^4.0.0"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.x-dev"
+                }
+            },
             "autoload": {
-                "psr-0": {
-                    "Parsedown": ""
+                "psr-4": {
+                    "Dflydev\\DotAccessData\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
             "authors": [
                 {
-                    "name": "Emanuil Rusev",
-                    "email": "hello@erusev.com",
-                    "homepage": "http://erusev.com"
+                    "name": "Dragonfly Development Inc.",
+                    "email": "info@dflydev.com",
+                    "homepage": "http://dflydev.com"
+                },
+                {
+                    "name": "Beau Simensen",
+                    "email": "beau@dflydev.com",
+                    "homepage": "http://beausimensen.com"
+                },
+                {
+                    "name": "Carlos Frutos",
+                    "email": "carlos@kiwing.it",
+                    "homepage": "https://github.com/cfrutos"
+                },
+                {
+                    "name": "Colin O'Dell",
+                    "email": "colinodell@gmail.com",
+                    "homepage": "https://www.colinodell.com"
                 }
             ],
-            "description": "Parser for Markdown.",
-            "homepage": "http://parsedown.org",
+            "description": "Given a deep data structure, access data by dot notation.",
+            "homepage": "https://github.com/dflydev/dflydev-dot-access-data",
             "keywords": [
-                "markdown",
-                "parser"
+                "access",
+                "data",
+                "dot",
+                "notation"
             ],
             "support": {
-                "issues": "https://github.com/erusev/parsedown/issues",
-                "source": "https://github.com/erusev/parsedown/tree/1.7.x"
+                "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
+                "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2"
             },
-            "time": "2019-12-30T22:54:17+00:00"
+            "time": "2022-10-27T11:44:00+00:00"
         },
         {
             "name": "froxlor/idna-convert-legacy",
             "version": "v2.1.2",
             "source": {
@@ -172,10 +197,198 @@
             "support": {
                 "source": "https://github.com/Froxlor/idna-convert-legacy/tree/v2.1.2"
             },
             "time": "2019-12-31T12:16:30+00:00"
         },
+        {
+            "name": "league/commonmark",
+            "version": "2.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/commonmark.git",
+                "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5",
+                "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "league/config": "^1.1.1",
+                "php": "^7.4 || ^8.0",
+                "psr/event-dispatcher": "^1.0",
+                "symfony/deprecation-contracts": "^2.1 || ^3.0",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "require-dev": {
+                "cebe/markdown": "^1.0",
+                "commonmark/cmark": "0.30.0",
+                "commonmark/commonmark.js": "0.30.0",
+                "composer/package-versions-deprecated": "^1.8",
+                "embed/embed": "^4.4",
+                "erusev/parsedown": "^1.0",
+                "ext-json": "*",
+                "github/gfm": "0.29.0",
+                "michelf/php-markdown": "^1.4 || ^2.0",
+                "nyholm/psr7": "^1.5",
+                "phpstan/phpstan": "^1.8.2",
+                "phpunit/phpunit": "^9.5.21",
+                "scrutinizer/ocular": "^1.8.1",
+                "symfony/finder": "^5.3 | ^6.0",
+                "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
+                "unleashedtech/php-coding-standard": "^3.1.1",
+                "vimeo/psalm": "^4.24.0 || ^5.0.0"
+            },
+            "suggest": {
+                "symfony/yaml": "v2.3+ required if using the Front Matter extension"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\CommonMark\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Colin O'Dell",
+                    "email": "colinodell@gmail.com",
+                    "homepage": "https://www.colinodell.com",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
+            "homepage": "https://commonmark.thephpleague.com",
+            "keywords": [
+                "commonmark",
+                "flavored",
+                "gfm",
+                "github",
+                "github-flavored",
+                "markdown",
+                "md",
+                "parser"
+            ],
+            "support": {
+                "docs": "https://commonmark.thephpleague.com/",
+                "forum": "https://github.com/thephpleague/commonmark/discussions",
+                "issues": "https://github.com/thephpleague/commonmark/issues",
+                "rss": "https://github.com/thephpleague/commonmark/releases.atom",
+                "source": "https://github.com/thephpleague/commonmark"
+            },
+            "funding": [
+                {
+                    "url": "https://www.colinodell.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.paypal.me/colinpodell/10.00",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/colinodell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/league/commonmark",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-08-30T16:55:00+00:00"
+        },
+        {
+            "name": "league/config",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/config.git",
+                "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
+                "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
+                "shasum": ""
+            },
+            "require": {
+                "dflydev/dot-access-data": "^3.0.1",
+                "nette/schema": "^1.2",
+                "php": "^7.4 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^1.8.2",
+                "phpunit/phpunit": "^9.5.5",
+                "scrutinizer/ocular": "^1.8.1",
+                "unleashedtech/php-coding-standard": "^3.1",
+                "vimeo/psalm": "^4.7.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Config\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Colin O'Dell",
+                    "email": "colinodell@gmail.com",
+                    "homepage": "https://www.colinodell.com",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "Define configuration arrays with strict schemas and access values with dot notation",
+            "homepage": "https://config.thephpleague.com",
+            "keywords": [
+                "array",
+                "config",
+                "configuration",
+                "dot",
+                "dot-access",
+                "nested",
+                "schema"
+            ],
+            "support": {
+                "docs": "https://config.thephpleague.com/",
+                "issues": "https://github.com/thephpleague/config/issues",
+                "rss": "https://github.com/thephpleague/config/releases.atom",
+                "source": "https://github.com/thephpleague/config"
+            },
+            "funding": [
+                {
+                    "url": "https://www.colinodell.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.paypal.me/colinpodell/10.00",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/colinodell",
+                    "type": "github"
+                }
+            ],
+            "time": "2022-12-11T20:36:23+00:00"
+        },
         {
             "name": "monolog/monolog",
             "version": "1.27.1",
             "source": {
                 "type": "git",
@@ -258,10 +471,158 @@
                     "type": "tidelift"
                 }
             ],
             "time": "2022-06-09T08:53:42+00:00"
         },
+        {
+            "name": "nette/schema",
+            "version": "v1.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/schema.git",
+                "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
+                "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
+                "shasum": ""
+            },
+            "require": {
+                "nette/utils": "^2.5.7 || ^3.1.5 ||  ^4.0",
+                "php": "7.1 - 8.3"
+            },
+            "require-dev": {
+                "nette/tester": "^2.3 || ^2.4",
+                "phpstan/phpstan-nette": "^1.0",
+                "tracy/tracy": "^2.7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "📐 Nette Schema: validating data structures against a given Schema.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "config",
+                "nette"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/schema/issues",
+                "source": "https://github.com/nette/schema/tree/v1.2.4"
+            },
+            "time": "2023-08-05T18:56:25+00:00"
+        },
+        {
+            "name": "nette/utils",
+            "version": "v3.2.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/utils.git",
+                "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2",
+                "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2 <8.4"
+            },
+            "conflict": {
+                "nette/di": "<3.0.6"
+            },
+            "require-dev": {
+                "jetbrains/phpstorm-attributes": "dev-master",
+                "nette/tester": "~2.0",
+                "phpstan/phpstan": "^1.0",
+                "tracy/tracy": "^2.3"
+            },
+            "suggest": {
+                "ext-gd": "to use Image",
+                "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+                "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+                "ext-json": "to use Nette\\Utils\\Json",
+                "ext-mbstring": "to use Strings::lower() etc...",
+                "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
+                "ext-xml": "to use Strings::length() etc. when mbstring is not available"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "array",
+                "core",
+                "datetime",
+                "images",
+                "json",
+                "nette",
+                "paginator",
+                "password",
+                "slugify",
+                "string",
+                "unicode",
+                "utf-8",
+                "utility",
+                "validation"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/utils/issues",
+                "source": "https://github.com/nette/utils/tree/v3.2.10"
+            },
+            "time": "2023-07-30T15:38:18+00:00"
+        },
         {
             "name": "pear/net_dns2",
             "version": "v1.5.3",
             "source": {
                 "type": "git",
@@ -437,10 +798,60 @@
                 "issues": "https://github.com/php-fig/container/issues",
                 "source": "https://github.com/php-fig/container/tree/1.1.2"
             },
             "time": "2021-11-05T16:50:12+00:00"
         },
+        {
+            "name": "psr/event-dispatcher",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/event-dispatcher.git",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\EventDispatcher\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Standard interfaces for event handling.",
+            "keywords": [
+                "events",
+                "psr",
+                "psr-14"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/event-dispatcher/issues",
+                "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+            },
+            "time": "2019-01-08T18:20:26+00:00"
+        },
         {
             "name": "psr/log",
             "version": "1.1.4",
             "source": {
                 "type": "git",
diff --git a/lib/Froxlor/System/Markdown.php b/lib/Froxlor/System/Markdown.php
new file mode 100644
index 0000000000..a7a6e50688
--- /dev/null
+++ b/lib/Froxlor/System/Markdown.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * This file is part of the Froxlor project.
+ * Copyright (c) 2010 the Froxlor Team (see authors).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you can also view it online at
+ * https://files.froxlor.org/misc/COPYING.txt
+ *
+ * @copyright  the authors
+ * @author     Froxlor team <team@froxlor.org>
+ * @license    https://files.froxlor.org/misc/COPYING.txt GPLv2
+ */
+
+namespace Froxlor\System;
+
+use League\CommonMark\Exception\CommonMarkException;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+class Markdown
+{
+
+	private static $converter = null;
+
+	public static function converter(): ?GithubFlavoredMarkdownConverter
+	{
+		if (is_null(self::$converter)) {
+			self::$converter = new GithubFlavoredMarkdownConverter([
+				'html_input' => 'strip',
+				'allow_unsafe_links' => false,
+			]);
+		}
+		return self::$converter;
+	}
+
+	public static function cleanCustomNotes(string $note = ""): string
+	{
+		if (!empty($note)) {
+			try {
+				$note = self::converter()->convert($note)->getContent();
+			} catch (CommonMarkException $e) {
+				$note = "";
+			}
+		}
+		return $note;
+	}
+}
diff --git a/lib/Froxlor/UI/Callbacks/Customer.php b/lib/Froxlor/UI/Callbacks/Customer.php
index b1c1016f51..aa2cb0613b 100644
--- a/lib/Froxlor/UI/Callbacks/Customer.php
+++ b/lib/Froxlor/UI/Callbacks/Customer.php
@@ -24,19 +24,21 @@
  */
 
 namespace Froxlor\UI\Callbacks;
 
 use Froxlor\Settings;
+use Froxlor\System\Markdown;
 
 class Customer
 {
-	public static function isLocked(array $attributes)
+	public static function isLocked(array $attributes): bool
 	{
 		return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
 			&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'));
 	}
 
-	public static function hasNote(array $attributes)
+	public static function hasNote(array $attributes): bool
 	{
-		return !empty($attributes['fields']['custom_notes']);
+		$cleanNote = Markdown::cleanCustomNotes($attributes['fields']['custom_notes'] ?? "");
+		return !empty($cleanNote);
 	}
 }
diff --git a/lib/Froxlor/UI/Callbacks/Text.php b/lib/Froxlor/UI/Callbacks/Text.php
index 22bbf91fa6..9e63e3c302 100644
--- a/lib/Froxlor/UI/Callbacks/Text.php
+++ b/lib/Froxlor/UI/Callbacks/Text.php
@@ -27,10 +27,11 @@ namespace Froxlor\UI\Callbacks;
 
 use Froxlor\CurrentUser;
 use Froxlor\Database\Database;
 use Froxlor\Froxlor;
 use Froxlor\PhpHelper;
+use Froxlor\System\Markdown;
 use Froxlor\UI\Panel\UI;
 use Froxlor\User;
 use PDO;
 
 class Text
@@ -91,11 +92,11 @@ class Text
 		$note = $attributes['fields']['custom_notes'] ?? '';
 		return [
 			'entry' => $attributes['fields']['id'],
 			'id' => 'cnModal' . $attributes['fields']['id'],
 			'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']),
-			'body' => nl2br($note)
+			'body' => nl2br(Markdown::cleanCustomNotes($note))
 		];
 	}
 
 	public static function apikeyDetailModal(array $attributes): array
 	{
diff --git a/lib/Froxlor/UI/Panel/FroxlorTwig.php b/lib/Froxlor/UI/Panel/FroxlorTwig.php
index d2f491757a..3cc32c1228 100644
--- a/lib/Froxlor/UI/Panel/FroxlorTwig.php
+++ b/lib/Froxlor/UI/Panel/FroxlorTwig.php
@@ -27,10 +27,11 @@ declare(strict_types=1);
 
 namespace Froxlor\UI\Panel;
 
 use Froxlor\Idna\IdnaWrapper;
 use Froxlor\Settings;
+use Froxlor\System\Markdown;
 use Parsedown;
 use Twig\Extension\AbstractExtension;
 use Twig\TwigFilter;
 use Twig\TwigFunction;
 use Twig\TwigTest;
@@ -51,13 +52,13 @@ class FroxlorTwig extends AbstractExtension
 			]),
 			new TwigFilter('idnDecode', [
 				$this,
 				'idnDecodeFilter'
 			]),
-			new TwigFilter('parsedown', [
+			new TwigFilter('markdown', [
 				$this,
-				'callParsedown'
+				'callMarkdown'
 			])
 		];
 	}
 
 	public function getTests()
@@ -146,14 +147,13 @@ class FroxlorTwig extends AbstractExtension
 	public function getLink($linkopts)
 	{
 		return UI::getLinker()->getLink($linkopts);
 	}
 
-	public function callParsedown($string)
+	public function callMarkdown($string): string
 	{
-		$pd = new Parsedown();
-		return $pd->line($string);
+		return Markdown::cleanCustomNotes($string ?? "");
 	}
 
 	/**
 	 *
 	 * {@inheritdoc}
diff --git a/lng/de.lng.php b/lng/de.lng.php
index ab66b97f89..a5ac336889 100644
--- a/lng/de.lng.php
+++ b/lng/de.lng.php
@@ -2211,11 +2211,11 @@ Vielen Dank, Ihr Administrator',
 		'dbupdate_required' => 'Froxlor-Dateien wurden aktualisiert, Datenbank-Aktualisierung notwendig',
 	],
 	'usersettings' => [
 		'custom_notes' => [
 			'title' => 'Eigene Notizen',
-			'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.',
+			'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.<br>Markdown ist unterstützt, HTML wird entfernt.',
 			'show' => 'Zeige die Notizen auf dem Dashboard des Benutzers',
 		],
 		'api_allowed' => [
 			'title' => 'Erlaube API Zugriff',
 			'description' => 'Wenn in den Einstellungen aktiviert, kann der Benutzer API Schlüssel erstellen und auf die froxlor API Zugreifen',
diff --git a/lng/en.lng.php b/lng/en.lng.php
index 010c2dfb81..24cd510b70 100644
--- a/lng/en.lng.php
+++ b/lng/en.lng.php
@@ -2344,11 +2344,11 @@ Yours sincerely, your administrator',
 		'dbupdate_required' => 'Froxlor files have been updated, database update required',
 	],
 	'usersettings' => [
 		'custom_notes' => [
 			'title' => 'Custom notes',
-			'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.',
+			'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.<br>Markdown is supported, HTML will be removed.',
 			'show' => 'Show your notes on the dashboard of the user',
 		],
 		'api_allowed' => [
 			'title' => 'Allow API access',
 			'description' => 'When enabled in the settings, this user can create API keys and access the froxlor API',
diff --git a/templates/Froxlor/user/index.html.twig b/templates/Froxlor/user/index.html.twig
index 8c3ef47903..7d40f263cc 100644
--- a/templates/Froxlor/user/index.html.twig
+++ b/templates/Froxlor/user/index.html.twig
@@ -134,16 +134,16 @@
 							</li>
 						{% endif %}
 					</ul>
 				</div>
 
-				{% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %}
+				{% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
 					<div class="card mb-3">
 						<ul class="list-group list-group-flush">
 							<li class="list-group-item list-group-item-info d-flex justify-content-between align-items-start">
 								<div class="ms-2 me-auto">
-									{{ userinfo.custom_notes|nl2br|raw }}
+									{{ userinfo.custom_notes|markdown|raw }}
 								</div>
 							</li>
 						</ul>
 					</div>
 				{% endif %}
@@ -261,14 +261,14 @@
 									{{ userinfo.zipcode }}
 									{{ userinfo.city }}
 								</div>
 							</li>
 						{% endif %}
-						{% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %}
+						{% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
 							<li class="list-group-item list-group-item-info d-flex justify-content-between align-items-start">
 								<div class="ms-2 me-auto">
-									{{ userinfo.custom_notes|nl2br|raw }}
+									{{ userinfo.custom_notes|markdown|raw }}
 								</div>
 							</li>
 						{% endif %}
 					</ul>
 				</div>

Vulnerable code: 		"erusev/parsedown": "^1.7",
		"amnuts/opcache-gui": "^3.4"
    "content-hash": "1f9acc318c920b38ee8141e74298655a",
            "name": "erusev/parsedown",
            "version": "1.7.4",
                "url": "https://github.com/erusev/parsedown.git",
                "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
                "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
                "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
                "ext-mbstring": "*",
                "php": ">=5.3.0"
                "phpunit/phpunit": "^4.8.35"
                "psr-0": {
                    "Parsedown": ""
                    "name": "Emanuil Rusev",
                    "email": "hello@erusev.com",
                    "homepage": "http://erusev.com"
            "description": "Parser for Markdown.",
            "homepage": "http://parsedown.org",
                "markdown",
                "parser"
                "issues": "https://github.com/erusev/parsedown/issues",
                "source": "https://github.com/erusev/parsedown/tree/1.7.x"
            "time": "2019-12-30T22:54:17+00:00"
	public static function isLocked(array $attributes)
	public static function hasNote(array $attributes)
		return !empty($attributes['fields']['custom_notes']);
			'body' => nl2br($note)
			new TwigFilter('parsedown', [
				'callParsedown'
	public function callParsedown($string)
		$pd = new Parsedown();
		return $pd->line($string);
			'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.',
			'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.',
				{% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %}
									{{ userinfo.custom_notes|nl2br|raw }}
						{% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %}
									{{ userinfo.custom_notes|nl2br|raw }}
Fixed code: 		"amnuts/opcache-gui": "^3.4",
		"league/commonmark": "^2.4"
    "content-hash": "cf7a156f58ab3f86204a4b4d263621e2",
            "name": "dflydev/dot-access-data",
            "version": "v3.0.2",
                "url": "https://github.com/dflydev/dflydev-dot-access-data.git",
                "reference": "f41715465d65213d644d3141a6a93081be5d3549"
                "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549",
                "reference": "f41715465d65213d644d3141a6a93081be5d3549",
                "php": "^7.1 || ^8.0"
                "phpstan/phpstan": "^0.12.42",
                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
                "scrutinizer/ocular": "1.6.0",
                "squizlabs/php_codesniffer": "^3.5",
                "vimeo/psalm": "^4.0.0"
            "extra": {
                "branch-alias": {
                    "dev-main": "3.x-dev"
                }
            },
                "psr-4": {
                    "Dflydev\\DotAccessData\\": "src/"
                    "name": "Dragonfly Development Inc.",
                    "email": "info@dflydev.com",
                    "homepage": "http://dflydev.com"
                },
                {
                    "name": "Beau Simensen",
                    "email": "beau@dflydev.com",
                    "homepage": "http://beausimensen.com"
                },
                {
                    "name": "Carlos Frutos",
                    "email": "carlos@kiwing.it",
                    "homepage": "https://github.com/cfrutos"
                },
                {
                    "name": "Colin O'Dell",
                    "email": "colinodell@gmail.com",
                    "homepage": "https://www.colinodell.com"
            "description": "Given a deep data structure, access data by dot notation.",
            "homepage": "https://github.com/dflydev/dflydev-dot-access-data",
                "access",
                "data",
                "dot",
                "notation"
                "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
                "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2"
            "time": "2022-10-27T11:44:00+00:00"
        {
            "name": "league/commonmark",
            "version": "2.4.1",
            "source": {
                "type": "git",
                "url": "https://github.com/thephpleague/commonmark.git",
                "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5",
                "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5",
                "shasum": ""
            },
            "require": {
                "ext-mbstring": "*",
                "league/config": "^1.1.1",
                "php": "^7.4 || ^8.0",
                "psr/event-dispatcher": "^1.0",
                "symfony/deprecation-contracts": "^2.1 || ^3.0",
                "symfony/polyfill-php80": "^1.16"
            },
            "require-dev": {
                "cebe/markdown": "^1.0",
                "commonmark/cmark": "0.30.0",
                "commonmark/commonmark.js": "0.30.0",
                "composer/package-versions-deprecated": "^1.8",
                "embed/embed": "^4.4",
                "erusev/parsedown": "^1.0",
                "ext-json": "*",
                "github/gfm": "0.29.0",
                "michelf/php-markdown": "^1.4 || ^2.0",
                "nyholm/psr7": "^1.5",
                "phpstan/phpstan": "^1.8.2",
                "phpunit/phpunit": "^9.5.21",
                "scrutinizer/ocular": "^1.8.1",
                "symfony/finder": "^5.3 | ^6.0",
                "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
                "unleashedtech/php-coding-standard": "^3.1.1",
                "vimeo/psalm": "^4.24.0 || ^5.0.0"
            },
            "suggest": {
                "symfony/yaml": "v2.3+ required if using the Front Matter extension"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-main": "2.5-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "League\\CommonMark\\": "src"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Colin O'Dell",
                    "email": "colinodell@gmail.com",
                    "homepage": "https://www.colinodell.com",
                    "role": "Lead Developer"
                }
            ],
            "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
            "homepage": "https://commonmark.thephpleague.com",
            "keywords": [
                "commonmark",
                "flavored",
                "gfm",
                "github",
                "github-flavored",
                "markdown",
                "md",
                "parser"
            ],
            "support": {
                "docs": "https://commonmark.thephpleague.com/",
                "forum": "https://github.com/thephpleague/commonmark/discussions",
                "issues": "https://github.com/thephpleague/commonmark/issues",
                "rss": "https://github.com/thephpleague/commonmark/releases.atom",
                "source": "https://github.com/thephpleague/commonmark"
            },
            "funding": [
                {
                    "url": "https://www.colinodell.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://www.paypal.me/colinpodell/10.00",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/colinodell",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/league/commonmark",
                    "type": "tidelift"
                }
            ],
            "time": "2023-08-30T16:55:00+00:00"
        },
        {
            "name": "league/config",
            "version": "v1.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/thephpleague/config.git",
                "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
                "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
                "shasum": ""
            },
            "require": {
                "dflydev/dot-access-data": "^3.0.1",
                "nette/schema": "^1.2",
                "php": "^7.4 || ^8.0"
            },
            "require-dev": {
                "phpstan/phpstan": "^1.8.2",
                "phpunit/phpunit": "^9.5.5",
                "scrutinizer/ocular": "^1.8.1",
                "unleashedtech/php-coding-standard": "^3.1",
                "vimeo/psalm": "^4.7.3"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-main": "1.2-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "League\\Config\\": "src"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Colin O'Dell",
                    "email": "colinodell@gmail.com",
                    "homepage": "https://www.colinodell.com",
                    "role": "Lead Developer"
                }
            ],
            "description": "Define configuration arrays with strict schemas and access values with dot notation",
            "homepage": "https://config.thephpleague.com",
            "keywords": [
                "array",
                "config",
                "configuration",
                "dot",
                "dot-access",
                "nested",
                "schema"
            ],
            "support": {
                "docs": "https://config.thephpleague.com/",
                "issues": "https://github.com/thephpleague/config/issues",
                "rss": "https://github.com/thephpleague/config/releases.atom",
                "source": "https://github.com/thephpleague/config"
            },
            "funding": [
                {
                    "url": "https://www.colinodell.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://www.paypal.me/colinpodell/10.00",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/colinodell",
                    "type": "github"
                }
            ],
            "time": "2022-12-11T20:36:23+00:00"
        },
        {
            "name": "nette/schema",
            "version": "v1.2.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/schema.git",
                "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
                "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
                "shasum": ""
            },
            "require": {
                "nette/utils": "^2.5.7 || ^3.1.5 ||  ^4.0",
                "php": "7.1 - 8.3"
            },
            "require-dev": {
                "nette/tester": "^2.3 || ^2.4",
                "phpstan/phpstan-nette": "^1.0",
                "tracy/tracy": "^2.7"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.2-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0-only",
                "GPL-3.0-only"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "📐 Nette Schema: validating data structures against a given Schema.",
            "homepage": "https://nette.org",
            "keywords": [
                "config",
                "nette"
            ],
            "support": {
                "issues": "https://github.com/nette/schema/issues",
                "source": "https://github.com/nette/schema/tree/v1.2.4"
            },
            "time": "2023-08-05T18:56:25+00:00"
        },
        {
            "name": "nette/utils",
            "version": "v3.2.10",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/utils.git",
                "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2",
                "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2",
                "shasum": ""
            },
            "require": {
                "php": ">=7.2 <8.4"
            },
            "conflict": {
                "nette/di": "<3.0.6"
            },
            "require-dev": {
                "jetbrains/phpstorm-attributes": "dev-master",
                "nette/tester": "~2.0",
                "phpstan/phpstan": "^1.0",
                "tracy/tracy": "^2.3"
            },
            "suggest": {
                "ext-gd": "to use Image",
                "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
                "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
                "ext-json": "to use Nette\\Utils\\Json",
                "ext-mbstring": "to use Strings::lower() etc...",
                "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
                "ext-xml": "to use Strings::length() etc. when mbstring is not available"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "3.2-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0-only",
                "GPL-3.0-only"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
            "homepage": "https://nette.org",
            "keywords": [
                "array",
                "core",
                "datetime",
                "images",
                "json",
                "nette",
                "paginator",
                "password",
                "slugify",
                "string",
                "unicode",
                "utf-8",
                "utility",
                "validation"
            ],
            "support": {
                "issues": "https://github.com/nette/utils/issues",
                "source": "https://github.com/nette/utils/tree/v3.2.10"
            },
            "time": "2023-07-30T15:38:18+00:00"
        },
        {
            "name": "psr/event-dispatcher",
            "version": "1.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/event-dispatcher.git",
                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
                "shasum": ""
            },
            "require": {
                "php": ">=7.2.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Psr\\EventDispatcher\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "PHP-FIG",
                    "homepage": "http://www.php-fig.org/"
                }
            ],
            "description": "Standard interfaces for event handling.",
            "keywords": [
                "events",
                "psr",
                "psr-14"
            ],
            "support": {
                "issues": "https://github.com/php-fig/event-dispatcher/issues",
                "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
            },
            "time": "2019-01-08T18:20:26+00:00"
        },
<?php

/**
 * This file is part of the Froxlor project.
 * Copyright (c) 2010 the Froxlor Team (see authors).
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can also view it online at
 * https://files.froxlor.org/misc/COPYING.txt
 *
 * @copyright  the authors
 * @author     Froxlor team <team@froxlor.org>
 * @license    https://files.froxlor.org/misc/COPYING.txt GPLv2
 */

namespace Froxlor\System;

use League\CommonMark\Exception\CommonMarkException;
use League\CommonMark\GithubFlavoredMarkdownConverter;

class Markdown
{

	private static $converter = null;

	public static function converter(): ?GithubFlavoredMarkdownConverter
	{
		if (is_null(self::$converter)) {
			self::$converter = new GithubFlavoredMarkdownConverter([
				'html_input' => 'strip',
				'allow_unsafe_links' => false,
			]);
		}
		return self::$converter;
	}

	public static function cleanCustomNotes(string $note = ""): string
	{
		if (!empty($note)) {
			try {
				$note = self::converter()->convert($note)->getContent();
			} catch (CommonMarkException $e) {
				$note = "";
			}
		}
		return $note;
	}
}
use Froxlor\System\Markdown;
	public static function isLocked(array $attributes): bool
	public static function hasNote(array $attributes): bool
		$cleanNote = Markdown::cleanCustomNotes($attributes['fields']['custom_notes'] ?? "");
		return !empty($cleanNote);
use Froxlor\System\Markdown;
			'body' => nl2br(Markdown::cleanCustomNotes($note))
use Froxlor\System\Markdown;
			new TwigFilter('markdown', [
				'callMarkdown'
	public function callMarkdown($string): string
		return Markdown::cleanCustomNotes($string ?? "");
			'description' => 'Hier können Notizen je nach Lust und Laune eingetragen werden. Diese werden in der Administrator/Kunden-Übersicht bei dem jeweiligen Benutzer angezeigt.<br>Markdown ist unterstützt, HTML wird entfernt.',
			'description' => 'Feel free to put any notes you want/need in here. They will show up in the admin/customer overview for the corresponding user.<br>Markdown is supported, HTML will be removed.',
				{% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
									{{ userinfo.custom_notes|markdown|raw }}
						{% if userinfo.custom_notes|markdown is not empty and userinfo.custom_notes_show == 1 %}
									{{ userinfo.custom_notes|markdown|raw }}