Tutor LMS Vulnerability: How esc_sql Corrupted Serialized Data
By WP Vanguard Team
Tutor LMS shipped version 4.0.8 on September 10, 2026. The Tutor LMS vulnerability that update fixed, tracked as CVE-2026-78175, is a PHP object injection bug reachable by any logged-in subscriber, and it traces back to something unusual: a WordPress escaping function built to make SQL queries safer ended up corrupting serialized data on its way back out of the database. Tutor LMS is a WordPress LMS plugin from Themeum, installed on more than 100,000 sites according to the WordPress.org plugin directory. Wordfence, which assigned the CVE, and the National Vulnerability Database both score it 8.8 out of 10, CVSS High severity, filed under CWE-502, deserialization of untrusted data.
What's Affected, and Who Can Reach It
Every Tutor LMS version through 4.0.7 carries the bug. The vulnerable code sits in the tutor_save_withdraw_account AJAX handler, part of the plugin's instructor withdrawal and payout feature, and it processes a field called withdraw_method_field. Per the CVE record, the handler checks a nonce but never checks the user's role or capability. Nonces confirm a request came from your own site's forms; they don't confirm the user submitting it should be allowed to. That gap means any account logged in at subscriber level, the default role WordPress hands new registrants, can reach the handler.
The withdrawal form only exists because Tutor LMS's monetization feature is on, the system that lets instructors connect a payout method to collect course revenue. Sites that never enabled monetization don't load this code path at all. Sites that did, and that also allow public registration, are left with no meaningful authentication barrier: an attacker registers an account and starts exploiting in the same session, no waiting for an admin to approve anything.
How esc_sql Corrupted Serialized Data
The mechanism traces back to a real WordPress core safeguard, not a mistake unique to Tutor LMS. WordPress's esc_sql() replaces every literal % character in a value with a 66-byte placeholder token before that value goes anywhere near a SQL query. That exists for a reason: a stray % in escaped data can otherwise get misread as a printf-style placeholder once it reaches $wpdb->prepare(), and mishandling of that exact character has caused SQL injection bugs before.
Tutor LMS's withdraw handler runs the attacker-supplied withdraw_method_field value through esc_sql(), then serializes the result and stores it with update_user_meta(). At the moment of serialization, everything is consistent. The placeholder token is part of the string, and PHP's serialize() writes a length prefix that counts every one of its bytes correctly. According to the CVE record, the problem appears later: when that stored meta value gets read back out, WordPress collapses each 66-byte placeholder token down to a single % character again. That shrinks the string by 65 bytes for every placeholder it holds, but nothing goes back and updates the length prefix serialize() already committed to disk.
Serialized PHP strings carry their own byte count, and unserialize() trusts that number over the bytes that actually follow it. After the collapse, every declared length in the stored value is 65 bytes too long for each % it held, so unserialize() keeps reading past the real end of the string and swallows the serialized data that comes next.
That next data is where the attacker gets control. The CVE record notes the array keys in this value come straight from POST field names, and those never went through any escaping. An attacker who lines up the over-read so it ends inside a key they wrote can make unserialize() treat the rest of that key as serialized structure. That structure can describe an object, so unserialize() builds one of whatever class the attacker named instead of returning a string.
From a Corrupted Length to Remote Code Execution
Object injection by itself doesn't run code. It hands unserialize() an object of a class the attacker picked, and PHP calls that class's magic methods, like __destruct(), automatically once the object exists. Our earlier look at PHP object injection across WordPress plugins covers why that automatic call is what turns a type-confusion bug into something an attacker can actually use. GiveWP's separate object injection flaw, CVE-2026-82222, reached code execution through a gadget chain in that plugin's bundled PDF library. Tutor LMS's bug follows the same general shape, with a different bundled library supplying the gadget.
Per the CVE description, Tutor LMS's RestAPI class registers a spl_autoload_register loader that pulls in the plugin's own bundled PayPal Composer autoloader. That loader gives an attacker's injected object a path to GuzzleHttp\Cookie\FileCookieJar, a class from the Guzzle HTTP library that many WordPress plugins bundle for outbound API calls, and that Tutor LMS uses for its PayPal integration. FileCookieJar writes cookie data to a file on disk. Chained through the corrupted deserialization, it writes attacker-controlled content to a filename the attacker also controls. A file write an attacker fully controls, on a server already running PHP, is remote code execution.
Disclosure Timeline and Exploitation Status
Wordfence researcher Chloe Chamberland is credited with the find, and Wordfence's published timeline shows the vendor was notified on August 23, 2026. Tutor LMS's fix shipped in version 4.0.8 on September 10. The public advisory followed on September 11, and NVD's entry went live the next day, September 12. Patch first, disclosure second, the sequence gives site owners a head start on updating before the technical details are public.
We found no evidence that CVE-2026-78175 appears in CISA's Known Exploited Vulnerabilities catalog or is being reported as under active attack as of this writing. That reflects the days immediately after disclosure, not a permanent guarantee. Our breakdown of how WordPress sites actually get hacked covers how quickly published technical detail tends to turn into automated scanning once a patch and an advisory both go public.
How to Check Your Site
Start with the installed version:
wp plugin get tutor --field=version
Anything at 4.0.7 or earlier is unpatched.
Next, check whether your site has the condition that turns this into an unauthenticated bug in practice:
wp option get users_can_register
A result of 1 means anyone can create an account and reach the vulnerable subscriber-level code path directly. In wp-admin, check Tutor LMS's settings for whether Monetization and a payment method like PayPal are active. That's what loads the withdrawal code this bug lives in; sites without it never run the vulnerable path at all.
If registration has been open for a while, look at who actually used it:
wp user list --role=subscriber --fields=ID,user_login,user_registered --orderby=user_registered --order=DESC
A cluster of unfamiliar subscriber accounts, especially ones registered close together around or after September 11, is worth investigating on its own, separate from this specific CVE.
What to Do Right Now
Update first:
wp plugin update tutor
If you can't update immediately and monetization is active, closing public registration removes the easiest path in without disabling the feature entirely, though it doesn't remove the vulnerable code path for subscriber accounts that already exist.
If your site ran an unpatched version with open registration and monetization enabled, don't treat updating as the end of it. Object injection that reaches a controlled file write is a full server compromise, not a WordPress-only problem. Check for files your deployment didn't create and review any subscriber accounts you don't recognize. Our checklist for signs a WordPress site has been hacked and our PHP backdoor field guide both cover what a file dropped this way tends to look like once it lands.
This bug is also a reminder that escaped and safe aren't the same property. esc_sql() did exactly what it's designed to do: it made a value safe to drop into a SQL query. Nobody designed it to run before serialize(), and that's where the guarantee broke down. Data prepared for one context doesn't automatically stay safe once code hands it to a different one.
Run a scan on your site to check your Tutor LMS version against our vulnerability database.
References
Related reading
Check Your WordPress Site Security
Free scan, no login required. Find vulnerabilities before attackers do.
Scan Your Site FreeGet weekly WordPress security tips
Vulnerability alerts, plugin updates, and security guides. No spam. Unsubscribe any time.