Download of Code Without Integrity Check

Description

Download of Code Without Integrity Check occurs when software downloads executable code or data from a remote source without verifying that it has not been modified. This includes downloading updates, plugins, scripts, or any executable content without cryptographic verification. Attackers who can intercept the download through man-in-the-middle attacks or by compromising the download server can inject malicious code that will be executed with the privileges of the downloading application.

Risk

Without integrity verification, any downloaded code is potentially malicious. The SolarWinds supply chain attack demonstrated how compromised update mechanisms can affect thousands of organizations. Auto-update features are particularly dangerous targets because they often run with elevated privileges. Even HTTPS is insufficient protection—the download server itself could be compromised. This vulnerability enables silent malware distribution at massive scale through trusted channels.

Solution

Always verify cryptographic signatures on downloaded code before execution. Use public key cryptography where the signing key is embedded in the application. Implement certificate pinning for download servers. Use Subresource Integrity (SRI) for web resources. Verify checksums from a separate channel. Use secure update frameworks (TUF, app stores with code signing). Never execute downloaded code directly—verify first. Implement rollback mechanisms for failed integrity checks.

Common Consequences

ImpactDetails
IntegrityScope: Code Execution

Malicious code can be injected and executed with application privileges.
ConfidentialityScope: Data Theft

Compromised code can steal credentials, keys, and sensitive data.
AvailabilityScope: System Compromise

Attackers gain persistent access through malicious updates.

Example Code + Solution Code

Vulnerable Code

# VULNERABLE: Downloading and executing without verification
import urllib.request
import subprocess

def update_application_vulnerable():
    # Downloads update without any verification
    update_url = "http://example.com/update.exe"

    # Using HTTP - easily intercepted!
    urllib.request.urlretrieve(update_url, "/tmp/update.exe")

    # Executing unverified code!
    subprocess.run(["/tmp/update.exe"])

# VULNERABLE: Plugin system without integrity check
def load_plugin_vulnerable(plugin_url):
    import requests

    # Download plugin
    response = requests.get(plugin_url)
    plugin_code = response.text

    # Execute without verification!
    exec(plugin_code)

# VULNERABLE: Configuration download
def load_remote_config_vulnerable():
    import json
    import urllib.request

    config_url = "http://example.com/config.json"
    response = urllib.request.urlopen(config_url)
    config = json.loads(response.read())

    # If config contains code to execute...
    if 'init_script' in config:
        exec(config['init_script'])  # Dangerous!
// VULNERABLE: Java auto-updater without verification
public class VulnerableUpdater {

    public void checkForUpdates() throws Exception {
        URL updateUrl = new URL("http://example.com/update.jar");

        // Download without verification
        try (InputStream in = updateUrl.openStream();
             FileOutputStream out = new FileOutputStream("update.jar")) {

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }

        // Execute unverified JAR!
        Runtime.getRuntime().exec("java -jar update.jar");
    }

    // VULNERABLE: Dynamic class loading
    public void loadRemoteClass(String classUrl) throws Exception {
        URL url = new URL(classUrl);
        URLClassLoader loader = new URLClassLoader(new URL[] { url });

        // Loading and instantiating unverified code!
        Class<?> clazz = loader.loadClass("com.example.Plugin");
        Object instance = clazz.getDeclaredConstructor().newInstance();
    }
}
// VULNERABLE: Node.js dynamic module loading
const https = require('https');
const fs = require('fs');

async function downloadPluginVulnerable(pluginUrl) {
    return new Promise((resolve, reject) => {
        const file = fs.createWriteStream('/tmp/plugin.js');

        // Download without verification
        https.get(pluginUrl, (response) => {
            response.pipe(file);
            file.on('finish', () => {
                file.close();

                // Execute unverified code!
                const plugin = require('/tmp/plugin.js');
                resolve(plugin);
            });
        });
    });
}

// VULNERABLE: eval of downloaded code
async function executeRemoteScript(scriptUrl) {
    const response = await fetch(scriptUrl);
    const script = await response.text();

    // Direct execution of downloaded code!
    eval(script);
}

Fixed Code

# SAFE: Cryptographic signature verification
import urllib.request
import subprocess
import hashlib
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature

