Active Debug Code
Description
Active Debug Code occurs when software is deployed with debugging code still enabled or accessible. This includes debug endpoints, verbose error messages, development backdoors, test accounts, debug flags, and diagnostic features that were intended only for development. Debug code often bypasses security controls, exposes sensitive information, or provides unauthorized access paths that attackers can exploit in production environments.
Risk
Debug code in production is a significant security risk. Debug endpoints may allow authentication bypass, remote code execution, or direct database access. Verbose error messages expose system internals, stack traces, and sensitive configuration. Test accounts with known credentials provide easy access. Debug flags may disable security features. Many major breaches have originated from forgotten debug code, including test APIs and development backdoors left in production releases.
Solution
Implement strict deployment processes that automatically disable debug features. Use environment-specific configurations that enable debugging only in development. Remove all debug code before release through code review and automated scanning. Never deploy with test accounts or known credentials. Use feature flags that are controlled server-side. Implement pre-deployment checks that fail if debug code is detected. Conduct regular security audits to identify residual debug functionality.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Information Disclosure Debug output exposes internal system information, credentials, and business logic. |
| Access Control | Scope: Authentication Bypass Debug backdoors and test accounts provide unauthorized access. |
| Integrity | Scope: System Manipulation Debug functionality may allow direct data modification or code execution. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Debug mode enabled in production
from flask import Flask
app = Flask(__name__)
app.debug = True # Exposes stack traces, enables debugger
# VULNERABLE: Debug endpoint left in code
@app.route('/debug/users')
def debug_users():
# No authentication - lists all users!
return jsonify([u.to_dict() for u in User.query.all()])
@app.route('/debug/sql')
def debug_sql():
# Direct SQL execution!
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():
# Exposes all configuration including secrets!
return jsonify(dict(app.config))
# VULNERABLE: Test account in code
TEST_USERS = {
'admin': 'admin123', # Hardcoded test credentials!
'test': 'test123',
'debug': 'debug'
}
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
# Check test accounts first (bypass normal auth!)
if username in TEST_USERS and TEST_USERS[username] == password:
return create_session(username, is_admin=True)
return normal_authentication(username, password)
# VULNERABLE: Debug flag disabling security
DEBUG_MODE = True # Forgot to change!
@app.before_request
def check_auth():
if DEBUG_MODE:
return # Skip all authentication in debug mode!
if not is_authenticated():
abort(401)
// VULNERABLE: Java debug code
@RestController
public class VulnerableController {
// Debug mode flag
private static final boolean DEBUG = true; // Left enabled!
@GetMapping("/api/data")
public ResponseEntity<?> getData(HttpServletRequest request) {
if (DEBUG) {
// Debug logging exposes sensitive info
System.out.println("Headers: " + getHeaders(request));
System.out.println("Session: " + request.getSession().getId());
System.out.println("User: " + request.getUserPrincipal());
}
// ... rest of method
}
// VULNERABLE: Debug endpoint
@GetMapping("/debug/heap")
public String heapDump() {
// Exposes memory contents!
Runtime runtime = Runtime.getRuntime();
return String.format("Memory: %d/%d", runtime.freeMemory(), runtime.totalMemory());
}
// VULNERABLE: Test backdoor
@GetMapping("/debug/login")
public ResponseEntity<?> debugLogin(@RequestParam String user) {
// Instant login as any user!
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(new UsernamePasswordAuthenticationToken(user, null, getAdminAuthorities()));
return ResponseEntity.ok("Logged in as " + user);
}
}
// VULNERABLE: Spring profile not properly restricted
@Profile("!prod") // Supposed to be disabled in prod, but profile might not be set!
@RestController
@RequestMapping("/debug")
public class DebugController {
@GetMapping("/env")
public Map<String, String> getEnv() {
return System.getenv(); // All environment variables!
}
}
// VULNERABLE: Node.js debug code
const express = require('express');
const app = express();
// Debug mode
const DEBUG = true; // Should be false in production!
// VULNERABLE: Debug middleware
app.use((req, res, next) => {
if (DEBUG) {
console.log('Request:', {
headers: req.headers, // Includes auth tokens!
body: req.body, // Includes passwords!
cookies: req.cookies
});
}
next();
});
// VULNERABLE: Debug routes
app.get('/debug/users', (req, res) => {
// No auth check!
User.find({}).then(users => res.json(users));
});
app.get('/debug/eval', (req, res) => {
// Remote code execution!
const code = req.query.code;
try {
const result = eval(code); // Extremely dangerous!
res.json({ result });
} catch (e) {
res.json({ error: e.message });
}
});
app.post('/debug/db', (req, res) => {
// Direct database access!
const { collection, query } = req.body;
db.collection(collection).find(query).toArray()
.then(results => res.json(results));
});
// VULNERABLE: Test credentials
const TEST_ACCOUNTS = {
'testadmin': { password: 'test123', role: 'admin' },
'testuser': { password: 'user123', role: 'user' }
};
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Check test accounts first!
if (TEST_ACCOUNTS[username]?.password === password) {
return res.json({
token: generateToken(username, TEST_ACCOUNTS[username].role)
});
}
// Normal authentication...
});
Fixed Code
# SAFE: Environment-based configuration
import os
from flask import Flask
app = Flask(__name__)
# Debug only in development, never in production
app.debug = os.environ.get('FLASK_ENV') == 'development'
# Configuration based on environment
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']))
# SAFE: Debug endpoints only in development
if app.config['DEBUG'] and os.environ.get('FLASK_ENV') == 'development':
@app.route('/debug/users')
def debug_users():
# Only accessible in development!
return jsonify([u.to_dict() for u in User.query.all()])
# SAFE: No test accounts in code
# Test accounts should be in separate test fixtures, not application code
# SAFE: Remove debug code before deployment
# Use pre-commit hooks or CI checks
# pre-commit hook example:
# grep -r "DEBUG.*=.*True" --include="*.py" && exit 1 || exit 0
# SAFE: Feature flags controlled server-side
class FeatureFlags:
def __init__(self):
self._flags = {}
self._load_from_database()
def _load_from_database(self):
# Load flags from secure database, not 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():
# Never skip authentication based on debug flags
if not is_authenticated() and request.endpoint not in PUBLIC_ENDPOINTS:
abort(401)
# SAFE: Logging without sensitive data
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):
# Remove sensitive data from logs
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())
// SAFE: Java without debug code in production
@RestController
public class SecureController {
private static final Logger logger = LoggerFactory.getLogger(SecureController.class);
// Use Spring profiles properly
@Value("${app.debug.enabled:false}")
private boolean debugEnabled;
@GetMapping("/api/data")
public ResponseEntity<?> getData(HttpServletRequest request) {
// Safe logging without sensitive data
logger.info("Request to /api/data from {}", request.getRemoteAddr());
// Process request...
return ResponseEntity.ok(data);
}
}
// SAFE: Debug controller only active in dev profile
@Profile("dev") // Only active when explicitly running with dev profile
@RestController
@RequestMapping("/debug")
@ConditionalOnProperty(name = "app.debug.endpoints.enabled", havingValue = "true")
public class DebugController {
// Even in dev, require authentication
@PreAuthorize("hasRole('DEVELOPER')")
@GetMapping("/info")
public Map<String, Object> getDebugInfo() {
// Return non-sensitive debug info only
return Map.of(
"timestamp", Instant.now(),
"javaVersion", System.getProperty("java.version"),
"uptime", ManagementFactory.getRuntimeMXBean().getUptime()
);
}
}
// SAFE: Production configuration
@Configuration
@Profile("prod")
public class ProductionSecurityConfig {
@PostConstruct
public void verifyNoDebugCode() {
// Fail fast if debug endpoints are accidentally enabled
if (Boolean.getBoolean("app.debug.endpoints.enabled")) {
throw new IllegalStateException("Debug endpoints cannot be enabled in production!");
}
}
}
// SAFE: CI/CD check for debug code
// In build.gradle or pom.xml, add a check:
/*
task checkDebugCode {
doLast {
def debugPatterns = [
~/DEBUG\s*=\s*true/,
~/\.debug\s*=\s*true/,
~/@Profile\("dev"\)/ // Flag for review
]
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 found:\n${violations.join('\n')}")
}
}
}
check.dependsOn checkDebugCode
*/
// SAFE: Node.js without debug code in production
const express = require('express');
const app = express();
// Environment-based configuration
const isProduction = process.env.NODE_ENV === 'production';
const isDevelopment = process.env.NODE_ENV === 'development';
// SAFE: Logging middleware without sensitive data
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] = '[REDACTED]';
}
}
return sanitized;
};
app.use((req, res, next) => {
// Safe logging
console.log('Request:', {
method: req.method,
path: req.path,
ip: req.ip
// No headers, body, or cookies!
});
next();
});
// SAFE: Debug routes only in development
if (isDevelopment) {
const debugRouter = express.Router();
// Still require authentication even in dev
debugRouter.use(requireDevAuth);
debugRouter.get('/info', (req, res) => {
res.json({
nodeVersion: process.version,
uptime: process.uptime(),
memoryUsage: process.memoryUsage()
});
});
app.use('/debug', debugRouter);
}
// SAFE: No test accounts - use proper test fixtures
// test/fixtures/users.js (separate from application code)
// SAFE: Fail if debug code detected in production
if (isProduction) {
// Check for debug routes
const debugRoutes = app._router.stack
.filter(r => r.route?.path?.includes('debug'));
if (debugRoutes.length > 0) {
console.error('Debug routes detected in production!');
process.exit(1);
}
// Check for dangerous flags
if (process.env.DEBUG_MODE === 'true') {
console.error('DEBUG_MODE cannot be enabled in production!');
process.exit(1);
}
}
// SAFE: Pre-commit hook for debug code detection
// .husky/pre-commit
/*
#!/bin/sh
# Check for debug code
if grep -r "DEBUG.*=.*true\|console\.log\|debugger" --include="*.js" src/; then
echo "Debug code detected! Please remove before committing."
exit 1
fi
*/
// SAFE: ESLint rules for debug code
// .eslintrc.js
/*
module.exports = {
rules: {
'no-console': 'error',
'no-debugger': 'error',
'no-alert': 'error'
}
};
*/
Exploited in the Wild
Django Debug Mode Exposures
Numerous Django applications have been found with DEBUG=True in production, exposing settings, database credentials, and full stack traces.
Symfony Profiler Access
The Symfony web profiler, when left enabled in production, has exposed sensitive application data and enabled code execution.
Spring Boot Actuator
Unsecured Spring Boot Actuator endpoints have exposed environment variables, heap dumps, and allowed application manipulation.
Tools to test/exploit
-
Nuclei — templates for debug endpoint detection.
-
Burp Suite — discover debug endpoints.
-
ffuf — fuzz for common debug paths.
-
GitLeaks — find debug code in repositories.
CVE Examples
-
CVE-2017-16894 — Laravel debug mode information disclosure.
-
CVE-2020-5902 — F5 BIG-IP debug endpoint vulnerability.
-
CVE-2021-21985 — VMware vCenter debug functionality.
References
-
MITRE. "CWE-489: Active Debug Code." https://cwe.mitre.org/data/definitions/489.html
-
OWASP. "Security Misconfiguration." https://owasp.org/Top10/A05_2021-Security_Misconfiguration/