Problem
GET /volunteer/:id/doc/download?url=<arbitrary> (doc.routes.ts:47) fetches any URL the caller supplies and pipes the response back to the client, with no domain validation:
const { url } = request.query as { url: string };
const [urlObj, error] = await tryCatch(fetch(url));
Even though the route is COORDINATOR-only, a compromised coordinator account can use this as an SSRF proxy to:
- Call
http://169.254.169.254/latest/meta-data/iam/security-credentials/ (AWS IMDSv1) and steal the EC2 instance's IAM credentials
- Probe internal services not reachable from the public internet
- Read internal S3 presigned URLs or admin endpoints
Fix
Validate the supplied URL against a domain allowlist before fetching. Only S3 hostnames (and/or the configured CDN) should be accepted:
const ALLOWED_HOSTS = new Set([
`${process.env.AWS_S3_BUCKET_NAME}.s3.eu-central-1.amazonaws.com`,
`${process.env.AWS_S3_BUCKET_NAME}.s3.amazonaws.com`,
// add CDN hostname if needed
]);
const parsed = new URL(url);
if (!ALLOWED_HOSTS.has(parsed.hostname)) {
return reply.status(400).send({ message: "URL not allowed." });
}
Alternatively, once real presigned URL generation is in place, the backend can fetch directly from S3 using the stored s3Key instead of accepting a URL at all — eliminating the user-supplied URL entirely.
Context
Part of pre-NGO-onboarding security audit.
Problem
GET /volunteer/:id/doc/download?url=<arbitrary>(doc.routes.ts:47) fetches any URL the caller supplies and pipes the response back to the client, with no domain validation:Even though the route is COORDINATOR-only, a compromised coordinator account can use this as an SSRF proxy to:
http://169.254.169.254/latest/meta-data/iam/security-credentials/(AWS IMDSv1) and steal the EC2 instance's IAM credentialsFix
Validate the supplied URL against a domain allowlist before fetching. Only S3 hostnames (and/or the configured CDN) should be accepted:
Alternatively, once real presigned URL generation is in place, the backend can fetch directly from S3 using the stored
s3Keyinstead of accepting a URL at all — eliminating the user-supplied URL entirely.Context
Part of pre-NGO-onboarding security audit.