CVE-2022-4591: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in mschaef toto up to 1.4.20. It has been declared as problematic. This vulnerability affects unknown code of the component Email Parameter Handler. The manipulation leads to cross site scripting. The attack can be initiated remotely. Upgrading to version 1.4.21 is able to address this issue. The name of the patch is 1f27f37c1a06f54a76971f70eaa6139dc139bdf9. It is recommended to upgrade the affected component. VDB-216178 is the identifier assigned to this vulnerability.
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-17T19:15Z
Repository: https://github.com/mschaef/toto
Language: Other
Files changed: src/toto/core/mail.clj, src/toto/core/util.clj, src/toto/site/user.clj, src/toto/view/auth.clj
Commit date: 2022-10-25 06:10:22 -0400
Commit message: Ensure e-mail parameters are escaped to avoid XSS attacks.

Diff: diff --git a/src/toto/core/mail.clj b/src/toto/core/mail.clj
index 09cac67..3dfdf2f 100644
--- a/src/toto/core/mail.clj
+++ b/src/toto/core/mail.clj
@@ -17,33 +17,41 @@
 ;; See the License for the specific language governing permissions and
 ;; limitations under the License.
 ;;
 ;; You must not remove this notice, or any other, from this software.
 
-
 (ns toto.core.mail
-  (:use hiccup.core)
+  (:use toto.core.util)
   (:require [clojure.tools.logging :as log]
-            [postal.core :as postal]))
+            [postal.core :as postal]
+            [hiccup.core :as hiccup]
+            [hiccup.util :as hiccup-util]))
+
+(defn- escape-email-params [ params ]
+  (map-values #(if (string? %)
+                 (hiccup-util/escape-html %)
+                 "")
+              params))
 
 (defn send-email [config message-info]
   (let [smtp (:smtp config)
-        {to :to subject :subject content :content params :params} message-info
-        html-content (html [:html (if (fn? content)
-                                    (content (merge config (or params {})))
-                                    content)])]
+        {:keys [ to subject content params ]} message-info
+        html-content (hiccup/html
+                      [:html
+                       (content (escape-email-params
+                                 (merge {:base-url (:base-url config)}
+                                        (or params {}))))])]
+
     (log/info "Sending mail to " to " with subject: " subject)
     (cond
       (not (:enabled smtp))
-      (do
-        (log/warn "E-mail disabled. Message not sent. Message text: ")
-        (log/warn html-content))
+      (log/warn "E-mail disabled. Message not sent. Message text: "
+                html-content)
 
       (or (nil? to) (= (count to) 0))
-      (do
-        (log/warn "No destination e-mail address. Message not send. Message text: ")
-        (log/warn html-content))
+      (log/warn "No destination e-mail address. Message not send. Message text: "
+                html-content)
 
       :else
       (postal/send-message {:host (:host smtp)
                             :user (:user smtp)
                             :pass (:password smtp)
diff --git a/src/toto/core/util.clj b/src/toto/core/util.clj
index 6453a21..1137fb0 100644
--- a/src/toto/core/util.clj
+++ b/src/toto/core/util.clj
@@ -153,5 +153,9 @@
   `(when (not ~condition)
      ~@body))
 
 (defmacro with-thread-name [ thread-name & body ]
   `(call-with-thread-name (fn [] ~@body) ~thread-name))
+
+(defn map-values [f m]
+  (->> (map (fn [[k v]] [k (f v)]) m)
+       (into {})))
diff --git a/src/toto/site/user.clj b/src/toto/site/user.clj
index 403eba9..8647af5 100644
--- a/src/toto/site/user.clj
+++ b/src/toto/site/user.clj
@@ -176,11 +176,11 @@
 (defn- verification-link-email-message [ params ]
   [:body
    [:h1
     "Verification E-mail"]
    [:p
-    "Thank you for registering with " [:a {:href (:base-url (:config params))} "Toto"]
+    "Thank you for registering with " [:a {:href (:base-url params)} "Toto"]
     ", the family to-do list manager. You can verify your e-mail address by clicking"
     [:a {:href (:verify-link-url params)} " here"] "."]
    [:p
     "If this isn't something you've requested, you can safely ignore this"
     " e-mail, and we won't send anything else."]])
@@ -198,11 +198,11 @@
   [:body
    [:h1
     "Unlock Password"]
    [:p
     "Click " [:a {:href (:verify-link-url params)} "here"] " to unlock your "
-    "account at " [:a {:href (:base-url (:config params))} "Toto"] ", the family "
+    "account at " [:a {:href (:base-url params)} "Toto"] ", the family "
     "to-do list manager."]])
 
 (defn- send-unlock-link [ config user-id ]
   (let [user (data/get-user-by-id user-id)
         link-url (get-verification-link-by-user-id config "unlock" user-id)]
@@ -216,11 +216,11 @@
   [:body
    [:h1
     "Reset Password"]
    [:p
     "Click " [:a {:href (:verify-link-url params)} "here"]
-    " to reset your password at " [:a {:href (:base-url (:config params))} "Toto"]
+    " to reset your password at " [:a {:href (:base-url params)} "Toto"]
     ", the family to-do list manager."]])
 
 (defn- send-reset-link [ config user-id ]
   (let [user (data/get-user-by-id user-id)
         link-url (get-verification-link-by-user-id config "reset" user-id)]
diff --git a/src/toto/view/auth.clj b/src/toto/view/auth.clj
index 0b0e369..78b71ef 100644
--- a/src/toto/view/auth.clj
+++ b/src/toto/view/auth.clj
@@ -100,30 +100,29 @@
     nil))
 
 (defn current-roles []
   (:roles (friend/current-authentication)))
 
-
-(defn password-change-message [ config ]
-  (let [from-mail (get-in config [:smtp :from])]
+(defn password-change-message [ params ]
+  (let [ { :keys [ from-mail ]} params ]
     [:body
      [:h1
       "Password Changed"]
      [:p
       "This mail confirms that you have changed the password for your "
-      "account at " [:a {:href (:base-url config)} "Toto"]
+      "account at " [:a {:href (:base-url params)} "Toto"]
       ", the family to-do list manager."]
      [:p
       "If this isn't something you've requested, please contact us"
       " immediately at " [:a {:href (str "mailto:" from-mail)} from-mail] "."]]))
 
 (defn send-password-change-message [ config username ]
   (mail/send-email config
                    {:to [ username ]
                     :subject "Todo - Password Changed"
                     :content password-change-message
-                    :params config}))
+                    :params { :from-mail (get-in config [:smtp :from]) }}))
 
 (defn set-user-password [ config username password ]
   (log/info "Changing password for user:" username)
   (data/set-user-password username (credentials/hash-bcrypt password))
   (send-password-change-message config username))

Vulnerable code: 
  (:use hiccup.core)
            [postal.core :as postal]))
        {to :to subject :subject content :content params :params} message-info
        html-content (html [:html (if (fn? content)
                                    (content (merge config (or params {})))
                                    content)])]
      (do
        (log/warn "E-mail disabled. Message not sent. Message text: ")
        (log/warn html-content))
      (do
        (log/warn "No destination e-mail address. Message not send. Message text: ")
        (log/warn html-content))
    "Thank you for registering with " [:a {:href (:base-url (:config params))} "Toto"]
    "account at " [:a {:href (:base-url (:config params))} "Toto"] ", the family "
    " to reset your password at " [:a {:href (:base-url (:config params))} "Toto"]

