NVD has published CVE-2026-104736, a CVSS 9.8 (CRITICAL), network-exploitable vulnerability in the LearnPress plugin for WordPress, one of the most widely deployed learning management system (LMS) plugins in the ecosystem. All versions up to and including 4.4.10 are vulnerable.
The flaw is a PHP Object Injection reachable by unauthenticated attackers through the checkout AJAX workflow. The checkout-email request parameter is passed through sanitize_text_field() — a WordPress sanitization function that does not strip PHP serialized object syntax — is persisted into the LearnPress session table, and is later fed into unserialize() without an allowed_classes restriction via LP_Helper::maybe_unserialize() during LP_Session_Handler::get() on subsequent requests.
If you operate WordPress sites running LearnPress — for corporate training portals, customer education platforms, or higher-ed deployments — treat this as an urgent remediation item. Unauthenticated, network-reachable deserialization bugs are exactly the class of vulnerability that gets weaponized into automated exploitation within days of disclosure.
Technical Analysis
Affected Component and Versions
| Item | Detail |
|---|---|
| CVE | CVE-2026-104736 |
| CVSS | 9.8 (Critical) — network vector, unauthenticated |
| Product | LearnPress plugin for WordPress |
| Affected versions | ≤ 4.4.10 |
| Vulnerability class | CWE-502 — Deserialization of Untrusted Data (PHP Object Injection) |
| Authentication required | None |
Attack Chain — From a Defender's Perspective
Understanding the two-stage nature of this bug is essential, because it changes where your detection opportunities live:
-
Stage 1 — Injection (write): An attacker submits a crafted
checkout-emailvalue during the LearnPress checkout AJAX action. The input passes throughsanitize_text_field(). Critically, that function sanitizes HTML and strips octets, but it does not reject or neutralize PHP serialized object syntax such asO:8:"ClassName":.... The attacker-controlled serialized payload is written to the LearnPress session table in the WordPress database. -
Stage 2 — Deserialization (trigger): On a subsequent request,
LP_Session_Handler::get()retrieves the stored session value and passes it toLP_Helper::maybe_unserialize(), which callsunserialize()with noallowed_classesrestriction. At this point, PHP instantiates whatever class the attacker serialized. If a suitable POP (Property-Oriented Programming) gadget chain exists anywhere in the codebase — WordPress core, LearnPress itself, or any co-installed plugin or theme — magic methods such as__wakeup(),__destruct(), or__toString()fire, leading to arbitrary code execution, file writes, or data exfiltration.
Why This Matters Even Without a Known Gadget Chain
PHP Object Injection severity is often initially downplayed with "no known POP chain in the component itself." That reasoning is dangerous in WordPress environments specifically:
- The average WordPress site runs dozens of plugins and themes. Any class with exploitable magic methods loaded at deserialization time becomes a candidate gadget — and the attacker gets to fingerprint your plugin inventory trivially.
- Publicly documented gadget chains exist in numerous popular WordPress plugins and libraries (e.g., anything bundling PHPMailer, Guzzle, or legacy PSR-compatible autoloaders), and security tooling like PHPGGC has commoditized chain generation.
- The attack surface is unauthenticated and reachable over the network — the worst combination for a CMS plugin.
Exploitation Status
At the time of writing, CVE-2026-104736 is newly published through NVD with no confirmed in-the-wild exploitation or CISA KEV listing. However, WordPress plugin vulnerabilities with a 9.8 score, unauthenticated reachability, and a clearly documented injection path historically attract mass scanning and automated exploitation quickly. Do not wait for a KEV listing to act — verify your LearnPress version today.
Detection & Response
The two-stage design gives defenders two distinct detection planes: the HTTP request carrying serialized payload syntax in the checkout-email parameter, and downstream behavioral anomalies from the web server process if deserialization is triggered with a working gadget.
Sigma Rules
The first rule targets web server access logs (ingested via a Sigma-compatible pipeline that maps to the webserver category) for serialized PHP object syntax in checkout-related requests. The second targets the blast radius: web server worker processes spawning shells or script interpreters — the classic post-exploitation signature of PHP code execution.
---
title: PHP Serialized Object Syntax in WordPress Checkout Parameter
description: Detects HTTP requests to WordPress AJAX or checkout endpoints containing PHP serialized object/array syntax consistent with CVE-2026-104736 exploitation attempts against the LearnPress checkout-email parameter.
id: 3f9c2b71-8a4e-4d5c-b2a1-6e7f8d9c0a12
status: experimental
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-104736
- https://cwe.mitre.org/data/definitions/502.html
author: Security Arsenal
date: 2026/04/10
tags:
- attack.initial_access
- attack.t1190
logsource:
category: webserver
detection:
selection_endpoint:
c-uri|contains:
- 'admin-ajax.php'
- 'lp-ajax'
- 'checkout'
selection_serialized:
cs-body|contains:
- 'O:%22:'
- 'O:+'
- 'checkout-email=O'
- 'checkout-email%5D=O'
- 'a:%7B'
- 'checkout-email=a%3A'
condition: selection_endpoint and selection_serialized
falsepositives:
- Legitimate applications passing serialized data via POST bodies are rare; review any hits against known custom integrations
level: high
---
title: Web Server Worker Spawning Shell or Script Interpreter
description: Detects php-fpm, Apache, or nginx worker processes spawning shells, downloaders, or script interpreters — a post-exploitation indicator consistent with PHP Object Injection achieving code execution, such as via CVE-2026-104736.
id: 8b1d4e52-3c6f-4a9b-d7e2-1f5a9c3e7b04
status: experimental
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-104736
- https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/04/10
tags:
- attack.execution
- attack.t1059
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith:
- '/php-fpm'
- '/php-fpm8.1'
- '/php-fpm8.2'
- '/php-fpm8.3'
- '/apache2'
- '/httpd'
- '/nginx'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/curl'
- '/wget'
- '/python'
- '/python3'
- '/perl'
- '/nc'
- '/ncat'
- '/base64'
condition: selection_parent and selection_child
falsepositives:
- Backup or cron plugins invoking system commands — validate against scheduled maintenance windows
level: critical
KQL — Microsoft Sentinel Hunting
This query assumes web server logs are ingested into Sentinel via Syslog/CEF or Azure diagnostics. It hunts for serialized PHP syntax in request URIs and POST data hitting AJAX/checkout endpoints, plus correlates web-host process execution for post-exploitation behavior.
// Hunt for PHP serialized object injection attempts against LearnPress checkout (CVE-2026-104736)
let serialized_patterns = dynamic(["O:%22", "O:+", "checkout-email=O", "checkout-email=a%3A", "%22__PHP_Incomplete_Class", "allowed_classes"]);
let window_start = ago(14d);
union isfuzzy=true
(CommonSecurityLog
| where TimeGenerated > window_start
| where RequestURL has_any ("admin-ajax.php", "lp-ajax", "checkout") or AdditionalExtensions has "checkout-email"
| extend Payload = coalesce(RequestURL, AdditionalExtensions, "")
| where Payload has_any (serialized_patterns)
| project TimeGenerated, SourceIP, DestinationHostName, RequestURL, RequestMethod, AdditionalExtensions, DeviceVendor),
(Syslog
| where TimeGenerated > window_start
| where SyslogMessage has_any ("admin-ajax.php", "lp-ajax") and SyslogMessage has_any (serialized_patterns)
| project TimeGenerated, HostIP, HostName, SyslogMessage, SeverityLevel)
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), AttemptCount=count() by SourceIP, DestinationHostName
| order by AttemptCount desc;
// Correlate: web server worker spawning suspicious child processes (post-deserialization code execution)
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName has_any ("php-fpm", "apache2", "httpd", "nginx", "php-cgi")
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "nc", "ncat", "python", "python3", "perl")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine, AccountName, InitiatingProcessCommandLine
| order by TimeGenerated desc;
Velociraptor VQL — Fleet-Wide LearnPress Version Triage
The fastest way to scope exposure across a fleet of web hosts is to enumerate installed LearnPress versions. WordPress plugins declare their version in the main plugin file header and readme.txt. This artifact globs common document roots, extracts the version string, and flags anything at or below 4.4.10.
-- Artifact: Custom.Hunt.LearnPressVersionTriage
-- Scope: identify LearnPress installations vulnerable to CVE-2026-104736 (<= 4.4.10)
LET plugin_files = SELECT FullPath AS PluginFile,
read_file(filename=FullPath, length=4096) AS Header
FROM glob(globs=[
'/var/www/*/wp-content/plugins/learnpress/learnpress.php',
'/var/www/html/**/wp-content/plugins/learnpress/learnpress.php',
'/srv/www/**/wp-content/plugins/learnpress/learnpress.php',
'/home/*/public_html/wp-content/plugins/learnpress/learnpress.php',
'C:/inetpub/**/wp-content/plugins/learnpress/learnpress.php'
])
LET parsed = SELECT PluginFile,
parse_string_with_regex(string=Header,
regex='Version:\\s*([0-9]+\\.[0-9]+\\.[0-9]+)') AS VersionMatch
FROM plugin_files
SELECT PluginFile,
VersionMatch[1] AS InstalledVersion,
if(condition=VersionMatch[1] =~ '^(4\\.[0-3]\\.|4\\.4\\.([0-9]|10)$|[0-3]\\.)',
then='VULNERABLE - CVE-2026-104736',
else='Review - verify patch level') AS RiskStatus,
timestamp(epoch=now()) AS ScanTime
FROM parsed
WHERE VersionMatch
Remediation and Verification Script
Use this Bash script on Linux web hosts to locate every LearnPress installation, report its version, and flag vulnerable instances. It is read-only by design — pair it with your deployment pipeline to push the patched plugin version.
#!/usr/bin/env bash
# CVE-2026-104736 — LearnPress version triage across common WordPress document roots
# Flags any LearnPress install <= 4.4.10 as vulnerable
set -euo pipefail
DOCROOTS=("/var/www" "/srv/www" "/home" "/opt/wordpress")
VULN=0
version_lte_4410() {
# Returns 0 (true) if $1 <= 4.4.10
[ "$(printf '%s\n4.4.10\n' "$1" | sort -V | head -n1)" = "$1" ] && [ "$1" != "4.4.11" ]
}
echo "[*] Scanning for LearnPress installations..."
for root in "${DOCROOTS[@]}"; do
[ -d "$root" ] || continue
while IFS= read -r pluginfile; do
ver=$(grep -m1 -E '^[[:space:]]*\*?[[:space:]]*Version:' "$pluginfile" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
[ -z "${ver:-}" ] && ver="UNKNOWN"
site=$(echo "$pluginfile" | awk -F'/wp-content/' '{print $1}')
if [ "$ver" != "UNKNOWN" ] && version_lte_4410 "$ver"; then
echo "[VULNERABLE] $site — LearnPress $ver (CVE-2026-104736)"
VULN=$((VULN+1))
else
echo "[OK] $site — LearnPress $ver"
fi
done < <(find "$root" -type f -path '*/wp-content/plugins/learnpress/learnpress.php' 2>/dev/null)
done
# Check for evidence of serialized payload injection in access logs (last 7 days)
echo ""
echo "[*] Checking access logs for serialized object injection patterns in checkout-email..."
LOGDIRS=("/var/log/nginx" "/var/log/apache2" "/var/log/httpd")
for ld in "${LOGDIRS[@]}"; do
[ -d "$ld" ] || continue
hits=$(zgrep -hE 'checkout-email=([0-9]*O|a%3A|O%3A|O:|a:)' "$ld"/*.log* 2>/dev/null | wc -l || true)
if [ "${hits:-0}" -gt 0 ]; then
echo "[ALERT] $hits suspicious request(s) found in $ld — investigate immediately"
zgrep -hE 'checkout-email=([0-9]*O|a%3A|O%3A|O:|a:)' "$ld"/*.log* 2>/dev/null | tail -n 20
else
echo "[CLEAN] No serialized-payload patterns in $ld"
fi
done
echo ""
echo "[*] Summary: $VULN vulnerable LearnPress installation(s) found."
[ "$VULN" -gt 0 ] && exit 1 || exit 0
Remediation
-
Update LearnPress immediately. Upgrade to the latest release beyond 4.4.10. Verify the update via the WordPress admin dashboard (
Plugins → Installed Plugins) or WP-CLI:wp plugin update learnpress && wp plugin get learnpress --field=version. Confirm the version string after updating — a stalled auto-update is a common false sense of closure. -
Audit for prior exploitation before assuming you are clean. Because the payload is persisted in the LearnPress session table, check the database for injected serialized strings: query the LearnPress session table for rows whose values match
O:%ora:%patterns with class names not present in your codebase. Review access logs forcheckout-emailparameters containing serialized syntax (the script above automates this). -
Restrict unserialize gadget exposure. Where PHP configuration permits, ensure
unserialize()is not globally invoked with attacker input elsewhere. Defense-in-depth: run a current PHP version (8.1+), and consider deploying a WAF rule blocking request bodies containingO:,a:, orC:serialized prefixes in POST parameters toadmin-ajax.phpandlp-ajaxendpoints — with tuned exceptions for legitimate integrations. -
Reduce the POP chain surface. Remove unused plugins and themes — every dormant class with a magic method is a potential deserialization gadget. This is cheap, high-yield hardening that pays off against the entire class of WordPress object-injection bugs, not just this CVE.
-
Segment and monitor web workloads. Web server processes should never spawn interactive shells. Alert on it (Sigma rule #2 above). Egress filtering on web hosts limits the value of code execution for staging second payloads.
-
Track the advisory. Monitor the NVD entry at https://nvd.nist.gov/vuln/detail/CVE-2026-104736 and the LearnPress changelog/Wordfence intelligence feed for updates on exploitation activity and any KEV addition, which would trigger emergency-change remediation timelines under most compliance frameworks.
Compliance Framing
For organizations under PCI-DSS or HIPAA, an unauthenticated RCE-adjacent flaw in a public-facing application handling checkout flows sits squarely in "critical vulnerability — expedited remediation" territory (PCI-DSS Req. 6.2 / 6.3 timelines; HIPAA Security Rule risk management obligations). Document your patch date, version verification, and hunt results — auditors will ask, and IR scoping will be far cheaper if you can demonstrate the environment was patched before any exploitation window.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.