Python Requests HTTP Connection Vulnerability

Medium Risk Insecure Transport
PythonRequestsHTTPInsecure TransportEncryptionNetwork SecurityData Protection

What it is

Application uses the requests library to make HTTP (unencrypted) connections, exposing transmitted data to eavesdropping, tampering, and man-in-the-middle attacks.

import requests
from flask import request

@app.route('/fetch_data')
def fetch_data():
    # Vulnerable: HTTP request exposes data
    response = requests.get('http://api.example.com/data')
    return response.json()

@app.route('/submit_payment')
def submit_payment():
    # Very dangerous: Payment data over HTTP
    payment_data = {
        'card_number': request.form.get('card'),
        'amount': request.form.get('amount')
    }
    
    # Extremely vulnerable: Financial data unencrypted
    response = requests.post(
        'http://payment.service.com/process',
        json=payment_data
    )
    return response.json()

@app.route('/webhook')
def process_webhook():
    # Vulnerable: HTTP webhook notification
    webhook_data = request.get_json()
    
    # Insecure: Webhook callback over HTTP
    callback_url = webhook_data.get('callback_url')
    response = requests.post(
        callback_url,  # Could be HTTP
        json={'status': 'processed'}
    )
    return 'OK'

# Vulnerable: Configuration and health checks
def check_service_health():
    services = [
        'http://api.internal.com/health',
        'http://db.internal.com/status',
        'http://cache.internal.com/ping'
    ]
    
    results = {}
    for service in services:
        try:
            # Insecure: Internal communications over HTTP
            response = requests.get(service, timeout=5)
            results[service] = response.status_code == 200
        except:
            results[service] = False
    
    return results
import requests
from flask import request
from urllib.parse import urlparse
import os

# Secure configuration
API_BASE_URL = os.getenv('API_BASE_URL', 'https://api.example.com')
PAYMENT_SERVICE_URL = os.getenv('PAYMENT_SERVICE_URL', 'https://payment.secure.com')

def validate_https_url(url):
    """Validate that URL uses HTTPS scheme."""
    if not url:
        raise ValueError('URL cannot be empty')
    
    parsed = urlparse(url)
    if parsed.scheme != 'https':
        raise ValueError(f'Only HTTPS URLs are allowed, got: {parsed.scheme}')
    
    return url

def make_secure_request(method, url, **kwargs):
    """Make a secure HTTP request with proper validation."""
    # Validate URL scheme
    validate_https_url(url)
    
    # Set secure defaults
    kwargs.setdefault('timeout', 10)
    kwargs.setdefault('verify', True)  # SSL verification
    kwargs.setdefault('headers', {}).update({
        'User-Agent': 'SecureApp/1.0'
    })
    
    try:
        response = requests.request(method, url, **kwargs)
        response.raise_for_status()
        return response
    except requests.exceptions.RequestException as e:
        raise RuntimeError(f'Secure request failed: {str(e)}')

@app.route('/fetch_data')
def fetch_data():
    """Securely fetch data from API."""
    try:
        api_url = f'{API_BASE_URL}/data'
        response = make_secure_request('GET', api_url)
        return response.json()
    except (ValueError, RuntimeError) as e:
        return {'error': str(e)}, 500

@app.route('/submit_payment')
def submit_payment():
    """Securely process payment data."""
    # Validate input
    card_number = request.form.get('card', '')
    amount = request.form.get('amount', '')
    
    if not card_number or not amount:
        return {'error': 'Missing payment information'}, 400
    
    # Basic validation
    if not amount.replace('.', '').isdigit():
        return {'error': 'Invalid amount'}, 400
    
    payment_data = {
        'card_number': card_number[:20],  # Limit length
        'amount': float(amount)
    }
    
    try:
        # Secure: HTTPS payment processing
        payment_url = f'{PAYMENT_SERVICE_URL}/process'
        response = make_secure_request(
            'POST',
            payment_url,
            json=payment_data,
            timeout=30  # Longer timeout for payment processing
        )
        return response.json()
    except (ValueError, RuntimeError) as e:
        return {'error': f'Payment processing failed: {str(e)}'}, 500

@app.route('/webhook')
def process_webhook():
    """Securely process webhook with URL validation."""
    webhook_data = request.get_json()
    
    if not webhook_data:
        return {'error': 'No webhook data provided'}, 400
    
    callback_url = webhook_data.get('callback_url', '')
    
    # Validate callback URL
    try:
        validate_https_url(callback_url)
    except ValueError as e:
        return {'error': f'Invalid callback URL: {str(e)}'}, 400
    
    # Allowlist of permitted callback domains
    allowed_domains = [
        'webhook.trusted.com',
        'callbacks.partner.com',
        'api.secure-client.com'
    ]
    
    parsed_url = urlparse(callback_url)
    if parsed_url.netloc not in allowed_domains:
        return {'error': 'Callback domain not allowed'}, 403
    
    try:
        # Secure webhook callback
        response = make_secure_request(
            'POST',
            callback_url,
            json={'status': 'processed', 'timestamp': '2024-01-01T00:00:00Z'},
            timeout=15
        )
        return {'status': 'callback_sent', 'response_code': response.status_code}
    except (ValueError, RuntimeError) as e:
        return {'error': f'Callback failed: {str(e)}'}, 500