(defn password-change-message [ config ]
  (let [from-mail (get-in config [:smtp :from])]
      "account at " [:a {:href (:base-url config)} "Toto"]
                    :params config}))
Fixed code:   (:use toto.core.util)
            [postal.core :as postal]
            [hiccup.core :as hiccup]
            [hiccup.util :as hiccup-util]))

(defn- escape-email-params [ params ]
  (map-values #(if (string? %)
                 (hiccup-util/escape-html %)
                 "")
              params))
        {:keys [ to subject content params ]} message-info
        html-content (hiccup/html
                      [:html
                       (content (escape-email-params
                                 (merge {:base-url (:base-url config)}
                                        (or params {}))))])]

      (log/warn "E-mail disabled. Message not sent. Message text: "
                html-content)
      (log/warn "No destination e-mail address. Message not send. Message text: "
                html-content)

(defn map-values [f m]
  (->> (map (fn [[k v]] [k (f v)]) m)
       (into {})))
    "Thank you for registering with " [:a {:href (:base-url params)} "Toto"]
    "account at " [:a {:href (:base-url params)} "Toto"] ", the family "
    " to reset your password at " [:a {:href (:base-url params)} "Toto"]
(defn password-change-message [ params ]
  (let [ { :keys [ from-mail ]} params ]
      "account at " [:a {:href (:base-url params)} "Toto"]
                    :params { :from-mail (get-in config [:smtp :from]) }}))