Record security remediation verification
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# M014 Security Remediation Verification
|
||||
|
||||
This report retests the two confirmed findings from `M013` after code fixes landed in `M014`.
|
||||
|
||||
Related assessment:
|
||||
|
||||
- `docs/security-assessments/M013-adversarial-security-assessment.md`
|
||||
|
||||
## Fixed Findings
|
||||
|
||||
### 1. Job import preview SSRF via hostname-based loopback/private-address bypass
|
||||
|
||||
- **Original issue:** `POST /api/jobimport/preview` accepted hostnames that resolved to loopback/private addresses and fetched internal targets.
|
||||
- **Fix status:** **Fixed**
|
||||
- **Primary code changes:**
|
||||
- `JobTrackerApi/Services/JobImport/JobImportService.cs`
|
||||
- `JobTrackerApi/Services/JobImport/IHostAddressResolver.cs`
|
||||
- `JobTrackerApi/Program.cs`
|
||||
|
||||
#### What changed
|
||||
|
||||
- URL validation now resolves hostnames before allowing outbound fetches.
|
||||
- Validation rejects loopback, private, link-local, and other internal destinations for both literal IPs and resolved hostnames.
|
||||
- Automatic redirects are disabled on the `jobimport` HTTP client.
|
||||
|
||||
#### Retest inputs and outcomes
|
||||
|
||||
| Exploit input | Expected after fix | Observed |
|
||||
| --- | --- | --- |
|
||||
| `http://127.0.0.1.nip.io:5202/api/auth/config` | reject | `400` with parser `none` and local/private-network rejection |
|
||||
| `http://localhost.localdomain:5202/api/auth/config` | reject | `400` with parser `none` and local/private-network rejection |
|
||||
| `http://[::1]:5202/api/auth/config` | reject | `400` with local/private-network rejection |
|
||||
| `http://2130706433:5202/api/auth/config` | reject | `400` with local/private-network rejection |
|
||||
| `https://example.com` | allow public fetch path | request reached parser path and failed only with `No JobPosting schema found.` |
|
||||
|
||||
#### Verdict
|
||||
|
||||
**Pass.** The original SSRF exploit shapes are now blocked and a normal external URL still follows the intended public-host path.
|
||||
|
||||
---
|
||||
|
||||
### 2. Subjectless signed local JWTs authenticate successfully and can disable owner scoping
|
||||
|
||||
- **Original issue:** a validly signed local JWT without `nameidentifier` / `sub` was accepted, and owner filters were written to allow all rows when `CurrentUserId` was null.
|
||||
- **Fix status:** **Fixed**
|
||||
- **Primary code changes:**
|
||||
- `JobTrackerApi/Services/LocalAuthIdentity.cs`
|
||||
- `JobTrackerApi/Services/CurrentUserService.cs`
|
||||
- `JobTrackerApi/Program.cs`
|
||||
- `Data/JobTrackerContext.cs`
|
||||
|
||||
#### What changed
|
||||
|
||||
- Local JWT bearer validation now rejects tokens without a concrete subject/nameidentifier.
|
||||
- Current-user resolution uses the same required-identity rule.
|
||||
- Owner query filters now deny on null current user instead of allowing all rows.
|
||||
|
||||
#### Retest input and outcomes
|
||||
|
||||
Malformed token shape reused from `M013`:
|
||||
|
||||
- valid local signature
|
||||
- valid issuer/audience/lifetime
|
||||
- **missing** `ClaimTypes.NameIdentifier` / `sub`
|
||||
|
||||
Observed after fix:
|
||||
|
||||
| Request | Expected after fix | Observed |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/auth/me` with subjectless signed local JWT | reject | `401` |
|
||||
| `GET /api/companies` with subjectless signed local JWT | reject | `401` |
|
||||
| `GET /api/rules` with subjectless signed local JWT | reject | `401` |
|
||||
|
||||
#### Automated proof
|
||||
|
||||
Focused tests now cover:
|
||||
|
||||
- required-subject local identity behavior
|
||||
- fail-closed owner query filters when current user is missing
|
||||
|
||||
#### Verdict
|
||||
|
||||
**Pass.** The malformed token no longer authenticates, and the owner filters fail closed behind the auth boundary.
|
||||
|
||||
## Focused Test Evidence
|
||||
|
||||
### SSRF-focused tests
|
||||
|
||||
```bash
|
||||
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobImportServiceTests
|
||||
```
|
||||
|
||||
Observed:
|
||||
|
||||
- passed
|
||||
- covers loopback-resolving hostname rejection
|
||||
- covers private-address hostname rejection
|
||||
- covers normal public-host path
|
||||
|
||||
### Local-auth / owner-scope tests
|
||||
|
||||
```bash
|
||||
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter "LocalAuthIdentityTests|AuthAndSystemControllerTests|OwnershipGuardTests"
|
||||
```
|
||||
|
||||
Observed:
|
||||
|
||||
- passed
|
||||
- covers required-subject behavior and fail-closed owner filter semantics
|
||||
|
||||
## Remaining Boundaries
|
||||
|
||||
- The local runtime still uses a partial SQLite schema for some domain tables, so broader cross-user raw-id authorization retests remain best treated as a separate follow-up pass in a fuller environment.
|
||||
- Those unresolved candidates were not needed to close the two confirmed `M013` findings, because both confirmed exploit shapes were retested directly and now fail.
|
||||
|
||||
## Final Verdict
|
||||
|
||||
`M014` closes the two confirmed `M013` vulnerabilities:
|
||||
|
||||
1. hostname-based authenticated SSRF in job import preview — **fixed**
|
||||
2. subjectless local JWT authentication / owner-scope fail-open behavior — **fixed**
|
||||
|
||||
Both fixes were verified with focused automated tests and hostile runtime retests using the original exploit shapes.
|
||||
Reference in New Issue
Block a user