Code Injection in alextselegidis/easyappointments
Description
Code Injection in GitHub repository alextselegidis/easyappointments prior to 1.5.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A code injection vulnerability in Easy!Appointments prior to 1.5.0 allows remote attackers to execute arbitrary code via the Google Analytics code field.
Analysis
A code injection vulnerability exists in Easy!Appointments versions prior to 1.5.0. The root cause is that user-supplied input for the Google Analytics tracking code is rendered in views without proper escaping. As shown in the fix commit [3], the $google_analytics_code variable was directly interpolated into JavaScript strings using short echo tags (<?= $google_analytics_code ?>), allowing an attacker to break out of the string context and inject arbitrary PHP or JavaScript code.
Exploitation
An attacker with administrative access to the Easy!Appointments instance can exploit this by providing a malicious Google Analytics code in the application's settings. Because the input is not sanitized or escaped, the attacker can inject arbitrary code that gets executed in the context of the PHP application. The vulnerability does not require authentication beyond the admin panel, but it does require that the attacker has admin credentials or can trick an admin into saving a crafted value [1].
Impact
Successful exploitation allows an attacker to execute arbitrary code on the server, potentially leading to full compromise of the application and its data. This could include stealing sensitive information, modifying application behavior, or using the server as a pivot for further attacks. The vulnerability is classified as critical with a CVSS score that reflects the high impact on confidentiality, integrity, and availability [1].
Mitigation
The vulnerability was fixed in version 1.5.0 by applying a new e() helper function that properly escapes output in view files [3]. Users are strongly advised to upgrade to Easy!Appointments 1.5.0 or later. No other workarounds have been publicly documented. The project is maintained on GitHub [2], and the commit history shows the exact changes made to address this issue.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
alextselegidis/easyappointmentsPackagist | < 1.5.0 | 1.5.0 |
Affected products
2- alextselegidis/alextselegidis/easyappointmentsv5Range: unspecified
Patches
1453c6e130229Apply the new escape helper function to the view files
7 files changed · +39 −37
application/views/components/appointments_modal.php+7 −5 modified@@ -43,6 +43,7 @@ // Group services by category, only if there is at least one service // with a parent category. $has_category = FALSE; + foreach ($available_services as $service) { if ( ! empty($service['category_id'])) @@ -72,6 +73,7 @@ // We need the uncategorized services at the end of the list, so we will use // another iteration only for the uncategorized services. $grouped_services['uncategorized'] = []; + foreach ($available_services as $service) { if ($service['category_id'] == NULL) @@ -83,7 +85,7 @@ foreach ($grouped_services as $key => $group) { $group_label = $key !== 'uncategorized' - ? $group[0]['category_name'] + ? e($group[0]['category_name']) : 'Uncategorized'; if (count($group) > 0) @@ -93,7 +95,7 @@ foreach ($group as $service) { echo '<option value="' . $service['id'] . '">' - . $service['name'] . '</option>'; + . e($service['name']) . '</option>'; } echo '</optgroup>'; @@ -105,7 +107,7 @@ foreach ($available_services as $service) { echo '<option value="' . $service['id'] . '">' - . $service['name'] . '</option>'; + . e($service['name']) . '</option>'; } } ?> @@ -137,8 +139,8 @@ </label> <select id="appointment-status" class="form-control"> <?php foreach ($appointment_status_options as $appointment_status_option): ?> - <option value="<?= $appointment_status_option ?>"> - <?= $appointment_status_option ?> + <option value="<?= e($appointment_status_option) ?>"> + <?= e($appointment_status_option) ?> </option> <?php endforeach ?> </select>
application/views/components/backend_footer.php+1 −1 modified@@ -44,7 +44,7 @@ <div class="ms-lg-auto"> <strong id="footer-user-display-name"> - <?= lang('hello') . ', ' . $user_display_name ?>! + <?= lang('hello') . ', ' . e($user_display_name) ?>! </strong> </div> </div>
application/views/components/google_analytics_script.php+2 −2 modified@@ -10,7 +10,7 @@ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,"script","//www.google-analytics.com/analytics.js","ga"); - ga("create", "<?= $google_analytics_code ?>", "auto"); + ga("create", "<?= e($google_analytics_code) ?>", "auto"); ga("send", "pageview"); </script> <?php endif ?> @@ -21,7 +21,7 @@ window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag("js", new Date()); - gtag("config", "<?= $google_analytics_code ?>"); + gtag("config", "<?= e($google_analytics_code) ?>"); </script> <?php endif ?>
application/views/components/matomo_analytics_script.php+2 −2 modified@@ -13,7 +13,7 @@ _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); (function () { - var u = "<?= $matomo_analytics_url ?>"; + var u = "<?= e($matomo_analytics_url) ?>"; _paq.push(['setTrackerUrl', u + 'matomo.php']); _paq.push(['setSiteId', '1']); var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0]; @@ -24,7 +24,7 @@ </script> <noscript> - <p><img src="<?= $matomo_analytics_url ?>matomo.php?idsite=1&rec=1" style="border:0;" alt=""/></p> + <p><img src="<?= e($matomo_analytics_url) ?>matomo.php?idsite=1&rec=1" style="border:0;" alt=""/></p> </noscript> <?php endif ?>
application/views/emails/account_recovery_email.php+2 −2 modified@@ -16,7 +16,7 @@ <div class="email-container" style="width: 650px; border: 1px solid #eee;"> <div id="header" style="background-color: #429a82; height: 45px; padding: 10px 15px;"> <strong id="logo" style="color: white; font-size: 20px; margin-top: 10px; display: inline-block"> - <?= $settings['company_name'] ?> + <?= e($settings['company_name']) ?> </strong> </div> @@ -37,7 +37,7 @@ </a> | <a href="<?= $settings['company_link'] ?>" style="text-decoration: none;"> - <?= $settings['company_name'] ?> + <?= e($settings['company_name']) ?> </a> </div> </div>
application/views/emails/appointment_deleted_email.php+12 −12 modified@@ -21,7 +21,7 @@ <div class="email-container" style="width: 650px; border: 1px solid #eee;"> <div id="header" style="background-color: #429a82; height: 45px; padding: 10px 15px;"> <strong id="logo" style="color: white; font-size: 20px; margin-top: 10px; display: inline-block"> - <?= $settings['company_name'] ?> + <?= e($settings['company_name']) ?> </strong> </div> @@ -44,15 +44,15 @@ <?= lang('service') ?> </td> <td style="padding: 3px;"> - <?= $service['name'] ?> + <?= e($service['name']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('provider') ?> </td> <td style="padding: 3px;"> - <?= $provider['first_name'] . ' ' . $provider['last_name'] ?> + <?= e($provider['first_name'] . ' ' . $provider['last_name']) ?> </td> </tr> <tr> @@ -87,7 +87,7 @@ <?= lang('location') ?> </td> <td style="padding: 3px;"> - <?= $appointment['location'] ?> + <?= e($appointment['location']) ?> </td> </tr> <?php endif ?> @@ -98,7 +98,7 @@ <?= lang('notes') ?> </td> <td style="padding: 3px;"> - <?= $appointment['notes'] ?> + <?= e($appointment['notes']) ?> </td> </tr> <?php endif ?> @@ -114,31 +114,31 @@ <?= lang('name') ?> </td> <td style="padding: 3px;"> - <?= $customer['first_name'] . ' ' . $customer['last_name'] ?> + <?= e($customer['first_name'] . ' ' . $customer['last_name']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('email') ?> </td> <td style="padding: 3px;"> - <?= $customer['email'] ?> + <?= e($customer['email']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('phone_number') ?> </td> <td style="padding: 3px;"> - <?= $customer['phone_number'] ?> + <?= e($customer['phone_number']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('address') ?> </td> <td style="padding: 3px;"> - <?= $customer['address'] ?> + <?= e($customer['address']) ?> </td> </tr> </table> @@ -148,7 +148,7 @@ </h2> <p> - <?= $reason ?> + <?= e($reason) ?> </p> </div> @@ -159,8 +159,8 @@ Easy!Appointments </a> | - <a href="<?= $settings['company_link'] ?>" style="text-decoration: none;"> - <?= $settings['company_name'] ?> + <a href="<?= e($settings['company_link']) ?>" style="text-decoration: none;"> + <?= e($settings['company_name']) ?> </a> </div> </div>
application/views/emails/appointment_saved_email.php+13 −13 modified@@ -25,7 +25,7 @@ <div class="email-container" style="width: 650px; border: 1px solid #eee;"> <div id="header" style="background-color: #429a82; height: 45px; padding: 10px 15px;"> <strong id="logo" style="color: white; font-size: 20px; margin-top: 10px; display: inline-block"> - <?= $settings['company_name'] ?> + <?= e($settings['company_name']) ?> </strong> </div> @@ -48,15 +48,15 @@ <?= lang('service') ?> </td> <td style="padding: 3px;"> - <?= $service['name'] ?> + <?= e($service['name']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('provider') ?> </td> <td style="padding: 3px;"> - <?= $provider['first_name'] . ' ' . $provider['last_name'] ?> + <?= e($provider['first_name'] . ' ' . $provider['last_name']) ?> </td> </tr> <tr> @@ -91,7 +91,7 @@ <?= lang('location') ?> </td> <td style="padding: 3px;"> - <?= $appointment['location'] ?> + <?= e($appointment['location']) ?> </td> </tr> <?php endif ?> @@ -102,7 +102,7 @@ <?= lang('notes') ?> </td> <td style="padding: 3px;"> - <?= $appointment['notes'] ?> + <?= e($appointment['notes']) ?> </td> </tr> <?php endif ?> @@ -118,31 +118,31 @@ <?= lang('name') ?> </td> <td style="padding: 3px;"> - <?= $customer['first_name'] . ' ' . $customer['last_name'] ?> + <?= e($customer['first_name'] . ' ' . $customer['last_name']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('email') ?> </td> <td style="padding: 3px;"> - <?= $customer['email'] ?> + <?= e($customer['email']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('phone_number') ?> </td> <td style="padding: 3px;"> - <?= $customer['phone_number'] ?> + <?= e($customer['phone_number']) ?> </td> </tr> <tr> <td class="label" style="padding: 3px;font-weight: bold;"> <?= lang('address') ?> </td> <td style="padding: 3px;"> - <?= $customer['address'] ?> + <?= e($customer['address']) ?> </td> </tr> </table> @@ -151,8 +151,8 @@ <?= lang('appointment_link_title') ?> </h2> - <a href="<?= $appointment_link ?>" style="width: 600px;"> - <?= $appointment_link ?> + <a href="<?= e($appointment_link) ?>" style="width: 600px;"> + <?= e($appointment_link) ?> </a> </div> @@ -163,8 +163,8 @@ Easy!Appointments </a> | - <a href="<?= $settings['company_link'] ?>" style="text-decoration: none;"> - <?= $settings['company_name'] ?> + <a href="<?= e($settings['company_link']) ?>" style="text-decoration: none;"> + <?= e($settings['company_name']) ?> </a> </div> </div>
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.