unrestricted-file-upload-anti-pattern

📁 igbuend/grimbard 📅 Jan 20, 2026
3
总安装量
3
周安装量
#56173
全站排名
安装命令
npx skills add https://github.com/igbuend/grimbard --skill unrestricted-file-upload-anti-pattern

Agent 安装分布

claude-code 3
codex 3
cursor 3
opencode 3
trae-cn 2
gemini-cli 2

Skill 文档

Unrestricted File Upload Anti-Pattern

Severity: Critical

Summary

Applications accept user-uploaded files without validating type, content, or size, enabling attackers to upload malicious scripts or executables. Leads to remote code execution (web shells), server compromise, or denial-of-service (disk exhaustion).

The Anti-Pattern

The anti-pattern is accepting uploaded files without validating type, content, and size.

BAD Code Example

# VULNERABLE: No validation of file type, content, or size.
from flask import Flask, request
import os

UPLOAD_FOLDER = '/var/www/uploads' # This directory might be accessible by the web server.
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return 'No file part', 400
    file = request.files['file']
    if file.filename == '':
        return 'No selected file', 400

    # CRITICAL FLAW: The application takes the filename as is and saves the file.
    # An attacker can upload a file named `shell.php` with PHP code.
    # If the `UPLOAD_FOLDER` is web-accessible and PHP is executed,
    # the attacker achieves Remote Code Execution.
    filename = file.filename
    file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    return f'File {filename} uploaded successfully', 200

# Attack Scenario:
# 1. Attacker crafts a PHP file named `shell.php` containing `<?php system($_GET['cmd']); ?>`.
# 2. Attacker uploads `shell.php` via this endpoint.
# 3. Attacker accesses `http://your-app.com/uploads/shell.php?cmd=ls%20-la`
#    and can now execute arbitrary commands on the server.

GOOD Code Example

# SECURE: Implement a multi-layered validation approach for file uploads.
from flask import Flask, request, jsonify
import os
import uuid # For generating unique filenames
from magic import from_buffer # `python-magic` for magic byte detection

UPLOAD_FOLDER = '/var/www/safe_uploads' # Store files outside the web root.
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'pdf'}
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/upload/secure', methods=['POST'])
def upload_file_secure():
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400

    if file:
        # 1. Validate file extension (allowlist approach).
        if not allowed_file(file.filename):
            return jsonify({'error': 'File type not allowed'}), 400

        # 2. Validate file size.
        file.seek(0, os.SEEK_END)
        file_length = file.tell()
        file.seek(0)
        if file_length > MAX_FILE_SIZE:
            return jsonify({'error': 'File too large'}), 400

        # 3. Validate actual MIME type using magic bytes (more reliable than Content-Type header).
        file_buffer = file.read(1024) # Read a chunk for magic byte detection
        file.seek(0) # Reset file pointer
        actual_mime = from_buffer(file_buffer, mime=True)
        if actual_mime not in ['image/png', 'image/jpeg', 'image/gif', 'application/pdf']:
            return jsonify({'error': f'Invalid file content type: {actual_mime}'}), 400

        # 4. Generate a unique and safe filename. Never use the original filename directly.
        original_extension = file.filename.rsplit('.', 1)[1].lower()
        safe_filename = str(uuid.uuid4()) + '.' + original_extension

        # 5. Store the file in a secure location, preferably outside the web root.
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], safe_filename))
        return jsonify({'message': f'File {safe_filename} uploaded successfully'}), 200

Detection

  • Review file upload handlers: Identify all endpoints that allow users to upload files.
  • Check validation logic: Examine how filenames, file types, and file contents are validated. Look for:
    • Missing extension checks or using blocklists instead of allowlists.
    • Relying solely on the Content-Type HTTP header, which is easily spoofed.
    • Not checking the actual content of the file (magic bytes).
    • Missing size limits.
  • Inspect storage location: Determine where uploaded files are stored. Are they in a web-accessible directory? Can executables be run from there?

Prevention

  • Strict allowlist for file extensions: Allow only specific safe extensions (.png, .jpg, .pdf). Never use blocklists.
  • Verify file content (magic bytes): Inspect first bytes to determine true file type. Never trust client-provided Content-Type header.
  • Enforce file size limits: Set maximum file size to prevent DoS and resource exhaustion.
  • Generate unique, random filenames: Use cryptographically secure random filenames. Never use user-provided filenames (prevents path traversal and overwrites).
  • Store files outside web root: Store uploads in non-web-accessible directories. Serve through controlled handlers.
  • Set no-execute permissions: Configure upload directory with no-execute permissions to block web shell execution.
  • Scan for malware: Integrate antivirus/malware scanner for all uploads.

Related Security Patterns & Anti-Patterns

References