Unprotected Primary Channel
Description
Unprotected Primary Channel occurs when a product implements a primary channel for administrative or restricted operations but fails to adequately safeguard that channel against unauthorized access or misuse. The primary channel is the main intended communication path for sensitive operations, but without proper protection mechanisms, attackers can access administrative functionality, escalate privileges, or bypass security controls designed to restrict such access.
Risk
Unprotected primary channels have severe implications. Unauthorized administrative access. Privilege escalation. Complete system compromise. Bypass of authentication controls. Data manipulation. Configuration tampering. Service disruption. High likelihood when administrative interfaces lack proper access controls.
Solution
Do not expose administrative functionality on the user UI during architecture and design phase. Protect the administrative/restricted functionality with a strong authentication mechanism. Implement network-level access controls to administrative channels. Use separate network segments for administrative access. Encrypt administrative communications.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Gain Privileges or Assume Identity - Attackers can escalate privileges through unprotected administrative channels. |
| Authorization | Scope: Authorization Bypass Protection Mechanism - Security controls can be circumvented via the unprotected channel. |
Example Code
Vulnerable Code
# Vulnerable: Unprotected administrative channel
from flask import Flask, request, jsonify
app = Flask(__name__)
# VULNERABLE: Admin endpoint without authentication
@app.route('/admin/users', methods=['GET', 'POST', 'DELETE'])
def admin_users():
# VULNERABLE: No authentication check
# Anyone can access this administrative function
if request.method == 'GET':
return jsonify(get_all_users())
elif request.method == 'POST':
# VULNERABLE: Create admin user without auth
data = request.json
create_user(data['username'], data['password'], admin=True)
return jsonify({'status': 'created'})
elif request.method == 'DELETE':
# VULNERABLE: Delete users without authorization
user_id = request.args.get('id')
delete_user(user_id)
return jsonify({'status': 'deleted'})
# VULNERABLE: Configuration endpoint exposed
@app.route('/admin/config', methods=['GET', 'POST'])
def admin_config():
# VULNERABLE: System configuration without protection
if request.method == 'GET':
return jsonify(get_system_config())
elif request.method == 'POST':
# VULNERABLE: Modify system config without auth
config = request.json
update_system_config(config)
return jsonify({'status': 'updated'})
# VULNERABLE: Debug endpoint in production
@app.route('/debug/execute', methods=['POST'])
def debug_execute():
# VULNERABLE: Command execution without authentication
command = request.json.get('cmd')
result = os.popen(command).read() # Also has command injection!
return jsonify({'output': result})
// Vulnerable: Java admin interface without protection
import javax.servlet.*;
import javax.servlet.http.*;
public class VulnerableAdminServlet extends HttpServlet {
// VULNERABLE: No authentication filter
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String action = req.getParameter("action");
// VULNERABLE: Administrative actions without auth check
switch (action) {
case "listUsers":
// Returns all user data
resp.getWriter().write(getAllUsers().toString());
break;
case "getConfig":
// VULNERABLE: Exposes system configuration
resp.getWriter().write(getSystemConfig().toString());
break;
case "getLogs":
// VULNERABLE: Exposes sensitive logs
resp.getWriter().write(getSystemLogs());
break;
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String action = req.getParameter("action");
// VULNERABLE: Modifying actions without authorization
switch (action) {
case "createAdmin":
// VULNERABLE: Anyone can create admin accounts
String username = req.getParameter("username");
String password = req.getParameter("password");
createAdminUser(username, password);
break;
case "updateConfig":
// VULNERABLE: System config modification
String config = req.getParameter("config");
updateSystemConfig(config);
break;
case "shutdown":
// VULNERABLE: System shutdown without auth
System.exit(0);
break;
}
}
}
// Vulnerable: Node.js Express admin routes without protection
const express = require('express');
const app = express();
// VULNERABLE: Admin router without authentication middleware
const adminRouter = express.Router();
// VULNERABLE: User management without auth
adminRouter.get('/users', (req, res) => {
// Returns all users including passwords/hashes
res.json(database.getAllUsers());
});
adminRouter.post('/users', (req, res) => {
// VULNERABLE: Create users without authentication
const { username, password, role } = req.body;
database.createUser(username, password, role);
res.json({ success: true });
});
adminRouter.delete('/users/:id', (req, res) => {
// VULNERABLE: Delete any user
database.deleteUser(req.params.id);
res.json({ success: true });
});
// VULNERABLE: System configuration
adminRouter.get('/config', (req, res) => {
res.json(systemConfig); // Includes secrets!
});
adminRouter.post('/config', (req, res) => {
Object.assign(systemConfig, req.body);
res.json({ success: true });
});
// VULNERABLE: Mounted without protection
app.use('/admin', adminRouter);
// VULNERABLE: Debug endpoint
app.post('/debug/eval', (req, res) => {
const result = eval(req.body.code); // Also has code injection!
res.json({ result });
});
app.listen(8080);
Fixed Code
# Fixed: Protected administrative channel
from flask import Flask, request, jsonify, g
from functools import wraps
import jwt
app = Flask(__name__)
# FIXED: Authentication decorator
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token:
return jsonify({'error': 'Authentication required'}), 401
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
g.current_user = payload
except jwt.InvalidTokenError:
return jsonify({'error': 'Invalid token'}), 401
return f(*args, **kwargs)
return decorated
# FIXED: Admin role requirement
def require_admin(f):
@wraps(f)
@require_auth
def decorated(*args, **kwargs):
if g.current_user.get('role') != 'admin':
return jsonify({'error': 'Admin access required'}), 403
return f(*args, **kwargs)
return decorated
# FIXED: Protected admin endpoint
@app.route('/admin/users', methods=['GET', 'POST', 'DELETE'])
@require_admin # FIXED: Requires authentication and admin role
def admin_users():
if request.method == 'GET':
# FIXED: Audit log
audit_log('user_list', g.current_user['id'])
return jsonify(get_all_users())
elif request.method == 'POST':
data = request.json
# FIXED: Audit log
audit_log('user_create', g.current_user['id'], data['username'])
create_user(data['username'], data['password'], admin=data.get('admin', False))
return jsonify({'status': 'created'})
elif request.method == 'DELETE':
user_id = request.args.get('id')
# FIXED: Audit log
audit_log('user_delete', g.current_user['id'], user_id)
delete_user(user_id)
return jsonify({'status': 'deleted'})
# FIXED: Network-level restriction for sensitive endpoints
@app.before_request
def check_admin_network():
if request.path.startswith('/admin/'):
# FIXED: Only allow from internal network
client_ip = request.remote_addr
if not is_internal_network(client_ip):
return jsonify({'error': 'Access denied'}), 403
# FIXED: Remove debug endpoints in production
if not app.debug:
@app.route('/debug/<path:path>')
def block_debug(path):
return jsonify({'error': 'Not found'}), 404
// Fixed: Java admin interface with proper protection
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebFilter;
// FIXED: Authentication filter for admin paths
@WebFilter(urlPatterns = {"/admin/*"})
public class AdminAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
HttpServletResponse httpResp = (HttpServletResponse) resp;
// FIXED: Check authentication
HttpSession session = httpReq.getSession(false);
if (session == null || session.getAttribute("user") == null) {
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// FIXED: Check admin role
User user = (User) session.getAttribute("user");
if (!user.hasRole("ADMIN")) {
httpResp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
// FIXED: Check source IP
String clientIP = httpReq.getRemoteAddr();
if (!isAllowedAdminIP(clientIP)) {
httpResp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
// FIXED: Audit log
auditLog(user.getUsername(), httpReq.getRequestURI());
chain.doFilter(req, resp);
}
}
// FIXED: Protected admin servlet
public class SecureAdminServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// FIXED: Filter already verified authentication and authorization
User user = (User) req.getSession().getAttribute("user");
String action = req.getParameter("action");
switch (action) {
case "listUsers":
// FIXED: Sanitize output, don't include sensitive data
resp.getWriter().write(getSanitizedUserList().toString());
break;
case "getConfig":
// FIXED: Return only safe config values
resp.getWriter().write(getSafeConfig().toString());
break;
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
User user = (User) req.getSession().getAttribute("user");
String action = req.getParameter("action");
// FIXED: Additional confirmation for critical actions
switch (action) {
case "createAdmin":
// FIXED: Require re-authentication for sensitive ops
if (!verifyPassword(user, req.getParameter("currentPassword"))) {
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String username = req.getParameter("username");
String password = req.getParameter("password");
createAdminUser(username, password);
// FIXED: Audit log
auditLog(user.getUsername(), "created admin: " + username);
break;
}
}
}
// Fixed: Node.js with protected admin routes
const express = require('express');
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const app = express();
// FIXED: Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Authentication required' });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// FIXED: Admin authorization middleware
const requireAdmin = (req, res, next) => {
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
next();
};
// FIXED: IP whitelist middleware
const adminIPWhitelist = (req, res, next) => {
const allowedIPs = process.env.ADMIN_IPS?.split(',') || ['127.0.0.1'];
const clientIP = req.ip || req.connection.remoteAddress;
if (!allowedIPs.includes(clientIP)) {
return res.status(403).json({ error: 'Access denied from this IP' });
}
next();
};
// FIXED: Rate limiting for admin routes
const adminRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
message: { error: 'Too many requests' }
});
// FIXED: Protected admin router
const adminRouter = express.Router();
// FIXED: Apply all security middleware
adminRouter.use(authenticate);
adminRouter.use(requireAdmin);
adminRouter.use(adminIPWhitelist);
adminRouter.use(adminRateLimiter);
// FIXED: Audit logging middleware
adminRouter.use((req, res, next) => {
auditLog({
user: req.user.id,
action: req.method + ' ' + req.path,
ip: req.ip,
timestamp: new Date()
});
next();
});
// FIXED: Protected endpoints
adminRouter.get('/users', (req, res) => {
// FIXED: Don't return sensitive data
const users = database.getAllUsers().map(u => ({
id: u.id,
username: u.username,
role: u.role,
// No password hash!
}));
res.json(users);
});
adminRouter.post('/users', (req, res) => {
const { username, password, role } = req.body;
// FIXED: Validate input
if (!username || !password) {
return res.status(400).json({ error: 'Invalid input' });
}
database.createUser(username, password, role);
res.json({ success: true });
});
// FIXED: Mount with protection
app.use('/admin', adminRouter);
// FIXED: No debug endpoints in production
if (process.env.NODE_ENV === 'production') {
app.use('/debug', (req, res) => {
res.status(404).json({ error: 'Not found' });
});
}
app.listen(8080);
CVE Examples
- CVE-2022-24112: Apache APISIX admin API unprotected allowing remote code execution.
- CVE-2020-17519: Apache Flink unprotected REST API allowing arbitrary file read.
- CVE-2019-0193: Apache Solr unprotected DataImportHandler.
Related CWEs
- CWE-923: Improper Restriction of Communication Channel to Intended Endpoints (parent)
- CWE-306: Missing Authentication for Critical Function (related)
- CWE-287: Improper Authentication (related)
References
- MITRE Corporation. "CWE-419: Unprotected Primary Channel." https://cwe.mitre.org/data/definitions/419.html
- OWASP. "Administrative Interface Security"
- NIST. "Security and Privacy Controls for Information Systems"