Skip to content

CWE-494: Download of Code Without Integrity Check

Overview

This vulnerability occurs when applications download code or executables from external sources without verifying their integrity, allowing attackers to inject malicious code into the application.

OWASP Classification

A08:2025 - Software or Data Integrity Failures

Risk

Critical: Attackers can tamper with downloaded code, leading to remote code execution, malware installation, or full system compromise.

Remediation Steps

Core principle: Only download/install code with integrity verification (signatures/hashes) from trusted sources over secure transport.

Locate Code Download Without Integrity Checks

When reviewing security scan results:

  • Examine data_paths: Identify where code/executables are downloaded from external sources
  • Find download operations: HTTP downloads, package installations, plugin loading, script fetching
  • Check for verification: Look for missing hash checks, signature validation, checksum verification
  • Identify sources: URLs, package repositories, CDNs, third-party sites
  • Assess risk: Is downloaded code executed, imported, or loaded as plugins

Common patterns:

  • Downloading and executing scripts: exec(requests.get(url).text)
  • Installing packages without verification
  • Loading plugins from untrusted sources
  • Fetching libraries without hash validation

Verify Integrity with Cryptographic Hashes (Primary Defense)

import requests
import hashlib

# Define expected hash (from trusted source)
EXPECTED_HASH = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'

def download_and_verify(url, expected_hash):
    # Download code
    response = requests.get(url, timeout=30)
    response.raise_for_status()
    code = response.content

    # Compute hash
    actual_hash = hashlib.sha256(code).hexdigest()

    # Verify integrity
    if actual_hash != expected_hash:
        raise ValueError(f"Integrity check failed! Expected {expected_hash}, got {actual_hash}")

    return code

# Use it
try:
    code = download_and_verify('https://example.com/plugin.py', EXPECTED_HASH)
    # Safe to execute now
    exec(code)
except ValueError as e:
    logger.error(f"Download verification failed: {e}")
    raise

Why this works: Cryptographic hashes create a unique fingerprint of the code. Any tampering changes the hash, making it detectable. Comparing against a known-good hash ensures code hasn't been modified.

Hash algorithms to use:

  • SHA-256 or higher (SHA-384, SHA-512)
  • SHA-3 family
  • Never use MD5 or SHA-1 (broken, collision attacks)

Use Cryptographic Signatures for Strong Verification

For higher security, verify digital signatures:

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature

