---
title: "Tutor LMS Vulnerability: How esc_sql Corrupted Serialized Data"
slug: "tutor-lms-php-object-injection-cve-2026-78175"
description: "CVE-2026-78175 shows how Tutor LMS's own SQL-escaping call corrupted serialized data, opening a path to PHP object injection and remote code execution."
date: 2026-09-15
author: "WP Vanguard Team"
category: "Research"
tags: ["tutor-lms-vulnerability", "cve-2026-78175", "php-object-injection", "deserialization", "esc-sql"]
featured_image: "/images/blog/tutor-lms-php-object-injection-cve-2026-78175.jpg"
---

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](/blog/php-object-injection-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](/blog/givewp-php-object-injection-rce-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](/blog/how-wordpress-sites-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:

```bash
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:

```bash
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:

```bash
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:

```bash
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](/blog/signs-wordpress-site-hacked) and our [PHP backdoor field guide](/blog/php-backdoors-in-wordpress) 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](/#scanner) to check your Tutor LMS version against our vulnerability database.

## References

- [Wordfence Intelligence: CVE-2026-78175](https://www.wordfence.com/threat-intel/vulnerabilities/id/d0077d56-11e7-4e74-abe0-63e81db67be3)
- [WordPress.org Trac: Tutor LMS 4.0.7 Withdraw.php](https://plugins.trac.wordpress.org/browser/tutor/tags/4.0.7/classes/Withdraw.php)
- [CVE.org: CVE-2026-78175 record](https://cveawg.mitre.org/api/cve/CVE-2026-78175)
- [NVD: CVE-2026-78175 detail](https://nvd.nist.gov/vuln/detail/CVE-2026-78175)
- [OpenCVE: CVE-2026-78175](https://app.opencve.io/cve/CVE-2026-78175)
- [WordPress.org: Tutor LMS changelog](https://wordpress.org/plugins/tutor/#developers)
