WP Courses LMS < 2.0.44 — Authenticated Stored XSS via 'Video Embed Code'
Introduction
While reviewing WordPress learning-management plugins, I focused on inputs that render inside lesson or course pages—especially fields intended for HTML or embeds. These areas often receive elevated trust from developers but still require strict server-side sanitization and output escaping. During this review, I looked at WP Courses LMS and examined how its “Video Embed Code (iframe)” field is processed and displayed to learners. My aim was to determine whether the plugin correctly sanitizes embed input and whether the resulting output is safely escaped when rendered on the front end. The behavior I observed indicated that crafted markup could persist to the database and execute when a visitor opens the affected course page.
Summary of the Finding
The Video Embed Code field in WP Courses LMS (versions prior to 2.0.44) did not adequately sanitize user-supplied HTML, allowing a high-privilege user to store malicious JavaScript that executes whenever the corresponding course page is viewed. This constitutes an authenticated stored XSS vulnerability. Notably, exploitation was possible even if the WordPress role lacked the unfiltered_html capability, because the plugin’s own handling failed to neutralize dangerous payloads at save or render time. The issue is documented publicly and fixed in later releases.
Reproduction steps (tested on local instance)
Performed only on a local test instance for verification and learning.
-
From the WordPress dashboard, I navigated to: WP Courses → Courses → Add New → Post settings → “Video Embed Code (iframe)”. This field accepts HTML intended to embed a video player inside a course page
-
I prepared a simple proof-of-concept using an iframe with a javascript: URL to confirm whether the input would be persisted and executed. Example payloads recorded in my notes:
1
2
<iframe src="javascript:alert(document.cookie)">
</iframe>;<iframe src="javascript:%61%6c%65%72%74%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29"></iframe>
- I saved/updated the course containing the payload, then, click Update, and to trigger XSS payload, open URL path of course.
Visiting the public (or preview) URL of the affected course caused the browser to execute the injected script, demonstrating stored XSS. On vulnerable versions, the plugin neither sanitized the payload at save time nor escaped it at render time, allowing the markup to run in the viewer’s context.
Timelines
- Publicly Published : 2021-08-16
- Added : 2021-08-16
- Last Updated : 2021-08-16