# Secure service health checks
def check_service_health():
    """Perform secure health checks on internal services."""
    # Use HTTPS for all internal communications
    services = {
        'api': 'https://api.internal.secure/health',
        'database': 'https://db.internal.secure/status',
        'cache': 'https://cache.internal.secure/ping'
    }
    
    results = {}
    
    for service_name, service_url in services.items():
        try:
            # Secure internal communication
            response = make_secure_request(
                'GET',
                service_url,
                timeout=5,
                verify=True  # Verify internal certificates
            )
            results[service_name] = {
                'healthy': response.status_code == 200,
                'response_time': response.elapsed.total_seconds()
            }
        except (ValueError, RuntimeError) as e:
            results[service_name] = {
                'healthy': False,
                'error': str(e)
            }
    
    return results

@app.route('/health')
def health_check():
    """Endpoint for secure health checking."""
    try:
        health_results = check_service_health()
        overall_health = all(result['healthy'] for result in health_results.values())
        
        return {
            'status': 'healthy' if overall_health else 'unhealthy',
            'services': health_results
        }
    except Exception as e:
        return {'status': 'error', 'message': str(e)}, 500

# Additional security: Custom requests session
class SecureSession(requests.Session):
    """Custom session that enforces HTTPS."""
    
    def request(self, method, url, **kwargs):
        # Validate HTTPS before making request
        validate_https_url(url)
        
        # Set secure defaults
        kwargs.setdefault('verify', True)
        kwargs.setdefault('timeout', 10)
        
        return super().request(method, url, **kwargs)

# Usage example
secure_session = SecureSession()

@app.route('/secure_api_call')
def secure_api_call():
    """Example using secure session."""
    try:
        response = secure_session.get('https://api.example.com/secure-endpoint')
        return response.json()
    except (ValueError, requests.exceptions.RequestException) as e:
        return {'error': str(e)}, 500

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Code makes HTTP requests: requests.get('http://api.example.com'). Unencrypted HTTP transmits data in plain text. API keys, credentials, personal data exposed to network eavesdropping. Man-in-the-middle attacks intercept or modify requests and responses. HTTP provides no confidentiality or integrity protection.

Root causes

Using requests.get/post/put with HTTP URLs

Code makes HTTP requests: requests.get('http://api.example.com'). Unencrypted HTTP transmits data in plain text. API keys, credentials, personal data exposed to network eavesdropping. Man-in-the-middle attacks intercept or modify requests and responses. HTTP provides no confidentiality or integrity protection.

URLs from Configuration or Environment Using HTTP Protocol

Reading URLs from config: url = os.environ['API_URL']; requests.get(url). Configuration contains http:// URLs. Environment variables not validated. Legacy configurations or migration oversights leave HTTP URLs. External URL sources require validation, can't assume HTTPS.

Not Validating URL Schemes Before Making Requests

Requests without protocol verification: requests.post(endpoint, json=data). Endpoint from user input, database, or external service. No check ensuring HTTPS. Trust in URL source without validation. Missing scheme validation allows insecure HTTP requests through application.

Using HTTP for Webhook or Callback URLs

Webhook endpoints with HTTP: requests.post('http://webhook.example.com', json=event). Callbacks to third-party services over HTTP. Sensitive event data or authentication tokens sent unencrypted. Webhooks transmit application data to external services, require HTTPS for confidentiality.

Development or Testing Code with HTTP URLs in Production

Debug code using HTTP: if os.environ.get('ENV') == 'dev': url = 'http://localhost'; requests.get(url). Development defaults left in production code. Environment variable failures activate HTTP paths. HTTP should never exist in production code paths regardless of configuration.

Fixes

1

Always Use HTTPS URLs for All requests Library Calls

Use HTTPS exclusively: requests.get('https://api.example.com'). Replace all http:// with https://. For external APIs and webhooks, HTTPS mandatory. Internal services should also use HTTPS. requests library supports HTTPS by default with proper certificate validation.

2

Validate URL Schemes Before Making Any Request

Check protocol before requests: from urllib.parse import urlparse; parsed = urlparse(url); if parsed.scheme != 'https': raise ValueError('HTTPS required'). Validate URLs from all sources: config, environment, user input, databases. Reject non-HTTPS before making requests.

3

Configure Default HTTPS URLs in Environment and Config

Use HTTPS defaults: API_URL = os.environ.get('API_URL', 'https://api.example.com'). Validate on application startup: assert all(u.startswith('https://') for u in [URL1, URL2]). Schema validation for config files. Environment variables with HTTPS prefixes and validation checking.

4

Enable Certificate Verification, Never Disable with verify=False

Always verify certificates: requests.get(url, verify=True). Never use verify=False which disables SSL validation. For self-signed certs, provide cert path: verify='/path/to/cert.pem'. Use certifi for updated CA bundle. Proper certificate validation essential for HTTPS security.

5

Use Static Analysis to Detect HTTP URLs in Code

Scan for insecure URLs: grep -r 'http://' --include="*.py" --exclude-dir=tests. Use bandit security scanner: bandit -r . to detect HTTP usage. Add pre-commit hooks rejecting HTTP URLs. CI/CD failures on HTTP detection. Automated scanning prevents HTTP introduction.

6

Implement Request Interceptors for Protocol Enforcement

Create wrapper validating URLs: def secure_get(url, **kwargs): if not url.startswith('https://'): raise ValueError('HTTPS only'); return requests.get(url, **kwargs). Use throughout codebase. Centralized enforcement prevents scattered HTTP usage. Wrapper ensures consistent HTTPS requirement.

Detect This Vulnerability in Your Code

Sourcery automatically identifies python requests http connection vulnerability and many other security issues in your codebase.