Skip to content

get-image performs no per-case authorization — any authenticated user can fetch any case image #8035

Description

@renemadsen

Summary

GET api/template-files/get-image/{fileName}.{ext} authenticates the caller but performs no per-case authorization. Any authenticated user can fetch any case image whose filename they can derive, regardless of which property or customer it belongs to.

Found while planning a compliance page that surfaces case images more widely (microting/eform-backendconfiguration-plugin#1160). Pre-existing — not introduced by that work — but that work increases exposure, so it is worth deciding on now.

The gap

eFormAPI.Web/Controllers/Eforms/EFormFilesController.cs:109-111 serves the endpoint under the class-level [Authorize] only. It takes a filename and nothing else — no case id, no property id — so there is nothing to authorize against. Any valid bearer token is sufficient for any image.

How guessable is the filename?

Not trivially, but it is derived rather than random. The 700px derivative is:

$"{UploadedData.Id}_700_{UploadedData.Checksum}{UploadedData.Extension}"

The checksum is the meaningful entropy. But note filenames are not secret in practice: they appear in API responses to any user who can read the case list, so a user with legitimate access to one property can retain and replay names, and names leak through logs, exports and browser history. This is closer to insecure-direct-object-reference than to a capability URL, and it should not be relied on as one.

Why it has not bitten

The frontend's authImage pipe (common/pipes/auth-image.pipe.ts) fetches with the bearer token and returns a base64 data: URL, so images are only ever requested for cases the UI already loaded. The gap is in what the endpoint permits, not in what the UI does.

Related, and deliberately not the fix

There is a second route, get-report-image/{name}.{ext}&token= (:116-119), which is [AllowAnonymous] and guarded by the SDK master token in the URL. That is weaker, not stronger — do not extend it or build on it.

Suggested direction

Take the case id (or the uploaded-data id) as a parameter and authorize the caller against that case's property, mirroring however case reads are already authorized. That is a contract change with frontend call sites, hence filing rather than fixing.

Worth noting for whoever picks this up: no GetPreSignedURL call exists anywhere in the SDK, so there is currently no token-free image URL other than the master-token route above. A presigned-URL approach would be a larger change than adding an authorization check.

Scope note

Server-side report generation is unaffected — WordService reads bytes from a Stream (S3 or disk) and never goes through this endpoint. This is specifically about browser-facing image fetches.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions