Python urllib Insecure FTP urlopen Vulnerability

High Risk Insecure Transport
PythonurllibFTPInsecure TransportPlaintext CredentialsNetwork Security

What it is

Application uses urllib.request.urlopen() with FTP URLs, which transmit data in plaintext including credentials, exposing sensitive information to network eavesdropping.

import urllib.request from flask import request @app.route('/download_file') def download_file(): # Vulnerable: FTP URL exposes credentials and data ftp_url = 'ftp://user:password@ftp.example.com/file.txt' response = urllib.request.urlopen(ftp_url) return response.read() @app.route('/fetch_ftp') def fetch_ftp(): # Vulnerable: User-controlled FTP access ftp_host = request.args.get('host') filename = request.args.get('file') # Dangerous: FTP with potential credential exposure ftp_url = f'ftp://anonymous@{ftp_host}/{filename}' try: response = urllib.request.urlopen(ftp_url) return response.read() except: return 'FTP error', 500
import urllib.request import urllib.parse from flask import request import ftplib import ssl def validate_secure_url(url): """Validate URL uses secure protocols only.""" parsed = urllib.parse.urlparse(url) secure_schemes = ['https', 'sftp', 'ftps'] if parsed.scheme not in secure_schemes: raise ValueError(f'Insecure protocol {parsed.scheme} not allowed. Use: {secure_schemes}') return parsed @app.route('/download_file') def download_file(): """Secure file download using HTTPS.""" try: # Secure: Use HTTPS instead of FTP https_url = 'https://secure.example.com/api/files/file.txt' validate_secure_url(https_url) # Create secure SSL context context = ssl.create_default_context() response = urllib.request.urlopen( https_url, timeout=30, context=context ) return response.read() except (ValueError, urllib.error.URLError) as e: return f'Download failed: {str(e)}', 500 @app.route('/fetch_ftp') def fetch_ftp(): """Secure file fetch with validation.""" return {'error': 'FTP access disabled for security. Use HTTPS API instead.'}, 400 # Secure alternative using SFTP class SecureFTPClient: """Secure FTP client using SFTP.""" def __init__(self, host, username, key_file=None): self.host = host self.username = username self.key_file = key_file # Validate host is in allowlist allowed_hosts = ['sftp.trusted.com', 'secure-ftp.partner.com'] if host not in allowed_hosts: raise ValueError(f'Host {host} not in allowlist') def download_file(self, remote_path, max_size=10*1024*1024): """Securely download file via SFTP.""" try: import paramiko # Requires paramiko for SFTP # Create SSH client ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.RejectPolicy()) # Strict host key checking # Connect with key-based authentication ssh.connect( self.host, username=self.username, key_filename=self.key_file, timeout=30 ) # Create SFTP client sftp = ssh.open_sftp() # Check file size before download file_stat = sftp.stat(remote_path) if file_stat.st_size > max_size: raise ValueError(f'File too large: {file_stat.st_size} bytes') # Download file securely with sftp.open(remote_path, 'rb') as remote_file: content = remote_file.read() sftp.close() ssh.close() return content except ImportError: raise RuntimeError('paramiko library required for SFTP') except Exception as e: raise RuntimeError(f'SFTP download failed: {str(e)}') @app.route('/secure_file_download') def secure_file_download(): """Example of secure file download.""" filename = request.args.get('file', '') # Validate filename if not filename or '..' in filename or filename.startswith('/'): return 'Invalid filename', 400 try: # Use secure SFTP client sftp_client = SecureFTPClient( host='sftp.trusted.com', username='secure_user', key_file='/path/to/private/key' ) content = sftp_client.download_file(f'/secure/{filename}') return content, 200, { 'Content-Type': 'application/octet-stream', 'Content-Disposition': f'attachment; filename={filename}' } except (ValueError, RuntimeError) as e: return f'Download error: {str(e)}', 500 # Alternative: Cloud storage approach @app.route('/cloud_download') def cloud_download(): """Secure cloud storage download.""" filename = request.args.get('file', '') if not filename: return 'Filename required', 400 # Use cloud storage API instead of FTP cloud_url = f'https://storage.cloud.com/secure-bucket/{filename}' try: validate_secure_url(cloud_url) # Add authentication headers request_obj = urllib.request.Request( cloud_url, headers={ 'Authorization': 'Bearer YOUR_SECURE_TOKEN', 'User-Agent': 'SecureApp/1.0' } ) context = ssl.create_default_context() response = urllib.request.urlopen(request_obj, timeout=30, context=context) return response.read() except (ValueError, urllib.error.URLError) as e: return f'Cloud download failed: {str(e)}', 500

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Code opens FTP URLs: urllib.request.urlopen('ftp://ftp.example.com/file.txt'). FTP transmits credentials and data unencrypted. Username and password sent in clear text. Files transferred without encryption. Network attackers intercept FTP authentication and file contents.

