124 lines
4.6 KiB
Markdown
124 lines
4.6 KiB
Markdown
# 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.
|