VYPR
High severityNVD Advisory· Published Mar 13, 2023· Updated Feb 27, 2025

Code Injection in alextselegidis/easyappointments

CVE-2023-1367

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.

PackageAffected versionsPatched versions
alextselegidis/easyappointmentsPackagist
< 1.5.01.5.0

Affected products

2

Patches

1
453c6e130229

Apply the new escape helper function to the view files

https://github.com/alextselegidis/easyappointmentsAlex TselegidisMar 13, 2023via ghsa
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&amp;rec=1" style="border:0;" alt=""/></p>
    +        <p><img src="<?= e($matomo_analytics_url) ?>matomo.php?idsite=1&amp;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

4

News mentions

0

No linked articles in our index yet.