Root causes

Using urllib.request.urlopen() with FTP URLs

Code opens FTP URLs: urllib.request.urlopen('ftp://ftp.example.com/file.txt'). FTP transmits credentials and data unencrypted. Username and password sent in clear text. Files transferred without encryption. Network attackers intercept FTP authentication and file contents.

Not Validating URL Schemes, Allowing FTP Protocol

Opening URLs without scheme validation: url = user_input; urllib.request.urlopen(url). User input or configuration may contain ftp:// URLs. No protocol restriction. Applications should validate schemes, rejecting insecure protocols. Missing validation allows FTP and other insecure protocols.

Using FTP for File Downloads in Automated Systems

Scheduled tasks downloading via FTP: urlopen('ftp://server/backup.tar.gz'). Legacy systems or integrations using FTP. Credentials in URLs or code. Automated processes transmitting sensitive files over unencrypted FTP. FTP common in legacy integrations but fundamentally insecure.

FTP URLs in Configuration Files or Environment Variables

Configuration with FTP: FILE_URL = os.environ['SOURCE_URL']; urlopen(FILE_URL). Legacy configurations containing ftp:// URLs. Migration from old systems. Environment variables not validated for protocol security. External configuration sources may specify insecure FTP.

Using Anonymous FTP Without Authentication Awareness

Anonymous FTP access: urlopen('ftp://ftp.example.com/pub/data'). Assumption of public data safety. Even public files retrieved over unencrypted FTP vulnerable to tampering. Man-in-the-middle can modify files during transfer. No integrity verification. Anonymous FTP still requires secure transport.

Fixes

1

Replace FTP with FTPS or SFTP for Encrypted File Transfer

Use FTPS or SFTP instead: from ftplib import FTP_TLS; ftp = FTP_TLS('host'); or use paramiko for SFTP: sftp = paramiko.SFTPClient.from_transport(transport). Both provide encryption. SFTP uses SSH, FTPS uses TLS. Never use plain FTP for any file transfers.

2

Use HTTPS for File Downloads Instead of FTP

Replace FTP with HTTPS: requests.get('https://example.com/file.txt'). Modern services should provide HTTPS endpoints. Better integration with authentication (OAuth, API keys). HTTPS provides encryption and integrity. Simpler than FTP, better security, easier authentication management.

3

Validate URL Schemes, Reject FTP and Insecure Protocols

Check schemes before urlopen: from urllib.parse import urlparse; scheme = urlparse(url).scheme; if scheme not in ['https', 'ftps']: raise ValueError('Secure protocol required'). Allowlist approach for protocols. Reject ftp, http, and other insecure schemes.

4

Use Object Storage Services Instead of FTP Servers

Migrate to cloud storage: boto3 for AWS S3, google-cloud-storage for GCS, azure-storage-blob for Azure. Provide HTTPS APIs. Built-in encryption, authentication, access control. Versioning and lifecycle management. Modern alternative to FTP with superior security and features.

5

Implement Integrity Verification for All File Downloads

Verify file hashes after download: import hashlib; downloaded_hash = hashlib.sha256(content).hexdigest(); if downloaded_hash != expected_hash: raise ValueError('Integrity check failed'). Even with encryption, verify integrity. Use checksums or signatures. Defense against tampering.

6

Scan Codebase for FTP URL Usage and Remove All Instances

Find FTP usage: grep -r 'ftp://' --include="*.py". Use bandit to detect insecure protocols. Replace all FTP with secure alternatives. Update documentation removing FTP references. Ensure configuration examples use HTTPS or SFTP. Complete removal prevents future FTP usage.

Detect This Vulnerability in Your Code

Sourcery automatically identifies python urllib insecure ftp urlopen vulnerability and many other security issues in your codebase.