SENAITE.CORE
Unauthenticated RCE via JSON API RecordsField Eval
The SENAITE JSON API update route omits the AccessJSONAPI permission check that its sibling create route enforces, leaving it reachable to anonymous callers on a stock Plone site. Once a target is resolved, set_fields_from_request runs eval() on the raw request value of any RecordsField or RecordField before the field mutator's permission check. A two-request anonymous chain executes arbitrary Python in the Zope worker process.
Description
Flaw 1: missing authorization on the `update` route. The @@API view is registered by plone.jsonapi.core at permission="zope2.View", which a stock Plone site grants to Anonymous. The create route guards itself with an explicit AccessJSONAPI permission check; update, update_many, remove, doActionFor, doActionFor_many, and getusers do not.
bika/lims/jsonapi/create.py:179-182 (the check that update omits)
if not getSecurityManager().checkPermission( "senaite.core: Access JSON API", parent): raise Unauthorized("You don't have permission ...")Flaw 2: the eval sink. After the route resolves the target object, set_fields_from_request iterates the request fields. For a RecordsField or RecordField, it runs eval() on the raw request string, before the field mutator and its write_permission check execute. The eval runs in the Zope worker with full Python builtins available.
bika/lims/jsonapi/__init__.py:237-245
elif fieldtype in ['senaite.core.browser.fields.records.RecordsField', 'senaite.core.browser.fields.record.RecordField']: try: value = eval(value) except Exception: logger.warning( "JSONAPI: " + fieldname + ": Invalid " "JSON/Python variable") return []The chain. The bika_setup object is anonymous-readable on a stock Plone site, exposes two RecordsField-typed fields (RejectionReasons and IDFormatting), and its UID is published by Plone's standard anonymous @@uuid view. An anonymous attacker fetches the UID at /<site>/bika_setup/@@uuid, then POSTs it to /<site>/@@API/update with a field value that reaches the eval sink. No Authorization, Cookie, or CSRF token is required.
The update route wraps the write in a transaction savepoint, so the ZODB changes roll back when the mutator later fails on the unexpected value. The Python side effects of the eval (subprocess execution, outbound network calls, file I/O outside ZODB) have already happened and are not reverted, so the failed transaction does not undo the code execution.
Impact
- Unauthenticated remote code execution: a single anonymous HTTP request executes arbitrary Python in the Zope worker process, with full read/write access to the ZODB (laboratory records, including patient data in clinical deployments), filesystem access on the container's data volume, and outbound network egress. Write access includes silently altering or deleting individual analysis results that clinicians rely on.
- Persistence: code execution gives direct access to
acl_users(the Plone PAS user folder) for creating administrator accounts, and the ZMI at/manageis exposed in the upstream Docker image. - The transaction rollback is not a mitigation: the failed mutator reverts ZODB writes, but the subprocess, network, and file-I/O side effects of the eval have already executed.
Mitigation
No patched release exists at time of publication, so both root causes must be addressed at the deployment or source level. Restrict anonymous access to the @@API view (for example, require authentication for /@@API/ at the reverse proxy) until an upstream fix ships. At the source level, add the AccessJSONAPI permission check to the top of every state-changing route in src/bika/lims/jsonapi/ (mirroring the existing check in create.py), and replace the eval() on RecordField/RecordsField values in set_fields_from_request with json.loads(): the stored shape is a JSON-compatible dict or list of dicts, so JSON parsing is sufficient and removes the code-execution primitive.
Defender's Checklist
Block anonymous access to the JSON API.
Until a patched release ships, require authentication for
/@@API/at the reverse proxy, or otherwise restrict the endpoint to trusted networks. The chain needs no credentials, so network exposure is the immediate risk.Replace eval() with json.loads() on RecordField/RecordsField values.
The same
eval()pattern also lives in the field setters atsrc/senaite/core/browser/fields/record.pyandrecords.py. Switch all three tojson.loads()with a try/except returning empty on failure.Add the AccessJSONAPI check to every state-changing route.
At minimum
update,update_many,remove,doActionFor,doActionFor_many, andgetusersare reachable to anonymous without it. Audit everyIRouteProviderinsrc/bika/lims/jsonapi/configure.zcml.Re-enable Plone's CSRF protection.
The audited release ships
ISenaiteCore(IDisableCSRFProtection), which disablesplone.protect's automatic CSRF write-detection for the SENAITE browser layer. Removing the inheritance does not affect this unauthenticated chain but closes several authenticated CSRF chains.
Severity Reasoning
@@API view is anonymous-reachable and the eval sink runs unconditionally for RecordsField/RecordField values.PR:NNo credentials are required. The chain works with no Authorization, Cookie, or CSRF token against a stock deployment.UI:NThe attacker drives both requests directly; no victim action is involved.S:UCode execution stays within the Zope worker's security authority; the vulnerable component and the impacted resources share the same scope.C:HArbitrary Python grants full read access to the ZODB, including laboratory records and patient data in clinical deployments.I:HArbitrary Python grants full write access to the ZODB and the ability to create administrator accounts via acl_users.A:HCode execution in the worker process allows denial of service against the application and its data store.References
How We Can Help
Who We Are
The security researchers behind this advisory.

Dr. rer. nat. Simon Weber
Senior Pentester & MedSec Researcher
I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.
- PhD on Hospital Cybersecurity
- Critical vulnerabilities found in hospital systems
- Alumni of THB MedSec Research Group
- gematik Security Hero

Dipl.-Inf. Volker Schönefeld
Senior Application Security Expert
As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.
- 20+ years as CTO, 50M+ app downloads
- Architected and secured large-scale IoT fleets
- Certified Web Exploitation Specialist
- gematik Security Hero
Looking for a Penetration Test?
Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.
