Record authorization replay findings
This commit is contained in:
@@ -0,0 +1,130 @@
|
|||||||
|
# M015 Cross-User Authorization Replay Report
|
||||||
|
|
||||||
|
This report covers the follow-up tenant-boundary work after `M013` and `M014`.
|
||||||
|
|
||||||
|
Related artifacts:
|
||||||
|
|
||||||
|
- `docs/security-assessments/M013-adversarial-security-assessment.md`
|
||||||
|
- `docs/security-assessments/M014-security-remediation-verification.md`
|
||||||
|
- `docs/security-assessments/M015-hostile-fixture-setup.md`
|
||||||
|
- `docs/security-assessments/M015-hostile-fixture-setup.json`
|
||||||
|
- `docs/security-assessments/M015-s02-probe-results.json`
|
||||||
|
|
||||||
|
## Test Setup
|
||||||
|
|
||||||
|
A dedicated hostile-test SQLite database was created from the current EF model because the default development DB was missing core domain tables needed for real authorization probes.
|
||||||
|
|
||||||
|
Fixture runtime:
|
||||||
|
|
||||||
|
- clean SQLite DB under `.tmp/m015-fixture`
|
||||||
|
- API started with `Data__Root=/home/pi/development/JobTracker/.tmp/m015-fixture`
|
||||||
|
- registration temporarily enabled for the fixture runtime
|
||||||
|
- two real local users created through the API:
|
||||||
|
- `alice.m015@example.com`
|
||||||
|
- `bob.m015@example.com`
|
||||||
|
|
||||||
|
Alice-owned fixture resources created through the real API:
|
||||||
|
|
||||||
|
- `company_id = 1`
|
||||||
|
- `job_id = 1`
|
||||||
|
- `correspondence_id = 1`
|
||||||
|
- `attachment_id = 1`
|
||||||
|
|
||||||
|
All mutating requests used the real cookie + CSRF contract.
|
||||||
|
|
||||||
|
## Cross-User Probe Summary
|
||||||
|
|
||||||
|
Bob targeted Alice’s fixture ids with a real authenticated session.
|
||||||
|
|
||||||
|
### Defended in this pass
|
||||||
|
|
||||||
|
The following probes failed closed with `404` when Bob targeted Alice’s resources:
|
||||||
|
|
||||||
|
- `GET /api/attachments/1`
|
||||||
|
- `GET /api/attachments/download/1`
|
||||||
|
- `PATCH /api/attachments/1`
|
||||||
|
- `DELETE /api/attachments/1`
|
||||||
|
- `GET /api/correspondence/1`
|
||||||
|
- `DELETE /api/correspondence/1`
|
||||||
|
- `GET /api/jobapplications/1`
|
||||||
|
- `PUT /api/jobapplications/1`
|
||||||
|
- `PATCH /api/jobapplications/1/followup`
|
||||||
|
- `GET /api/jobapplications/1/timeline`
|
||||||
|
- `GET /api/jobapplications/1/tailored-cv-draft`
|
||||||
|
- `GET /api/jobapplications/1/followup-draft`
|
||||||
|
|
||||||
|
These routes did not expose or mutate Alice-owned data in this hostile fixture pass.
|
||||||
|
|
||||||
|
## Confirmed Finding
|
||||||
|
|
||||||
|
### Cross-user read leak on job history
|
||||||
|
|
||||||
|
- **Category:** Authorization / data exposure
|
||||||
|
- **Endpoint:** `GET /api/jobapplications/{id}/history`
|
||||||
|
- **Risk:** **Medium**
|
||||||
|
|
||||||
|
#### Vulnerability
|
||||||
|
|
||||||
|
Before the fix, Bob could request Alice’s job history by raw job id and receive Alice’s `JobEvent` rows.
|
||||||
|
|
||||||
|
Observed pre-fix response:
|
||||||
|
|
||||||
|
- `GET /api/jobapplications/1/history` as Bob
|
||||||
|
- `200 OK`
|
||||||
|
- payload included Alice-owned event data, including the `Created` event for Alice’s job
|
||||||
|
|
||||||
|
#### Example exploit input
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/jobapplications/1/history
|
||||||
|
Cookie: jobtracker_auth=<bob session cookie>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Root cause
|
||||||
|
|
||||||
|
Two issues combined:
|
||||||
|
|
||||||
|
1. `GetHistory(...)` queried `JobEvents` directly by `JobApplicationId` without verifying that the parent job belonged to the current user.
|
||||||
|
2. `JobEvent` had no owner-scoped query filter in `Data/JobTrackerContext.cs`.
|
||||||
|
|
||||||
|
#### Fix
|
||||||
|
|
||||||
|
- `GetHistory(...)` now checks whether the requested job exists in the current user’s scoped `JobApplications` query and returns `404` if it does not.
|
||||||
|
- `JobEvent` now has an owner-scoped query filter tied to `JobApplication.OwnerUserId`.
|
||||||
|
- Added focused regression test:
|
||||||
|
- `JobTrackerApi.Tests/JobApplicationsAuthorizationTests.cs`
|
||||||
|
|
||||||
|
#### Replay after fix
|
||||||
|
|
||||||
|
Observed post-fix response:
|
||||||
|
|
||||||
|
- `GET /api/jobapplications/1/history` as Bob
|
||||||
|
- `404 Not Found`
|
||||||
|
|
||||||
|
#### Verdict
|
||||||
|
|
||||||
|
**Fixed.**
|
||||||
|
|
||||||
|
## Automated Evidence
|
||||||
|
|
||||||
|
### Focused regression test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsAuthorizationTests
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed:
|
||||||
|
|
||||||
|
- passed
|
||||||
|
- verifies `GetHistory` returns `NotFound` for another user’s job
|
||||||
|
|
||||||
|
## Final Assessment
|
||||||
|
|
||||||
|
For the prioritized raw-id authorization seams exercised in this milestone:
|
||||||
|
|
||||||
|
- **confirmed and fixed:** `GET /api/jobapplications/{id}/history`
|
||||||
|
- **no finding in this fixture pass:** attachments, correspondence, primary job read/update, follow-up patch, timeline, tailored draft, follow-up draft
|
||||||
|
|
||||||
|
## Remaining Boundary
|
||||||
|
|
||||||
|
This report covers the endpoints actually exercised in the hostile fixture pass. It does **not** claim that every authorization-sensitive route in the application has been exhaustively proven safe; it closes the high-risk raw-id seams prioritized from the earlier assessment with a real two-user runtime and replay evidence.
|
||||||
Reference in New Issue
Block a user