CVE-2023-36463: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Meldekarten generator is an open source project to create a program, running locally in the browser without the need for an internet-connection, to create, store and print registration cards for volunteers. All text fields on the webpage are vulnerable to XSS attacks. The user input isn't (fully) sanitized after submission. This issue has been addressed in commit `77e04f4af` which is included in the `1.0.0b1.1.2` release. Users are advised to upgrade. There are no known workarounds for 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: 2023-06-27T20:15Z
Repository: https://github.com/jucktnich/meldekarten-generator
Language: JavaScript
Files changed: app.js
Commit date: 2023-06-27 18:39:45 +0200
Commit message: Fix XSS (no working exploit found)

This is adds some checks in form of .replace function calls on some user input. I was not able to exploit the issue in a security-relevant way, i only was able to render the site completly unresponsive.

Tried exploits:
" classs="first-name" type="text" placeholder="Vorname"><script>console.log('Hello World')</script><input value="
If you click on the "Neue Person" button, the script tag gets in the HTML, the code inside of it gets never executed, so you only can only break the site, without the possibility to execute any code.

<script>console.log('Hello World')</script>
If you print the document, the field is empty, but no code gets executed

;
If you export it to a CSV-file you can add an arbitrary number of fields.

\n
Did nothing, was included in the CSV as \n and did not cause a new line.
Diff: diff --git a/app.js b/app.js
index d607308..794bcc3 100644
--- a/app.js
+++ b/app.js
@@ -88,11 +88,11 @@ function doExportString() {
         arrayForEntry[11] = entry.place;
         arrayForEntry[12] = entry.unit;
         arrayForEntry[13] = dateToStandard(entry.start, true);
         let entryString = "";
         for (let j = 0; j < entryLength - 1; j++) {
-            if (arrayForEntry[j] != undefined) entryString += arrayForEntry[j] + ";";
+            if (arrayForEntry[j] != undefined) entryString += arrayForEntry[j].replace(/;/g, ",") + ";";
             else entryString += ";";
         }
         exportArray.push(entryString);
     }
     let newExportString = "";
@@ -198,35 +198,35 @@ function render() {
         }
         console.log
         html += `<div class="entry"><form>
         <div style="display: flex; justify-content: space-between;">
             <span>
-                <input value="${entry.name}" class="name" type="text" placeholder="Nachname">
-                <input value="${entry.firstName}" class="first-name" type="text" placeholder="Vorname">
+                <input value="${entry.name.replace(/"/g, "&quot;")}" class="name" type="text" placeholder="Nachname">
+                <input value="${entry.firstName.replace(/"/g, "&quot;")}" class="first-name" type="text" placeholder="Vorname">
             </span>
             <button type="button" onclick="openDeleteEntry(${i})" class="input-style not-printable">🗑️</button>
         </div>
-        <input value="${entry.birthdate}" class="birthdate" type="${birthdateType}" max="${new Date().toISOString().split('T')[0]}" placeholder="Geburtsdatum"
+        <input value="${entry.birthdate.replace(/"/g, "&quot;")}" class="birthdate" type="${birthdateType}" max="${new Date().toISOString().split('T')[0]}" placeholder="Geburtsdatum"
             onfocus="(this.type='date')" onblur="if(this.value==''){this.type='text'}">
         <fieldset id="fs-${i}" class="input-style" style="font-size: 21px;" aria-label="Geschlecht">
-            <label aria-label="männlich"> <input value="m" type="radio" name="sex" ${maleChecked}>♂</label>
-            <label aria-label="weiblich"> <input value="f" type="radio" name="sex" ${femaleChecked}>♀</label>
-            <label aria-label="divers"> <input value="d" type="radio" name="sex" ${diverseChecked}>⚧</label>
+            <label aria-label="männlich"> <input value="m" type="radio" name="sex" ${maleChecked.replace(/"/g, "&quot;")}>♂</label>
+            <label aria-label="weiblich"> <input value="f" type="radio" name="sex" ${femaleChecked.replace(/"/g, "&quot;")}>♀</label>
+            <label aria-label="divers"> <input value="d" type="radio" name="sex" ${diverseChecked.replace(/"/g, "&quot;")}>⚧</label>
         </fieldset>
-        <input value="${entry.nationality}" class="nationality" type="text" placeholder="Nationalität">
+        <input value="${entry.nationality.replace(/"/g, "&quot;")}" class="nationality" type="text" placeholder="Nationalität">
         <br>
-        <input value="${entry.street}" class="street" type="text" placeholder="Straße">
-        <input value="${entry.zip}" class="zip" type="text" placeholder="PLZ" pattern="[0-9]{5}" inputmode="numeric">
-        <input value="${entry.residency}" class="residency" type="text" placeholder="Wohnort">
+        <input value="${entry.street.replace(/"/g, "&quot;")}" class="street" type="text" placeholder="Straße">
+        <input value="${entry.zip.replace(/"/g, "&quot;")}" class="zip" type="text" placeholder="PLZ" pattern="[0-9]{5}" inputmode="numeric">
+        <input value="${entry.residency.replace(/"/g, "&quot;")}" class="residency" type="text" placeholder="Wohnort">
         <br>
-        <input value="${entry.identityDisk}" aria-label="Nummer der Erkennungsmarke" class="identity-disk" type="text" placeholder="Nr. der Erk.-Marke">
-        <input value="${entry.district}" class="district" type="text" placeholder="Kreisverband">
-        <input value="${entry.rcUnit}" class="rc-unit" type="text" placeholder="Gemeinschaft">
+        <input value="${entry.identityDisk.replace(/"/g, "&quot;")}" aria-label="Nummer der Erkennungsmarke" class="identity-disk" type="text" placeholder="Nr. der Erk.-Marke">
+        <input value="${entry.district.replace(/"/g, "&quot;")}" class="district" type="text" placeholder="Kreisverband">
+        <input value="${entry.rcUnit.replace(/"/g, "&quot;")}" class="rc-unit" type="text" placeholder="Gemeinschaft">
         <br>
-        <input value="${entry.place}" class="place" type="text" placeholder="Einsatzort">
-        <input value="${entry.unit}" class="unit" type="text" placeholder="Einsatzformation">
-        <input value="${entry.start}" class="start" type="${startType}" placeholder="Einsatzbeginn"
+        <input value="${entry.place.replace(/"/g, "&quot;")}" class="place" type="text" placeholder="Einsatzort">
+        <input value="${entry.unit.replace(/"/g, "&quot;")}" class="unit" type="text" placeholder="Einsatzformation">
+        <input value="${entry.start.replace(/"/g, "&quot;")}" class="start" type="${startType.replace(/"/g, "&quot;")}" placeholder="Einsatzbeginn"
             onfocus="(this.type='datetime-local')" onblur="if(this.value==''){this.type='text'}">
     </form></div>`
     }
     document.getElementById("entry-list").innerHTML = html
 }
