Destructive workflow CRUD ops without confirmation/audit/MFA
low
CVSS 3.1: 4.8 · Asset: admin.corezoid.com/api/2/json
- Severity: Low-Medium (operational integrity / insider-threat vector; not a direct CIA-triad exploit)
- CVSS 3.1:
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L→ 4.8 - CWE: CWE-841 (Improper Enforcement of Behavioral Workflow), CWE-778 (Insufficient Logging)
- Asset:
admin.corezoid.com/api/2/json—{"type":"delete", "obj":"conv"} - Discovered: 2026-04-26
- Status: Open — recommend adding confirmation + audit trail
Summary
During API op enumeration on /api/2/json, I confirmed
that a single POST request with an authenticated session
immediately deletes an entire workflow (conv
object) with:
- No confirmation token (no "are-you-sure" second-step)
- No CSRF token beyond the standard cookies (mw + __Host_mw)
- No MFA re-prompt for destructive actions
- No visible audit trail in the response
This was verified against tester-created workflows
(obj_id 1835953, 1835954, 1835955, 1835956 — all created
and deleted cleanly during the engagement as part of op-enumeration).
The server responds simply with {"proc":"ok"} and the
workflow is gone.
While conv_ttl: 0 in the Ansible config suggests deleted
processes are kept in trash "forever" (recoverable via the
restore op), the trash-retention doesn't mitigate:
- An attacker with session cookies who calls
deleteon all workflows → immediate operational outage - An attacker who also calls
restoreto re-activate their own modified versions - A compromised admin account that enumerates all
conv_ids and scripts mass deletion
Reproduction
# 1. Create a test workflow
curl -sk -b cookies.txt -X POST 'https://admin.corezoid.com/api/2/json' \
-H 'Content-Type: application/json' \
--data '{"ops":[{"type":"create","obj":"conv","title":"test","folder_id":0}]}'
# → {"proc":"ok","obj_id":1835953,...}
# 2. Single DELETE request — no confirmation required
curl -sk -b cookies.txt -X POST 'https://admin.corezoid.com/api/2/json' \
-H 'Content-Type: application/json' \
--data '{"ops":[{"type":"delete","obj":"conv","obj_id":1835953}]}'
# → {"proc":"ok","obj_id":1835953}
# 3. Verify gone
curl ... --data '{"ops":[{"type":"show","obj":"conv","obj_id":1835953}]}'
# → {"proc":"error","description":"bad object"} (or similar)
Ops enumeration result (context)
The same endpoint also accepts these state-changing ops with no confirmation:
| Op | Object | Effect |
|---|---|---|
create |
conv | creates new workflow (rate-limited per Erlang config) |
delete |
conv | deletes workflow (soft-delete to trash by default) |
modify |
conv | modifies workflow |
restore |
conv | restores from trash (reverses delete) |
Impact scenarios
Session hijack → business disruption. Any attacker who obtains admin session cookies (via phishing, subdomain XSS, stolen laptop) can issue a script that enumerates
list convand deletes every workflow. Trash retention means it's recoverable, but the service disruption during recovery could be hours-to-days and requires operator intervention.Insider ops risk. Developers or ops staff with admin access can accidentally delete production workflows (e.g., via a misconfigured CI script calling the API). No two-step confirmation makes fat-finger recovery harder.
Batch operations via
opsarray. The/api/2/jsonendpoint accepts an array of ops in a single request. An attacker can pack dozens of deletes in one call:{"ops":[{"type":"delete","obj":"conv","obj_id":1},{"type":"delete",...},...]}— single HTTP request, mass deletion, no rate-limit interaction.CSRF-adjacent. The cookie duplication issue (CRZ-008) means on cross-site attacks that land a single POST with valid cookies, destructive ops complete atomically. The Lax-SameSite
mwcookie alone is insufficient for /api/2/json (which requires both cookies), so the CSRF risk is lower here than on the GET endpoints — but any future bug that accepts the weaker auth would cascade into destructive capability.
Remediation
Priority 1 — confirmation token on destructive ops:
- Require a CSRF-style confirmation token obtained
via a separate GET for each destructive op (or a signed short-lived
token tied to the session + obj_id). Example:
{"ops":[{"type":"delete","obj":"conv","obj_id":1835953,"confirm_token":"<signed-token>"}]} - For web UI flows, show a confirmation modal on all
deleteactions. - For batch deletes (more than 3 ops in a single request), require step-up auth (MFA re-prompt).
Priority 2 — audit trail:
- Emit an event to a tamper-evident audit log on every state-changing op: timestamp, user_id, remote_ip, op type, obj_id, obj_title, success/fail.
- Make audit logs read-only from the application level (write-once to a separate log bucket with immutability, like S3 Object Lock).
- Add "Activity" or "Audit" page in admin UI so users can see their own account's history.
- Alert on anomalous op volume (e.g., >50 deletes in <1 min from one session).
Priority 3 — defensive rate limits:
- The Erlang config
capi.config.j2mentions rate limits on create/modify/delete for conv|folder|dashboard — verify these actually fire (my probes didn't hit them, so either the limits are generous or not active). - Per-object type: <10 deletes/minute, <100 modifies/minute by default.
- Tenant-level: no more than 10% of workflows deleted in 1 hour without an override.
Testing note
During this pentest I created 4 test workflows (IDs 1835953, 1835954, 1835955, 1835956 — all titled variants of "pentest-probe") and deleted each immediately after inspection. No residual test data was left in production. The engagement produced this finding as a side effect of trying to understand the API surface for sandbox-escape testing (which was not attempted — see out-of-scope notes in technical-report.md).
References
- CWE-841, CWE-778, CWE-306
- OWASP ASVS 4.0 — V4.3.2 (multi-factor for high-risk operations)
- AWS S3 Object Lock for immutable audit logs