def verify_signature(code, signature, public_key_pem):
    # Load public key
    public_key = serialization.load_pem_public_key(public_key_pem.encode())

    try:
        # Verify signature
        public_key.verify(
            signature,
            code,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return True
    except InvalidSignature:
        return False

# Download code and signature
code = requests.get('https://example.com/plugin.py').content
signature = requests.get('https://example.com/plugin.py.sig').content

# Verify before executing
if verify_signature(code, signature, TRUSTED_PUBLIC_KEY):
    exec(code)
else:
    raise ValueError("Signature verification failed")

Benefits of signatures:

  • Verifies authenticity (who created it)
  • Verifies integrity (not tampered)
  • Requires attacker to have private key (harder than hash collision)

Use Secure Channels and Restrict Sources

import requests
from urllib.parse import urlparse

# Allowlist of trusted sources
TRUSTED_SOURCES = [
    'cdn.example.com',
    'plugins.example.com',
    'github.com/yourorg'
]

def download_from_trusted_source(url, expected_hash):
    # Parse URL
    parsed = urlparse(url)

    # Verify HTTPS
    if parsed.scheme != 'https':
        raise ValueError("Must use HTTPS for code downloads")

    # Verify trusted source
    if parsed.hostname not in TRUSTED_SOURCES:
        raise ValueError(f"Untrusted source: {parsed.hostname}")

    # Download with SSL verification enabled
    response = requests.get(url, verify=True, timeout=30)
    response.raise_for_status()
    code = response.content

    # Verify hash
    actual_hash = hashlib.sha256(code).hexdigest()
    if actual_hash != expected_hash:
        raise ValueError("Hash mismatch")

    return code

Security controls:

  • Always use HTTPS (never HTTP for code downloads)
  • Verify SSL certificates (verify=True)
  • Allowlist allowed download sources
  • Set download timeouts
  • Validate URLs before downloading

Restrict Code Execution Permissions

import os
import stat
import tempfile

def save_downloaded_code_safely(code, expected_hash):
    # Verify integrity first
    actual_hash = hashlib.sha256(code).hexdigest()
    if actual_hash != expected_hash:
        raise ValueError("Integrity check failed")

    # Create temp file with restrictive permissions
    fd = os.open(
        '/opt/app/plugins/plugin.py',
        os.O_WRONLY | os.O_CREAT | os.O_EXCL,
        stat.S_IRUSR | stat.S_IWUSR  # 0600 - owner read/write only
    )

    os.write(fd, code)
    os.close(fd)

    # Make executable only if needed (and still restrict to owner)
    # os.chmod('/opt/app/plugins/plugin.py', stat.S_IRWXU)  # 0700

# Run with limited privileges
import subprocess
result = subprocess.run(
    ['python', '/opt/app/plugins/plugin.py'],
    user='limited_user',  # Not root!
    timeout=30,
    capture_output=True
)

Least privilege principles:

  • Save downloaded code with restrictive permissions (600/700)
  • Run in sandboxed environment
  • Use separate user account (not root)
  • Apply resource limits (CPU, memory, time)
  • Use containers for isolation

Monitor and Audit Code Downloads

Logging strategy:

import logging

logger = logging.getLogger(__name__)

def download_code_with_audit(url, expected_hash):
    logger.info(f"Downloading code from {url}")

    try:
        code = requests.get(url).content
        actual_hash = hashlib.sha256(code).hexdigest()

        if actual_hash == expected_hash:
            logger.info(f"Integrity verified for {url}: {actual_hash}")
            return code
        else:
            logger.error(f"INTEGRITY FAILURE: {url} - Expected {expected_hash}, got {actual_hash}")
            # Alert security team
            send_security_alert(f"Code integrity check failed for {url}")
            raise ValueError("Integrity check failed")
    except Exception as e:
        logger.error(f"Download failed for {url}: {e}")
        raise

Monitoring:

  • Log all code download attempts with URLs
  • Log integrity check results (pass/fail)
  • Alert on integrity check failures
  • Track download sources and frequencies
  • Monitor for downloads from unexpected sources

Testing:

  • Test with correct hash (should succeed)
  • Test with tampered code (should fail integrity check)
  • Test with wrong hash (should reject)
  • Test with HTTP instead of HTTPS (should reject)
  • Test with untrusted source (should reject)
  • Verify logging captures all downloads

Common Vulnerable Patterns

  • Downloading and executing code without verification
  • Using HTTP or insecure channels for code downloads

Unverified Remote Code Execution (Python)

# Downloads and executes code without verification
import requests
exec(requests.get(url).text)

Why this is vulnerable: Downloading and executing code without cryptographic integrity verification allows attackers to inject malicious code through man-in-the-middle attacks, compromised servers, or DNS hijacking, resulting in remote code execution with full application privileges and complete system compromise.

Secure Patterns

Cryptographic Hash Verification Before Execution (Python)

# Verifies code integrity before execution
import requests, hashlib
code = requests.get(url).text
expected_hash = 'sha256:abc123def456...'  # From trusted source
actual_hash = 'sha256:' + hashlib.sha256(code.encode()).hexdigest()
if actual_hash == expected_hash:
    exec(code)
else:
    raise Exception('Code integrity check failed - possible tampering')

Why this works:

  • Cryptographic hash (SHA-256) verification ensures downloaded code hasn't been tampered with
  • Prevents attackers from injecting malicious code through man-in-the-middle attacks or compromised servers
  • Blocks execution if code differs from expected hash, protecting against supply chain attacks
  • Expected hash should be obtained from trusted source (signed manifest, secure configuration)
  • Combined with HTTPS downloads, provides strong assurance of code authenticity and integrity

Additional Resources