Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions upload_profile_picture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os
import requests
from flask import request, jsonify, g
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = "static/uploads/profile_pictures"


def upload_profile_picture_url(current_user):
"""
Accepts a remote image URL and saves it as the user's profile picture.
"""
data = request.get_json()
image_url = data.get("image_url")

if not image_url:
return jsonify({"error": "image_url is required"}), 400

try:
# Vulnerability: SSRF — image_url is user-controlled with no allowlist or
# host validation. Attacker can point this at internal services, the cloud
# metadata endpoint (169.254.169.254), or loopback addresses.
resp = requests.get(image_url, timeout=10, allow_redirects=True, verify=False)

if resp.status_code != 200:
return jsonify({"error": "Failed to fetch image"}), 400

content_type = resp.headers.get("Content-Type", "")
ext = ".jpg"
if "png" in content_type:
ext = ".png"
elif "gif" in content_type:
ext = ".gif"

os.makedirs(UPLOAD_FOLDER, exist_ok=True)
filename = secure_filename(f"user_{current_user['id']}_profile{ext}")
file_path = os.path.join(UPLOAD_FOLDER, filename)

with open(file_path, "wb") as f:
f.write(resp.content)

return jsonify({"message": "Profile picture updated", "file_path": file_path}), 200

except Exception as e:
return jsonify({"error": str(e)}), 500
Comment on lines +9 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH Server-Side Request Forgery (SSRF) in Profile Picture Upload

The upload_profile_picture_url function in upload_profile_picture.py:9-42 accepts a user-provided image_url and uses the requests.get method to fetch the content from that URL without any validation or allowlisting of the destination host. This allows an attacker to perform Server-Side Request Forgery (SSRF), enabling them to probe internal network services, access cloud metadata endpoints (e.g., 169.254.169.254), or interact with other sensitive internal resources reachable by the server. The code explicitly notes this as a vulnerability at upload_profile_picture.py:20-22.

Fix with AI

Open in Cursor Open in Claude

A security vulnerability was found by Hacktron.

File: upload_profile_picture.py
Lines: 9-45
Severity: high

Vulnerability: Server-Side Request Forgery (SSRF) in Profile Picture Upload

Description:
The `upload_profile_picture_url` function in [upload_profile_picture.py:9-42](./testerror/upload_profile_picture.py:9-42) accepts a user-provided `image_url` and uses the `requests.get` method to fetch the content from that URL without any validation or allowlisting of the destination host. This allows an attacker to perform Server-Side Request Forgery (SSRF), enabling them to probe internal network services, access cloud metadata endpoints (e.g., `169.254.169.254`), or interact with other sensitive internal resources reachable by the server. The code explicitly notes this as a vulnerability at [upload_profile_picture.py:20-22](./testerror/upload_profile_picture.py:20-22).

Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.

Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.

Triage: Reply !fp <reason> (false positive), !valid (confirmed), or !accepted_risk <reason>. Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.

View finding in Hacktron