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)
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