@@ -237,23 +237,23 @@ function printCard() {
     for (let i = 0; i < entries.length; i++) {
         let entry = entries[i]
         entry.residency = entry.zip + " " + entry.residency
         if (!entry.sex) entry.sex = "uk"
         html += `<div>
-            <p class="name">${entry.name}</p>
+            <p class="name">${entry.name.replace(/</g, "&lt;")}</p>
             <p class="birthdate">${dateToStandard(entry.birthdate)}</p>
-            <p class="residency">${entry.residency}</p>
-            <p class="street">${entry.street}</p>
-            <p class="district">${entry.district}</p>
-            <p class="place">${entry.place}</p>
+            <p class="residency">${entry.residency.replace(/</g, "&lt;")}</p>
+            <p class="street">${entry.street.replace(/</g, "&lt;")}</p>
+            <p class="district">${entry.district.replace(/</g, "&lt;")}</p>
+            <p class="place">${entry.place.replace(/</g, "&lt;")}</p>
             <p class="start">${dateToStandard(entry.start, true)}</p>
-            <p class="first-name">${entry.firstName}</p>
-            <p class="${entry.sex}">&times;</p>
-            <p class="nationality">${entry.nationality}</p>
-            <p class="identity-disk">${entry.identityDisk}</p>
-            <p class="rc-unit">${entry.rcUnit}</p>
-            <p class="unit">${entry.unit}</p>
+            <p class="first-name">${entry.firstName.replace(/</g, "&lt;")}</p>
+            <p class="${entry.sex.replace(/</g, "&lt;")}">&times;</p>
+            <p class="nationality">${entry.nationality.replace(/</g, "&lt;")}</p>
+            <p class="identity-disk">${entry.identityDisk.replace(/</g, "&lt;")}</p>
+            <p class="rc-unit">${entry.rcUnit.replace(/</g, "&lt;")}</p>
+            <p class="unit">${entry.unit.replace(/</g, "&lt;")}</p>
         </div>`
     }
     document.getElementById("print-area").innerHTML = html
     print()
 }

