TA AzeezCode

Case studies

Secure file-upload implementation review

A document upload feature trusted the client supplied content type and stored files inside the web root. Rebuilt with type verification, random storage names, out-of-root storage and authorised download.

high severity File upload Secure design Access control Retested

Context

A customer portal allowed users to upload supporting documents. The review covered design and implementation, performed against a staging deployment with written authorisation.

Scope

Upload endpoint, storage location, retrieval endpoint and the surrounding authorisation checks.

Vulnerability

Three issues in one workflow:

  • The MIME type was taken from the client supplied Content-Type header.
  • Uploaded files kept their original filename and were written inside the web root.
  • Retrieval used a predictable path with no ownership check.

Root cause

The feature was built for the happy path: real users uploading real PDFs from a browser. Every control assumed the client was honest, and storage location was chosen for convenience of serving rather than for isolation.

Impact

A user could upload a file with an executable extension and request it directly, and any user could enumerate and download other customers' documents. Combined, this was the highest-risk finding in the engagement.

Remediation

  • Verified the real type server-side with a finfo inspection and an allow-list of accepted types.
  • Enforced a maximum size before processing, and rejected multi-extension filenames.
  • Generated a random storage name and discarded the user supplied name, keeping the original only as display metadata.
  • Moved storage outside the web root so no uploaded file is directly reachable.
  • Served downloads through an authorised controller action that checks record ownership, streams the file and sets Content-Disposition: attachment with X-Content-Type-Options: nosniff.
  • Re-encoded images and stripped metadata where images were accepted.
$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file->getRealPath());

abort_unless(in_array($mime, ['application/pdf', 'image/png', 'image/jpeg'], true), 422);

$path = $file->storeAs('documents', bin2hex(random_bytes(16)).'.bin', 'private');

Retesting

Retesting attempted a PHP file renamed to .pdf, a polyglot image, a double extension, a null-byte filename, an oversized upload and direct requests to the previous storage path. All were rejected or returned 404. Cross-account download attempts returned 403 with no information disclosure.

Lessons

  • The client tells you what it wants you to believe; the file itself tells you what it is.
  • Storage location is a security control, not an infrastructure detail.
  • Upload security is incomplete until the download path enforces authorisation.

Authorisation

This work was carried out under written authorisation or inside an authorised training environment. All evidence has been sanitised.

Continue reading

Related case studies

high severity Access control

IDOR: missing server-side authorisation in an invoice workflow

An authenticated user could modify an invoice identifier and attempt to access another customer's resource. The remediation enforced ownership at the database query layer and was verified with positive and negative authorisation tests.

IDOR Broken Access Control Authorisation PHP

5 min read

high severity SSRF

SSRF in a server-side document import feature

An authorised security lab examining how a user-controlled URL can cause a web server to make unintended requests to internal or restricted destinations, followed by layered remediation and retesting.

SSRF Web Security PHP Input Validation

9 min read