# Embedded public key for verification
PUBLIC_KEY_PEM = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
"""

def verify_signature(data, signature, public_key_pem):
    """Verify RSA signature of data."""
    from cryptography.hazmat.backends import default_backend

    public_key = serialization.load_pem_public_key(
        public_key_pem.encode(),
        backend=default_backend()
    )

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

def update_application_safe():
    import requests

    # Download from HTTPS
    update_url = "https://secure.example.com/update.exe"
    signature_url = "https://secure.example.com/update.exe.sig"

    # Download update file
    response = requests.get(update_url)
    update_data = response.content

    # Download signature
    sig_response = requests.get(signature_url)
    signature = sig_response.content

    # Verify signature
    if not verify_signature(update_data, signature, PUBLIC_KEY_PEM):
        raise ValueError("Update signature verification failed!")

    # Safe to write and execute
    update_path = "/tmp/update.exe"
    with open(update_path, 'wb') as f:
        f.write(update_data)

    subprocess.run([update_path])

# SAFE: Plugin system with integrity verification
class SecurePluginLoader:
    KNOWN_PLUGINS = {
        'analytics': {
            'url': 'https://plugins.example.com/analytics.py',
            'sha256': 'a1b2c3d4e5f6...',
            'signature_url': 'https://plugins.example.com/analytics.py.sig'
        }
    }

    def __init__(self, public_key_pem):
        self.public_key_pem = public_key_pem

    def load_plugin(self, plugin_name):
        import requests

        if plugin_name not in self.KNOWN_PLUGINS:
            raise ValueError(f"Unknown plugin: {plugin_name}")

        plugin_info = self.KNOWN_PLUGINS[plugin_name]

        # Download plugin
        response = requests.get(plugin_info['url'])
        plugin_code = response.content

        # Verify hash
        calculated_hash = hashlib.sha256(plugin_code).hexdigest()
        if calculated_hash != plugin_info['sha256']:
            raise ValueError("Plugin hash mismatch!")

        # Verify signature
        sig_response = requests.get(plugin_info['signature_url'])
        if not verify_signature(plugin_code, sig_response.content, self.public_key_pem):
            raise ValueError("Plugin signature invalid!")

        # Safe to execute
        exec(plugin_code.decode(), {'__name__': plugin_name})
// SAFE: Java updater with code signing verification
import java.security.*;
import java.security.cert.*;
import java.io.*;
import java.net.*;
import java.util.jar.*;

public class SecureUpdater {

    private final PublicKey trustedPublicKey;

    public SecureUpdater(PublicKey publicKey) {
        this.trustedPublicKey = publicKey;
    }

    public void downloadAndVerifyUpdate(String updateUrl, String signatureUrl)
            throws Exception {

        // Download update
        byte[] updateData = downloadFile(updateUrl);
        byte[] signature = downloadFile(signatureUrl);

        // Verify signature
        if (!verifySignature(updateData, signature)) {
            throw new SecurityException("Update signature verification failed!");
        }

        // Verify JAR signature if it's a JAR file
        File tempFile = File.createTempFile("update", ".jar");
        try (FileOutputStream fos = new FileOutputStream(tempFile)) {
            fos.write(updateData);
        }

        if (!verifyJarSignature(tempFile)) {
            tempFile.delete();
            throw new SecurityException("JAR signature verification failed!");
        }

        // Safe to proceed with update
        processUpdate(tempFile);
    }

    private boolean verifySignature(byte[] data, byte[] signature) throws Exception {
        Signature sig = Signature.getInstance("SHA256withRSA");
        sig.initVerify(trustedPublicKey);
        sig.update(data);
        return sig.verify(signature);
    }

    private boolean verifyJarSignature(File jarFile) throws Exception {
        try (JarFile jar = new JarFile(jarFile, true)) {
            // Verify all entries
            byte[] buffer = new byte[8192];

            for (JarEntry entry : jar.stream().toList()) {
                if (entry.isDirectory()) continue;

                try (InputStream is = jar.getInputStream(entry)) {
                    while (is.read(buffer) != -1) {
                        // Reading triggers verification
                    }
                }

                // Check code signers
                CodeSigner[] signers = entry.getCodeSigners();
                if (signers == null || signers.length == 0) {
                    if (!entry.getName().startsWith("META-INF/")) {
                        return false;  // Unsigned entry
                    }
                }

                // Verify against trusted key
                for (CodeSigner signer : signers) {
                    List<? extends Certificate> certs =
                        signer.getSignerCertPath().getCertificates();
                    if (!certs.isEmpty()) {
                        PublicKey signerKey = certs.get(0).getPublicKey();
                        if (signerKey.equals(trustedPublicKey)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private byte[] downloadFile(String url) throws Exception {
        HttpsURLConnection connection =
            (HttpsURLConnection) new URL(url).openConnection();

        // Certificate pinning
        connection.setHostnameVerifier((hostname, session) -> {
            // Verify hostname against expected
            return "secure.example.com".equals(hostname);
        });

        try (InputStream is = connection.getInputStream();
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesRead);
            }
            return baos.toByteArray();
        }
    }

    private void processUpdate(File updateFile) {
        // Apply update after verification
    }
}
// SAFE: Node.js with integrity verification
const https = require('https');
const crypto = require('crypto');
const fs = require('fs');

// Embedded public key
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`;

class SecureDownloader {

    constructor(publicKey) {
        this.publicKey = publicKey;
    }

    async downloadAndVerify(url, signatureUrl, expectedHash) {
        // Download file
        const data = await this.download(url);

        // Verify hash
        const hash = crypto.createHash('sha256').update(data).digest('hex');
        if (hash !== expectedHash) {
            throw new Error('Hash verification failed');
        }

        // Download and verify signature
        const signature = await this.download(signatureUrl);
        if (!this.verifySignature(data, signature)) {
            throw new Error('Signature verification failed');
        }

        return data;
    }

    download(url) {
        return new Promise((resolve, reject) => {
            https.get(url, {
                // Certificate pinning
                checkServerIdentity: (hostname, cert) => {
                    const expectedFingerprint = 'AB:CD:EF:...';
                    if (cert.fingerprint256 !== expectedFingerprint) {
                        return new Error('Certificate pinning failed');
                    }
                }
            }, (response) => {
                const chunks = [];
                response.on('data', chunk => chunks.push(chunk));
                response.on('end', () => resolve(Buffer.concat(chunks)));
                response.on('error', reject);
            }).on('error', reject);
        });
    }

    verifySignature(data, signature) {
        const verify = crypto.createVerify('RSA-SHA256');
        verify.update(data);
        return verify.verify(this.publicKey, signature);
    }
}

// SAFE: Plugin loader with SRI
async function loadPluginSecure(pluginName) {
    const PLUGIN_MANIFEST = {
        'analytics': {
            url: 'https://plugins.example.com/analytics.js',
            integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/...',
            signature: 'base64signature...'
        }
    };

    const plugin = PLUGIN_MANIFEST[pluginName];
    if (!plugin) {
        throw new Error('Unknown plugin');
    }

    const downloader = new SecureDownloader(PUBLIC_KEY);

    // Download with integrity verification
    const response = await fetch(plugin.url, {
        integrity: plugin.integrity  // Subresource Integrity
    });

    const code = await response.text();

    // Additional signature verification
    const codeBuffer = Buffer.from(code);
    const signatureBuffer = Buffer.from(plugin.signature, 'base64');

    const verify = crypto.createVerify('RSA-SHA256');
    verify.update(codeBuffer);

    if (!verify.verify(PUBLIC_KEY, signatureBuffer)) {
        throw new Error('Plugin signature invalid');
    }

    // Create sandboxed context for plugin
    const vm = require('vm');
    const sandbox = {
        console: console,
        // Limited API for plugins
    };

    vm.createContext(sandbox);
    vm.runInContext(code, sandbox);

    return sandbox;
}

Exploited in the Wild

SolarWinds Supply Chain Attack (2020)

Attackers compromised SolarWinds' build system and injected malicious code into Orion software updates. The updates were signed and distributed through legitimate channels, affecting 18,000+ organizations including US government agencies.

CCleaner Compromise (2017)

Attackers compromised CCleaner's build environment and distributed malware through the official update channel, affecting 2.27 million users before detection.

NotPetya via M.E.Doc (2017)

The NotPetya ransomware was distributed through a compromised update mechanism of Ukrainian accounting software M.E.Doc, causing billions in damages worldwide.


Tools to test/exploit


CVE Examples


References

  1. MITRE. "CWE-494: Download of Code Without Integrity Check." https://cwe.mitre.org/data/definitions/494.html

  2. NIST. "Software Supply Chain Security Guidance." https://www.nist.gov/itl/executive-order-improving-nations-cybersecurity