漏洞总结 漏洞概述 漏洞标题: Time entry update endpoint allows cross-organization modification of a known time-entry UUID 漏洞描述: 该漏洞允许攻击者通过修改已知的时间条目UUID,跨组织修改时间条目数据。具体来说,API 接受来自另一个组织的时间条目,并在调用者具有 权限时,允许修改和重新绑定到调用者组织中的对象。 影响范围 受影响版本: 0.12.0 修复版本: v0.12.1 CVSS评分: 5.8 / 10 攻击向量: 网络 攻击复杂度: 高 所需权限: 高 用户交互: 高 影响范围: 变更 机密性: 无 完整性: 高 可用性: 无 修复方案 修复版本: v0.12.1 POC代码 ```python request URL organization: orgA route-bound timeEntry.id: orgB.time_entries[id = victim_uuid] submitted project/task/tag/member: orgA-owned objects result: orgB row is updated with orgA object references This is a cross-tenant integrity issue rather than a purely local authorization smell. The application globally disables mass-assignment protection, the database only enforces single-column foreign keys, and the existing self-consistency command does not check whether a time entry's organization_id matches its project, task, client, or member organization. After the save, the controller dispatches recalculation jobs for the old and new project/task, and those model relations aggregate time_entries by project_id or task_id without an additional organization filter. I also confirmed that report aggregation resolves project/task/client descriptors by ID without organization scoping, so polluted rows can affect project/task totals and grouped report labels. I did not identify a built-in low-privilege endpoint in this codebase that discloses foreign time_entry.id values, so the confirmed exploit boundary is limited to cases where that UUID is already known from some other source. Until a fix exists, the update path needs the same organization ownership check already present in destroy() or equivalent scoped binding enforcement. PoC 1. Create or use an authenticated account that has time-entries:write:all in organization orgA. 2. Obtain any valid time_entries.id from another organization orgB. In this review I confirmed exploitation only for the known-ID case; I did not identify a built-in low-privilege disclosure path for foreign IDs. 3. Pick any project_id and optional task_id that belong to orgA, then send: PUT /api/v4/organizations/{orgA}/time-entries/{victim_uuid} Content-Type: application/json { "project_id": "{orgA_project_uuid}", "task_id": "{orgA_task_uuid}" } 4. Observe a successful update response instead of a forbidden response, and confirm the row state changes even though the row still belongs to orgB: before: time_entries.id = victim_uuid, organization_id = orgB, project_id = old_orgB.project after: time_entries.id = victim_uuid, organization_id = orgB, project_id = orgA.project 5. Wait for or run the recalculation jobs. The new orgA project/task totals now include the foreign row, and grouped report output can resolve descriptor names from the orgA objects attached to that orgB row. Impact A caller with elevated time-entry update rights in one organization can modify a known time-entry row from another organization and persist cross-tenant reference corruption. The confirmed impact is unauthorized integrity modification of another tenant's records plus downstream pollution of project/task totals and report group labels. I did not confirm a built-in path in this codebase to enumerate arbitrary foreign time-entry UUIDs, so broader exploitation depends on how such identifiers are exposed in the deployment.