Vulnerable code:             if (arrayForEntry[j] != undefined) entryString += arrayForEntry[j] + ";";
                <input value="${entry.name}" class="name" type="text" placeholder="Nachname">
                <input value="${entry.firstName}" class="first-name" type="text" placeholder="Vorname">
        <input value="${entry.birthdate}" class="birthdate" type="${birthdateType}" max="${new Date().toISOString().split('T')[0]}" placeholder="Geburtsdatum"
            <label aria-label="männlich"> <input value="m" type="radio" name="sex" ${maleChecked}>♂</label>
            <label aria-label="weiblich"> <input value="f" type="radio" name="sex" ${femaleChecked}>♀</label>
            <label aria-label="divers"> <input value="d" type="radio" name="sex" ${diverseChecked}>⚧</label>
        <input value="${entry.nationality}" class="nationality" type="text" placeholder="Nationalität">
        <input value="${entry.street}" class="street" type="text" placeholder="Straße">
        <input value="${entry.zip}" class="zip" type="text" placeholder="PLZ" pattern="[0-9]{5}" inputmode="numeric">
        <input value="${entry.residency}" class="residency" type="text" placeholder="Wohnort">
        <input value="${entry.identityDisk}" aria-label="Nummer der Erkennungsmarke" class="identity-disk" type="text" placeholder="Nr. der Erk.-Marke">
        <input value="${entry.district}" class="district" type="text" placeholder="Kreisverband">
        <input value="${entry.rcUnit}" class="rc-unit" type="text" placeholder="Gemeinschaft">
        <input value="${entry.place}" class="place" type="text" placeholder="Einsatzort">
        <input value="${entry.unit}" class="unit" type="text" placeholder="Einsatzformation">
        <input value="${entry.start}" class="start" type="${startType}" placeholder="Einsatzbeginn"
            <p class="name">${entry.name}</p>
            <p class="residency">${entry.residency}</p>
            <p class="street">${entry.street}</p>
            <p class="district">${entry.district}</p>
            <p class="place">${entry.place}</p>
            <p class="first-name">${entry.firstName}</p>
            <p class="${entry.sex}">&times;</p>
            <p class="nationality">${entry.nationality}</p>
            <p class="identity-disk">${entry.identityDisk}</p>
            <p class="rc-unit">${entry.rcUnit}</p>
            <p class="unit">${entry.unit}</p>
Fixed code:             if (arrayForEntry[j] != undefined) entryString += arrayForEntry[j].replace(/;/g, ",") + ";";
                <input value="${entry.name.replace(/"/g, "&quot;")}" class="name" type="text" placeholder="Nachname">
                <input value="${entry.firstName.replace(/"/g, "&quot;")}" class="first-name" type="text" placeholder="Vorname">
        <input value="${entry.birthdate.replace(/"/g, "&quot;")}" class="birthdate" type="${birthdateType}" max="${new Date().toISOString().split('T')[0]}" placeholder="Geburtsdatum"
            <label aria-label="männlich"> <input value="m" type="radio" name="sex" ${maleChecked.replace(/"/g, "&quot;")}>♂</label>
            <label aria-label="weiblich"> <input value="f" type="radio" name="sex" ${femaleChecked.replace(/"/g, "&quot;")}>♀</label>
            <label aria-label="divers"> <input value="d" type="radio" name="sex" ${diverseChecked.replace(/"/g, "&quot;")}>⚧</label>
        <input value="${entry.nationality.replace(/"/g, "&quot;")}" class="nationality" type="text" placeholder="Nationalität">
        <input value="${entry.street.replace(/"/g, "&quot;")}" class="street" type="text" placeholder="Straße">
        <input value="${entry.zip.replace(/"/g, "&quot;")}" class="zip" type="text" placeholder="PLZ" pattern="[0-9]{5}" inputmode="numeric">
        <input value="${entry.residency.replace(/"/g, "&quot;")}" class="residency" type="text" placeholder="Wohnort">
        <input value="${entry.identityDisk.replace(/"/g, "&quot;")}" aria-label="Nummer der Erkennungsmarke" class="identity-disk" type="text" placeholder="Nr. der Erk.-Marke">
        <input value="${entry.district.replace(/"/g, "&quot;")}" class="district" type="text" placeholder="Kreisverband">
        <input value="${entry.rcUnit.replace(/"/g, "&quot;")}" class="rc-unit" type="text" placeholder="Gemeinschaft">
        <input value="${entry.place.replace(/"/g, "&quot;")}" class="place" type="text" placeholder="Einsatzort">
        <input value="${entry.unit.replace(/"/g, "&quot;")}" class="unit" type="text" placeholder="Einsatzformation">
        <input value="${entry.start.replace(/"/g, "&quot;")}" class="start" type="${startType.replace(/"/g, "&quot;")}" placeholder="Einsatzbeginn"
            <p class="name">${entry.name.replace(/</g, "&lt;")}</p>
            <p class="residency">${entry.residency.replace(/</g, "&lt;")}</p>
            <p class="street">${entry.street.replace(/</g, "&lt;")}</p>
            <p class="district">${entry.district.replace(/</g, "&lt;")}</p>
            <p class="place">${entry.place.replace(/</g, "&lt;")}</p>
            <p class="first-name">${entry.firstName.replace(/</g, "&lt;")}</p>
            <p class="${entry.sex.replace(/</g, "&lt;")}">&times;</p>
            <p class="nationality">${entry.nationality.replace(/</g, "&lt;")}</p>
            <p class="identity-disk">${entry.identityDisk.replace(/</g, "&lt;")}</p>
            <p class="rc-unit">${entry.rcUnit.replace(/</g, "&lt;")}</p>
            <p class="unit">${entry.unit.replace(/</g, "&lt;")}</p>