Download von Code ohne Integritätsprüfung
Beschreibung
Download von Code ohne Integritätsprüfung tritt auf, wenn Software ausführbaren Code oder Daten von einer entfernten Quelle herunterlädt, ohne zu verifizieren, dass er nicht modifiziert wurde. Dies umfasst das Herunterladen von Updates, Plugins, Skripten oder jeglichem ausführbaren Inhalt ohne kryptographische Verifizierung. Angreifer, die den Download durch Man-in-the-Middle-Angriffe abfangen oder den Download-Server kompromittieren können, können bösartigen Code einschleusen, der mit den Privilegien der herunterladenden Anwendung ausgeführt wird.
Risiko
Ohne Integritätsverifizierung ist jeder heruntergeladene Code potenziell bösartig. Der SolarWinds-Supply-Chain-Angriff hat demonstriert, wie kompromittierte Update-Mechanismen Tausende von Organisationen betreffen können. Auto-Update-Funktionen sind besonders gefährliche Ziele, weil sie oft mit erhöhten Privilegien ausgeführt werden. Selbst HTTPS ist kein ausreichender Schutz — der Download-Server selbst könnte kompromittiert sein. Diese Schwachstelle ermöglicht lautlose Malware-Verteilung in massivem Umfang über vertrauenswürdige Kanäle.
Lösung
Verifizieren Sie immer kryptographische Signaturen auf heruntergeladenem Code vor der Ausführung. Verwenden Sie Public-Key-Kryptographie, bei der der Signaturschlüssel in der Anwendung eingebettet ist. Implementieren Sie Zertifikat-Pinning für Download-Server. Verwenden Sie Subresource Integrity (SRI) für Web-Ressourcen. Verifizieren Sie Prüfsummen über einen separaten Kanal. Verwenden Sie sichere Update-Frameworks (TUF, App-Stores mit Code-Signierung). Führen Sie niemals heruntergeladenen Code direkt aus — verifizieren Sie zuerst. Implementieren Sie Rollback-Mechanismen für fehlgeschlagene Integritätsprüfungen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Umfang: Codeausführung Bösartiger Code kann eingeschleust und mit Anwendungsprivilegien ausgeführt werden. |
| Vertraulichkeit | Umfang: Datendiebstahl Kompromittierter Code kann Anmeldedaten, Schlüssel und sensible Daten stehlen. |
| Verfügbarkeit | Umfang: Systemkompromittierung Angreifer erlangen persistenten Zugang durch bösartige Updates. |
Beispielcode
Anfälliger Code
# ANFÄLLIG: Download und Ausführung ohne Verifizierung
import urllib.request
import subprocess
def update_application_vulnerable():
# Lädt Update ohne jegliche Verifizierung herunter
update_url = "http://example.com/update.exe"
# Verwendet HTTP - leicht abfangbar!
urllib.request.urlretrieve(update_url, "/tmp/update.exe")
# Führt unverifizierter Code aus!
subprocess.run(["/tmp/update.exe"])
# ANFÄLLIG: Plugin-System ohne Integritätsprüfung
def load_plugin_vulnerable(plugin_url):
import requests
# Plugin herunterladen
response = requests.get(plugin_url)
plugin_code = response.text
# Ohne Verifizierung ausführen!
exec(plugin_code)
# ANFÄLLIG: Konfigurations-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())
# Wenn config auszuführenden Code enthält...
if 'init_script' in config:
exec(config['init_script']) # Gefährlich!
// ANFÄLLIG: Java Auto-Updater ohne Verifizierung
public class VulnerableUpdater {
public void checkForUpdates() throws Exception {
URL updateUrl = new URL("http://example.com/update.jar");
// Download ohne Verifizierung
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);
}
}
// Unverifiziertes JAR ausführen!
Runtime.getRuntime().exec("java -jar update.jar");
}
// ANFÄLLIG: Dynamisches Laden von Klassen
public void loadRemoteClass(String classUrl) throws Exception {
URL url = new URL(classUrl);
URLClassLoader loader = new URLClassLoader(new URL[] { url });
// Laden und Instanziieren von unverifiziertem Code!
Class<?> clazz = loader.loadClass("com.example.Plugin");
Object instance = clazz.getDeclaredConstructor().newInstance();
}
}
// ANFÄLLIG: Node.js dynamisches Modulladen
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 ohne Verifizierung
https.get(pluginUrl, (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
// Unverifizierter Code ausführen!
const plugin = require('/tmp/plugin.js');
resolve(plugin);
});
});
});
}
// ANFÄLLIG: eval von heruntergeladenem Code
async function executeRemoteScript(scriptUrl) {
const response = await fetch(scriptUrl);
const script = await response.text();
// Direkte Ausführung von heruntergeladenem Code!
eval(script);
}
Korrigierter Code
# SICHER: Kryptographische Signaturverifizierung
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
# Eingebetteter öffentlicher Schlüssel zur Verifizierung
PUBLIC_KEY_PEM = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
"""
def verify_signature(data, signature, public_key_pem):
"""RSA-Signatur von Daten verifizieren."""
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 von HTTPS
update_url = "https://secure.example.com/update.exe"
signature_url = "https://secure.example.com/update.exe.sig"
# Update-Datei herunterladen
response = requests.get(update_url)
update_data = response.content
# Signatur herunterladen
sig_response = requests.get(signature_url)
signature = sig_response.content
# Signatur verifizieren
if not verify_signature(update_data, signature, PUBLIC_KEY_PEM):
raise ValueError("Update-Signaturverifizierung fehlgeschlagen!")
# Sicher zu schreiben und auszuführen
update_path = "/tmp/update.exe"
with open(update_path, 'wb') as f:
f.write(update_data)
subprocess.run([update_path])
# SICHER: Plugin-System mit Integritätsverifizierung
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"Unbekanntes Plugin: {plugin_name}")
plugin_info = self.KNOWN_PLUGINS[plugin_name]
# Plugin herunterladen
response = requests.get(plugin_info['url'])
plugin_code = response.content
# Hash verifizieren
calculated_hash = hashlib.sha256(plugin_code).hexdigest()
if calculated_hash != plugin_info['sha256']:
raise ValueError("Plugin-Hash stimmt nicht überein!")
# Signatur verifizieren
sig_response = requests.get(plugin_info['signature_url'])
if not verify_signature(plugin_code, sig_response.content, self.public_key_pem):
raise ValueError("Plugin-Signatur ungültig!")
# Sicher auszuführen
exec(plugin_code.decode(), {'__name__': plugin_name})
// SICHER: Java-Updater mit Code-Signatur-Verifizierung
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 {
// Update herunterladen
byte[] updateData = downloadFile(updateUrl);
byte[] signature = downloadFile(signatureUrl);
// Signatur verifizieren
if (!verifySignature(updateData, signature)) {
throw new SecurityException("Update-Signaturverifizierung fehlgeschlagen!");
}
// JAR-Signatur verifizieren wenn es eine JAR-Datei ist
File tempFile = File.createTempFile("update", ".jar");
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
fos.write(updateData);
}
if (!verifyJarSignature(tempFile)) {
tempFile.delete();
throw new SecurityException("JAR-Signaturverifizierung fehlgeschlagen!");
}
// Sicher mit Update fortzufahren
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)) {
// Alle Einträge verifizieren
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) {
// Lesen löst Verifizierung aus
}
}
// Code-Signierer prüfen
CodeSigner[] signers = entry.getCodeSigners();
if (signers == null || signers.length == 0) {
if (!entry.getName().startsWith("META-INF/")) {
return false; // Unsignierter Eintrag
}
}
// Gegen vertrauenswürdigen Schlüssel verifizieren
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();
// Zertifikat-Pinning
connection.setHostnameVerifier((hostname, session) -> {
// Hostnamen gegen erwarteten verifizieren
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) {
// Update nach Verifizierung anwenden
}
}
// SICHER: Node.js mit Integritätsverifizierung
const https = require('https');
const crypto = require('crypto');
const fs = require('fs');
// Eingebetteter öffentlicher Schlüssel
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`;
class SecureDownloader {
constructor(publicKey) {
this.publicKey = publicKey;
}
async downloadAndVerify(url, signatureUrl, expectedHash) {
// Datei herunterladen
const data = await this.download(url);
// Hash verifizieren
const hash = crypto.createHash('sha256').update(data).digest('hex');
if (hash !== expectedHash) {
throw new Error('Hash-Verifizierung fehlgeschlagen');
}
// Signatur herunterladen und verifizieren
const signature = await this.download(signatureUrl);
if (!this.verifySignature(data, signature)) {
throw new Error('Signatur-Verifizierung fehlgeschlagen');
}
return data;
}
download(url) {
return new Promise((resolve, reject) => {
https.get(url, {
// Zertifikat-Pinning
checkServerIdentity: (hostname, cert) => {
const expectedFingerprint = 'AB:CD:EF:...';
if (cert.fingerprint256 !== expectedFingerprint) {
return new Error('Zertifikat-Pinning fehlgeschlagen');
}
}
}, (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);
}
}
// SICHER: Plugin-Loader mit SRI
async function loadPluginSecure(pluginName) {
const PLUGIN_MANIFEST = {
'analytics': {
url: 'https://plugins.example.com/analytics.js',
integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/...',
signature: 'base64signatur...'
}
};
const plugin = PLUGIN_MANIFEST[pluginName];
if (!plugin) {
throw new Error('Unbekanntes Plugin');
}
const downloader = new SecureDownloader(PUBLIC_KEY);
// Download mit Integritätsverifizierung
const response = await fetch(plugin.url, {
integrity: plugin.integrity // Subresource Integrity
});
const code = await response.text();
// Zusätzliche Signaturverifizierung
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-Signatur ungültig');
}
// Sandboxed Kontext für Plugin erstellen
const vm = require('vm');
const sandbox = {
console: console,
// Begrenzte API für Plugins
};
vm.createContext(sandbox);
vm.runInContext(code, sandbox);
return sandbox;
}
Ausgenutzt in der Praxis
SolarWinds Supply-Chain-Angriff (2020)
Angreifer kompromittierten das Build-System von SolarWinds und schleusten bösartigen Code in Orion-Software-Updates ein. Die Updates wurden signiert und über legitime Kanäle verteilt, was 18.000+ Organisationen einschließlich US-Regierungsbehörden betraf.
CCleaner-Kompromittierung (2017)
Angreifer kompromittierten die Build-Umgebung von CCleaner und verteilten Malware über den offiziellen Update-Kanal, was 2,27 Millionen Benutzer vor der Erkennung betraf.
NotPetya via M.E.Doc (2017)
Die NotPetya-Ransomware wurde über einen kompromittierten Update-Mechanismus der ukrainischen Buchhaltungssoftware M.E.Doc verbreitet und verursachte weltweit Milliardenschäden.
CVE-Beispiele
-
CVE-2020-10148 — SolarWinds Orion Authentifizierungsumgehung.
-
CVE-2017-5638 — Apache Struts (häufig bei Auto-Update-Exploits).
-
CVE-2019-3462 — APT-Paketmanager-Schwachstelle.
Referenzen
-
MITRE Corporation. "CWE-494: Download of Code Without Integrity Check." https://cwe.mitre.org/data/definitions/494.html
-
NIST. "Software Supply Chain Security Guidance." https://www.nist.gov/itl/executive-order-improving-nations-cybersecurity