Aktiver Debug-Code
Beschreibung
Aktiver Debug-Code tritt auf, wenn Software mit noch aktiviertem oder zugänglichem Debugging-Code bereitgestellt wird. Dies umfasst Debug-Endpunkte, ausführliche Fehlermeldungen, Entwicklungs-Hintertüren, Testkonten, Debug-Flags und Diagnosefunktionen, die nur für die Entwicklung gedacht waren. Debug-Code umgeht oft Sicherheitskontrollen, legt sensible Informationen offen oder bietet unbefugte Zugriffspfade, die Angreifer in Produktionsumgebungen ausnutzen können.
Risiko
Debug-Code in der Produktion ist ein erhebliches Sicherheitsrisiko. Debug-Endpunkte können Authentifizierungsumgehung, Remote-Codeausführung oder direkten Datenbankzugriff ermöglichen. Ausführliche Fehlermeldungen legen Systeminterna, Stack-Traces und sensible Konfiguration offen. Testkonten mit bekannten Anmeldedaten bieten einfachen Zugang. Debug-Flags können Sicherheitsfunktionen deaktivieren. Viele große Sicherheitsverletzungen sind von vergessenem Debug-Code ausgegangen, einschließlich Test-APIs und Entwicklungs-Hintertüren, die in Produktionsversionen verblieben.
Lösung
Implementieren Sie strikte Bereitstellungsprozesse, die Debug-Funktionen automatisch deaktivieren. Verwenden Sie umgebungsspezifische Konfigurationen, die Debugging nur in der Entwicklung aktivieren. Entfernen Sie allen Debug-Code vor der Veröffentlichung durch Code-Review und automatisches Scannen. Stellen Sie niemals mit Testkonten oder bekannten Anmeldedaten bereit. Verwenden Sie Feature-Flags, die serverseitig gesteuert werden. Implementieren Sie Vor-Bereitstellungs-Prüfungen, die fehlschlagen, wenn Debug-Code erkannt wird. Führen Sie regelmäßige Sicherheitsaudits durch, um verbleibende Debug-Funktionalität zu identifizieren.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Informationsoffenlegung Debug-Ausgabe legt interne Systeminformationen, Anmeldedaten und Geschäftslogik offen. |
| Zugriffskontrolle | Umfang: Authentifizierungsumgehung Debug-Hintertüren und Testkonten bieten unbefugten Zugang. |
| Integrität | Umfang: Systemmanipulation Debug-Funktionalität kann direkte Datenmodifikation oder Codeausführung ermöglichen. |
Beispielcode
Anfälliger Code
# ANFÄLLIG: Debug-Modus in Produktion aktiviert
from flask import Flask
app = Flask(__name__)
app.debug = True # Legt Stack-Traces offen, aktiviert Debugger
# ANFÄLLIG: Debug-Endpunkt im Code verblieben
@app.route('/debug/users')
def debug_users():
# Keine Authentifizierung - listet alle Benutzer!
return jsonify([u.to_dict() for u in User.query.all()])
@app.route('/debug/sql')
def debug_sql():
# Direkte SQL-Ausführung!
query = request.args.get('q')
result = db.engine.execute(query)
return jsonify([dict(row) for row in result])
@app.route('/debug/config')
def debug_config():
# Legt gesamte Konfiguration einschließlich Geheimnisse offen!
return jsonify(dict(app.config))
# ANFÄLLIG: Testkonto im Code
TEST_USERS = {
'admin': 'admin123', # Hartcodierte Test-Anmeldedaten!
'test': 'test123',
'debug': 'debug'
}
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
# Testkonten zuerst prüfen (umgeht normale Auth!)
if username in TEST_USERS and TEST_USERS[username] == password:
return create_session(username, is_admin=True)
return normal_authentication(username, password)
# ANFÄLLIG: Debug-Flag deaktiviert Sicherheit
DEBUG_MODE = True # Vergessen zu ändern!
@app.before_request
def check_auth():
if DEBUG_MODE:
return # Überspringt alle Authentifizierung im Debug-Modus!
if not is_authenticated():
abort(401)
// ANFÄLLIG: Java Debug-Code
@RestController
public class VulnerableController {
// Debug-Modus-Flag
private static final boolean DEBUG = true; // Aktiviert gelassen!
@GetMapping("/api/data")
public ResponseEntity<?> getData(HttpServletRequest request) {
if (DEBUG) {
// Debug-Logging legt sensible Informationen offen
System.out.println("Headers: " + getHeaders(request));
System.out.println("Session: " + request.getSession().getId());
System.out.println("Benutzer: " + request.getUserPrincipal());
}
// ... Rest der Methode
}
// ANFÄLLIG: Debug-Endpunkt
@GetMapping("/debug/heap")
public String heapDump() {
// Legt Speicherinhalte offen!
Runtime runtime = Runtime.getRuntime();
return String.format("Speicher: %d/%d", runtime.freeMemory(), runtime.totalMemory());
}
// ANFÄLLIG: Test-Hintertür
@GetMapping("/debug/login")
public ResponseEntity<?> debugLogin(@RequestParam String user) {
// Sofortige Anmeldung als beliebiger Benutzer!
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(new UsernamePasswordAuthenticationToken(user, null, getAdminAuthorities()));
return ResponseEntity.ok("Angemeldet als " + user);
}
}
// ANFÄLLIG: Spring-Profil nicht ordnungsgemäß eingeschränkt
@Profile("!prod") // Sollte in prod deaktiviert sein, aber Profil könnte nicht gesetzt sein!
@RestController
@RequestMapping("/debug")
public class DebugController {
@GetMapping("/env")
public Map<String, String> getEnv() {
return System.getenv(); // Alle Umgebungsvariablen!
}
}
// ANFÄLLIG: Node.js Debug-Code
const express = require('express');
const app = express();
// Debug-Modus
const DEBUG = true; // Sollte in Produktion false sein!
// ANFÄLLIG: Debug-Middleware
app.use((req, res, next) => {
if (DEBUG) {
console.log('Anfrage:', {
headers: req.headers, // Enthält Auth-Tokens!
body: req.body, // Enthält Passwörter!
cookies: req.cookies
});
}
next();
});
// ANFÄLLIG: Debug-Routen
app.get('/debug/users', (req, res) => {
// Keine Auth-Prüfung!
User.find({}).then(users => res.json(users));
});
app.get('/debug/eval', (req, res) => {
// Remote-Codeausführung!
const code = req.query.code;
try {
const result = eval(code); // Extrem gefährlich!
res.json({ result });
} catch (e) {
res.json({ error: e.message });
}
});
app.post('/debug/db', (req, res) => {
// Direkter Datenbankzugriff!
const { collection, query } = req.body;
db.collection(collection).find(query).toArray()
.then(results => res.json(results));
});
// ANFÄLLIG: Test-Anmeldedaten
const TEST_ACCOUNTS = {
'testadmin': { password: 'test123', role: 'admin' },
'testuser': { password: 'user123', role: 'user' }
};
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Testkonten zuerst prüfen!
if (TEST_ACCOUNTS[username]?.password === password) {
return res.json({
token: generateToken(username, TEST_ACCOUNTS[username].role)
});
}
// Normale Authentifizierung...
});
Korrigierter Code
# SICHER: Umgebungsbasierte Konfiguration
import os
from flask import Flask
app = Flask(__name__)
# Debug nur in Entwicklung, niemals in Produktion
app.debug = os.environ.get('FLASK_ENV') == 'development'
# Konfiguration basierend auf Umgebung
class Config:
DEBUG = False
TESTING = False
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': ProductionConfig
}
env = os.environ.get('FLASK_ENV', 'production')
app.config.from_object(config.get(env, config['default']))
# SICHER: Debug-Endpunkte nur in Entwicklung
if app.config['DEBUG'] and os.environ.get('FLASK_ENV') == 'development':
@app.route('/debug/users')
def debug_users():
# Nur in Entwicklung zugänglich!
return jsonify([u.to_dict() for u in User.query.all()])
# SICHER: Keine Testkonten im Code
# Testkonten sollten in separaten Test-Fixtures sein, nicht Anwendungscode
# SICHER: Debug-Code vor Bereitstellung entfernen
# Pre-commit-Hooks oder CI-Prüfungen verwenden
# pre-commit Hook Beispiel:
# grep -r "DEBUG.*=.*True" --include="*.py" && exit 1 || exit 0
# SICHER: Feature-Flags serverseitig gesteuert
class FeatureFlags:
def __init__(self):
self._flags = {}
self._load_from_database()
def _load_from_database(self):
# Flags aus sicherer Datenbank laden, nicht Code
flags = FeatureFlag.query.all()
self._flags = {f.name: f.enabled for f in flags}
def is_enabled(self, flag_name):
return self._flags.get(flag_name, False)
feature_flags = FeatureFlags()
@app.before_request
def check_auth():
# Authentifizierung niemals basierend auf Debug-Flags überspringen
if not is_authenticated() and request.endpoint not in PUBLIC_ENDPOINTS:
abort(401)
# SICHER: Logging ohne sensible Daten
import logging
class SanitizingFilter(logging.Filter):
SENSITIVE_FIELDS = {'password', 'token', 'secret', 'api_key', 'authorization'}
def filter(self, record):
if hasattr(record, 'msg'):
record.msg = self._sanitize(record.msg)
return True
def _sanitize(self, msg):
# Sensible Daten aus Logs entfernen
if isinstance(msg, dict):
return {k: '***' if k.lower() in self.SENSITIVE_FIELDS else v
for k, v in msg.items()}
return msg
logger = logging.getLogger(__name__)
logger.addFilter(SanitizingFilter())
// SICHER: Java ohne Debug-Code in Produktion
@RestController
public class SecureController {
private static final Logger logger = LoggerFactory.getLogger(SecureController.class);
// Spring-Profile ordnungsgemäß verwenden
@Value("${app.debug.enabled:false}")
private boolean debugEnabled;
@GetMapping("/api/data")
public ResponseEntity<?> getData(HttpServletRequest request) {
// Sicheres Logging ohne sensible Daten
logger.info("Anfrage an /api/data von {}", request.getRemoteAddr());
// Anfrage verarbeiten...
return ResponseEntity.ok(data);
}
}
// SICHER: Debug-Controller nur mit dev-Profil aktiv
@Profile("dev") // Nur aktiv wenn explizit mit dev-Profil ausgeführt
@RestController
@RequestMapping("/debug")
@ConditionalOnProperty(name = "app.debug.endpoints.enabled", havingValue = "true")
public class DebugController {
// Selbst in dev Authentifizierung erfordern
@PreAuthorize("hasRole('DEVELOPER')")
@GetMapping("/info")
public Map<String, Object> getDebugInfo() {
// Nur nicht-sensible Debug-Informationen zurückgeben
return Map.of(
"timestamp", Instant.now(),
"javaVersion", System.getProperty("java.version"),
"uptime", ManagementFactory.getRuntimeMXBean().getUptime()
);
}
}
// SICHER: Produktionskonfiguration
@Configuration
@Profile("prod")
public class ProductionSecurityConfig {
@PostConstruct
public void verifyNoDebugCode() {
// Schnell fehlschlagen wenn Debug-Endpunkte versehentlich aktiviert
if (Boolean.getBoolean("app.debug.endpoints.enabled")) {
throw new IllegalStateException("Debug-Endpunkte können in Produktion nicht aktiviert sein!");
}
}
}
// SICHER: CI/CD-Prüfung für Debug-Code
// In build.gradle oder pom.xml eine Prüfung hinzufügen:
/*
task checkDebugCode {
doLast {
def debugPatterns = [
~/DEBUG\s*=\s*true/,
~/\.debug\s*=\s*true/,
~/@Profile\("dev"\)/ // Zur Überprüfung markieren
]
def violations = []
fileTree('src/main').matching { include '**/*.java' }.each { file ->
def content = file.text
debugPatterns.each { pattern ->
if (content =~ pattern) {
violations << "${file.path}: ${pattern}"
}
}
}
if (violations) {
throw new GradleException("Debug-Code gefunden:\n${violations.join('\n')}")
}
}
}
check.dependsOn checkDebugCode
*/
// SICHER: Node.js ohne Debug-Code in Produktion
const express = require('express');
const app = express();
// Umgebungsbasierte Konfiguration
const isProduction = process.env.NODE_ENV === 'production';
const isDevelopment = process.env.NODE_ENV === 'development';
// SICHER: Logging-Middleware ohne sensible Daten
const sanitizeForLog = (obj) => {
const sensitiveKeys = ['password', 'token', 'secret', 'authorization', 'cookie'];
const sanitized = { ...obj };
for (const key of Object.keys(sanitized)) {
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
sanitized[key] = '[GESCHWÄRZT]';
}
}
return sanitized;
};
app.use((req, res, next) => {
// Sicheres Logging
console.log('Anfrage:', {
method: req.method,
path: req.path,
ip: req.ip
// Keine Headers, Body oder Cookies!
});
next();
});
// SICHER: Debug-Routen nur in Entwicklung
if (isDevelopment) {
const debugRouter = express.Router();
// Auch in dev Authentifizierung erfordern
debugRouter.use(requireDevAuth);
debugRouter.get('/info', (req, res) => {
res.json({
nodeVersion: process.version,
uptime: process.uptime(),
memoryUsage: process.memoryUsage()
});
});
app.use('/debug', debugRouter);
}
// SICHER: Keine Testkonten - ordnungsgemäße Test-Fixtures verwenden
// test/fixtures/users.js (getrennt vom Anwendungscode)
// SICHER: Fehlschlagen wenn Debug-Code in Produktion erkannt
if (isProduction) {
// Auf Debug-Routen prüfen
const debugRoutes = app._router.stack
.filter(r => r.route?.path?.includes('debug'));
if (debugRoutes.length > 0) {
console.error('Debug-Routen in Produktion erkannt!');
process.exit(1);
}
// Auf gefährliche Flags prüfen
if (process.env.DEBUG_MODE === 'true') {
console.error('DEBUG_MODE kann in Produktion nicht aktiviert sein!');
process.exit(1);
}
}
// SICHER: Pre-commit Hook zur Debug-Code-Erkennung
// .husky/pre-commit
/*
#!/bin/sh
# Auf Debug-Code prüfen
if grep -r "DEBUG.*=.*true\|console\.log\|debugger" --include="*.js" src/; then
echo "Debug-Code erkannt! Bitte vor dem Commit entfernen."
exit 1
fi
*/
// SICHER: ESLint-Regeln für Debug-Code
// .eslintrc.js
/*
module.exports = {
rules: {
'no-console': 'error',
'no-debugger': 'error',
'no-alert': 'error'
}
};
*/
Ausgenutzt in der Praxis
Django Debug-Modus-Expositionen
Zahlreiche Django-Anwendungen wurden mit DEBUG=True in der Produktion gefunden, was Einstellungen, Datenbank-Anmeldedaten und vollständige Stack-Traces offenlegte.
Symfony Profiler-Zugriff
Der Symfony-Web-Profiler, wenn in der Produktion aktiviert gelassen, hat sensible Anwendungsdaten offengelegt und Codeausführung ermöglicht.
Spring Boot Actuator
Ungesicherte Spring Boot Actuator-Endpunkte haben Umgebungsvariablen, Heap-Dumps offengelegt und Anwendungsmanipulation ermöglicht.
CVE-Beispiele
-
CVE-2017-16894 — Laravel Debug-Modus Informationsoffenlegung.
-
CVE-2020-5902 — F5 BIG-IP Debug-Endpunkt-Schwachstelle.
-
CVE-2021-21985 — VMware vCenter Debug-Funktionalität.
Referenzen
-
MITRE Corporation. "CWE-489: Active Debug Code." https://cwe.mitre.org/data/definitions/489.html
-
OWASP. "Security Misconfiguration." https://owasp.org/Top10/A05_2021-Security_Misconfiguration/