Blog

From the Shadows: The Authentication That Went Missing

0 Mins Read

·

Friday, August 29, 2025

Brad Geesaman

Principal Security Engineer

From the Shadows: The Authentication That Went Missing

What if an endpoint meant for internal use only quietly exposed file upload privileges to the entire internet? No keys. No tokens. No rate limiting.

It looked harmless enough. A simple POST endpoint that returns a pre-signed S3 URL to upload a file. Nothing fancy. But when you look closer, something important is missing: authentication.

That’s all it takes. One unguarded endpoint, and suddenly, anyone who finds it can start uploading to your bucket.

What the Feature Was Supposed to Do

The original intent was straightforward: allow users to upload files to an S3 bucket via a pre-signed URL, obtained by calling a backend endpoint that generates it securely. Authentication was intended so that only known, logged-in users should be able to request upload URLs.

But somewhere between development and deployment, the authentication check got disabled. Maybe for local testing. Maybe for internal tooling. Either way, it stayed that way. And the endpoint quietly went live, unauthenticated.

Shining a Light on this Flaw

This is an anonymized version of a real finding detected by the Ghost platform in a Python/Flask application.

See if you can spot the problem.


@app.route('/files/init-upload', methods=['POST'])
def init_file_upload():
    ...
    upload_info = generate_presigned_url(filename)
    return jsonify({
        "status": "success",
        "message": "Pre-signed URL generated successfully",
        "data": upload_info
    })

Did you catch it? The critical piece missing here is a decorator like @requires_auth . That one line would enforce authentication on all calls to this endpoint. It’s subtle, and it’s easy for the eye to miss.

How would this be Exploited?

Now, there’s nothing stopping an unauthenticated caller from generating pre-signed URLs at will:


$ curl -X POST <http://your-app.com/files/init-upload> \\
    -H 'Content-Type: application/json' \\
    -d '{"filename": "test-document.pdf"}'

And just like that, they receive:


{
  "data": {
    "expires_at": "2025-08-27T17:20:12.978160",
    "filename": "test-document.pdf",
    "presigned_url": "https://files-prod-storage.s3.us-east-1.amazonaws.com/588c6a73-ddcb-490c-8239-921eba2cec76/test-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE&X-Amz-Date=20250827T162012Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=9BlgeFj8wsImHJoBpK9lfxyrJV4t",
    "upload_id": "588c6a73-ddcb-490c-8239-921eba2cec76"
  },
  "message": "Pre-signed URL generated successfully",
  "status": "success"
}

Which provides them with a short-lived URL they can use to directly upload files to that S3 bucket. They can upload malicious files to change or disrupt a workflow. But perhaps more costly would be a Denial of Wallet attack where they repeatedly upload a huge quantity of large files and leave you with a surprise cloud bill at the end of the month.

The Fix

Fixing this requires explicitly adding an authentication check to the route. That means wrapping the function with a decorator (e.g. @requires_auth) that verifies credentials before allowing the request to proceed:


@app.route('/files/init-upload', methods=['POST'])
@requires_auth # Define a function that ensures this request is authenticated
def init_file_upload():
    ...
    upload_info = generate_presigned_url(filename)
    return jsonify({
        "status": "success",
        "message": "Pre-signed URL generated successfully",
        "data": upload_info
    })

Now, unauthenticated requests will receive a 401 response instead of a pre-signed upload token.

Why This Hides from Legacy Scanners

Static analysis tools often look for common and specific patterns to surface potential risks with keywords like exec, eval, or direct access to sensitive resources. They might even check for authentication decorators, but they won’t always have the context to know when those have a real impact when detected.

Dynamic scanners won’t fare much better unless they:

  • Know the expected authentication model,

  • Are aware this endpoint exists (e.g. it’s not documented in an OpenAPI spec), and

  • Understand that S3 pre-signed URLs are a sensitive output worth flagging.

The endpoint isn’t doing anything obviously dangerous. It’s not deleting data or returning secrets. But it grants a capability like an upload token to a cloud resource which only makes sense in a trusted context. Without that context, scanners typically move on.

This is a classic example of something that looked ok in isolation, but becomes risky once you understand how it’s meant to be used. Ghost's Agentic AI scanning engine understands the context—and flags this kind of exposure immediately.

Takeaways for Developers

  • Never assume authentication exists just because it used to.

  • For sensitive operations involving uploads, downloads, or modifications to cloud resources: enforce access control explicitly.

  • Always test endpoints from the outside-in, like an attacker would. If you find yourself saying “this should be protected,” stop and confirm that it actually is.

Stay tuned for the next edition of From the Shadows, where we’ll shine a light on another subtle flaw lurking just beneath the surface of everyday application logic.

If you’d like to see how Ghost uncovers these kinds of issues before attackers do, sign up here.

Step Into The Underworld Of
Autonomous AppSec

Step Into The Underworld Of
Autonomous AppSec

Step Into The Underworld Of
Autonomous AppSec

Ghost Security provides autonomous app security with Agentic AI, enabling teams to discover, test, and mitigate risks in real time across complex digital environments.

Join our E-mail list

Join the Ghost Security email list—where we haunt vulnerabilities and banish breaches!

© 2025 Ghost Security. All rights reserved

Ghost Security provides autonomous app security with Agentic AI, enabling teams to discover, test, and mitigate risks in real time across complex digital environments.

Join our E-mail list

Join the Ghost Security email list—where we haunt vulnerabilities and banish breaches!

© 2025 Ghost Security. All rights reserved

Ghost Security provides autonomous app security with Agentic AI, enabling teams to discover, test, and mitigate risks in real time across complex digital environments.

Join our E-mail list

Join the Ghost Security email list—where we haunt vulnerabilities and banish breaches!

© 2025 Ghost Security. All rights reserved