Ungeschützter Alternativkanal
Beschreibung
Ungeschützter Alternativkanal tritt auf, wenn ein Produkt einen Primärkanal schützt, aber gleichwertige Sicherheitsmaßnahmen nicht auf alternative Zugriffspfade anwendet. Während der Hauptkommunikationskanal möglicherweise ordnungsgemäße Authentifizierung, Verschlüsselung oder Zugriffskontrollen hat, können alternative Kanäle wie Backup-Schnittstellen, Debug-Ports, sekundäre Protokolle oder gespiegelte Ressourcen diese Schutzmaßnahmen nicht haben. Angreifer können diese ungeschützten Alternativkanale ausnutzen, um Sicherheitsmaßnahmen zu umgehen, die auf dem Primärkanal implementiert sind.
Risiko
Ungeschützte Alternativkanale haben schwerwiegende Auswirkungen. Vollständige Umgehung der Authentifizierung. Zugriff auf geschützte Ressourcen über alternative Pfade. Ausnutzung von Debug-Schnittstellen. Missbrauch von Backup-Kanälen. Zugriff auf Spiegel/Replikate ohne Autorisierung. Protokoll-Downgrade-Angriffe. Administrative Umgehung. Hohe Wahrscheinlichkeit wenn Sicherheit nur auf primäre Schnittstellen fokussiert ist.
Lösung
Identifizieren Sie alle Alternativkanale und verwenden Sie dieselben Schutzmechanismen, die auf Primärkanale während der Architektur- und Entwurfsphase angewendet werden. Prüfen Sie alle Zugriffspfade zu sensiblen Ressourcen. Deaktivieren Sie unnötige Alternativkanale. Wenden Sie konsistente Sicherheitsrichtlinien über alle Schnittstellen während der Implementierungsphase an. Überwachen Sie Alternativkanale auf unbefugten Zugriff.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: Zugriffskontrolle Privilegien erlangen oder Identität annehmen - Angreifer können Schutzmechanismen umgehen, indem sie den ungeschützten Alternativkanal ausnutzen. |
| Autorisierung | Umfang: Autorisierung Schutzmechanismus umgehen - Sicherheitskontrollen auf dem Primärkanal werden wirkungslos. |
Beispielcode
Anfälliger Code
// Anfällig: Hardware mit ungeschütztem alternativem Registerzugriff
module vulnerable_register_access (
input wire clk,
input wire rst_n,
input wire [31:0] address,
input wire [31:0] data_in,
input wire write_en,
input wire auth_valid, // Authentifizierungssignal
output reg [31:0] data_out,
output reg access_denied
);
// Sicheres Register an Primäradresse
reg [31:0] SECURE_REG;
localparam SECURE_ADDR = 32'h0000_0F00;
// ANFÄLLIG: Ungeschützter Spiegel an alternativer Adresse
localparam MIRROR_ADDR = 32'h0080_0F00;
// ANFÄLLIG: Nur Primäradresse ist geschützt
wire addr_needs_auth = (address == SECURE_ADDR);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SECURE_REG <= 32'h0;
access_denied <= 1'b0;
end else begin
access_denied <= 1'b0;
if (address == SECURE_ADDR) begin
// Primäradresse - erfordert Authentifizierung
if (auth_valid) begin
if (write_en) begin
SECURE_REG <= data_in;
end
data_out <= SECURE_REG;
end else begin
access_denied <= 1'b1;
end
end
else if (address == MIRROR_ADDR) begin
// ANFÄLLIG: Spiegeladresse - KEINE Authentifizierung!
if (write_en) begin
SECURE_REG <= data_in; // Schreibt in dasselbe Register!
end
data_out <= SECURE_REG; // Liest dasselbe Register!
end
end
end
// Angriff: MIRROR_ADDR (0x00800F00) statt SECURE_ADDR (0x0F00) zugreifen
// Umgeht alle Authentifizierungsprüfungen
endmodule
# Anfällig: Anwendung mit ungeschützten alternativen Endpunkten
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if not verify_auth_token(request.headers.get('Authorization')):
return jsonify({'error': 'Nicht autorisiert'}), 401
return f(*args, **kwargs)
return decorated
# Primäre API - geschützt
@app.route('/api/v2/users', methods=['GET'])
@require_auth
def get_users_v2():
return jsonify(get_all_users())
@app.route('/api/v2/config', methods=['GET', 'POST'])
@require_auth
def config_v2():
if request.method == 'GET':
return jsonify(get_config())
else:
update_config(request.json)
return jsonify({'status': 'updated'})
# ANFÄLLIG: Legacy-API noch aktiv - ungeschützt!
@app.route('/api/v1/users', methods=['GET'])
def get_users_v1():
# ANFÄLLIG: Keine Authentifizierung!
return jsonify(get_all_users())
@app.route('/api/v1/config', methods=['GET', 'POST'])
def config_v1():
# ANFÄLLIG: Keine Authentifizierung!
if request.method == 'GET':
return jsonify(get_config())
else:
update_config(request.json)
return jsonify({'status': 'updated'})
# ANFÄLLIG: Debug-Endpunkt - Alternativkanal
@app.route('/debug/dump', methods=['GET'])
def debug_dump():
# ANFÄLLIG: Vollständiger Daten-Dump ohne Auth
return jsonify({
'users': get_all_users(),
'config': get_config(),
'secrets': get_secrets() # Noch schlimmer!
})
# ANFÄLLIG: Health-Check legt Daten offen
@app.route('/health', methods=['GET'])
def health_check():
# ANFÄLLIG: Enthält sensible Informationen
return jsonify({
'status': 'ok',
'database': get_db_status(),
'users_count': len(get_all_users()),
'config': get_config() # Offengelegt!
})
// Anfällig: Java-Dienst mit ungeschütztem Alternativkanal
public class VulnerableService {
// Primärkanal - HTTPS mit Authentifizierung
@RestController
@RequestMapping("/api/secure")
public class SecureController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/data")
public ResponseEntity<Data> getData() {
return ResponseEntity.ok(dataService.getAllData());
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/config")
public ResponseEntity<Void> updateConfig(@RequestBody Config config) {
configService.update(config);
return ResponseEntity.ok().build();
}
}
// ANFÄLLIG: JMX ohne Authentifizierung offengelegt
@ManagedResource
public class DataManager {
@ManagedOperation
public String getAllDataAsJson() {
// ANFÄLLIG: JMX ohne Auth zugänglich
return dataService.getAllData().toJson();
}
@ManagedOperation
public void updateConfiguration(String configJson) {
// ANFÄLLIG: Config-Update via JMX
configService.update(Config.fromJson(configJson));
}
}
// ANFÄLLIG: Actuator-Endpunkte offengelegt
// application.properties:
// management.endpoints.web.exposure.include=*
// management.endpoint.env.show-values=always
// LEGT OFFEN: /actuator/env, /actuator/configprops, etc.
// ANFÄLLIG: RMI-Schnittstelle ohne SSL/Auth
public interface RemoteDataService extends Remote {
Data getAllData() throws RemoteException;
void updateConfig(Config config) throws RemoteException;
}
}
Korrigierter Code
// Korrigiert: Hardware mit geschütztem alternativem Registerzugriff
module secure_register_access (
input wire clk,
input wire rst_n,
input wire [31:0] address,
input wire [31:0] data_in,
input wire write_en,
input wire auth_valid,
output reg [31:0] data_out,
output reg access_denied
);
reg [31:0] SECURE_REG;
localparam SECURE_ADDR = 32'h0000_0F00;
localparam MIRROR_ADDR = 32'h0080_0F00;
// KORRIGIERT: Sowohl primäre als auch alternative Adressen erfordern Authentifizierung
wire addr_needs_auth = (address == SECURE_ADDR) ||
(address == MIRROR_ADDR);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SECURE_REG <= 32'h0;
access_denied <= 1'b0;
end else begin
access_denied <= 1'b0;
// KORRIGIERT: Auth für beide Adressen prüfen
if (address == SECURE_ADDR || address == MIRROR_ADDR) begin
if (auth_valid) begin
if (write_en) begin
SECURE_REG <= data_in;
end
data_out <= SECURE_REG;
end else begin
// KORRIGIERT: Zugriff ohne Auth verweigern
access_denied <= 1'b1;
data_out <= 32'h0; // Keine Daten lecken
end
end
end
end
endmodule
// KORRIGIERT: Alternative - den Spiegel vollständig entfernen
module secure_register_no_mirror (
input wire clk,
input wire rst_n,
input wire [31:0] address,
input wire [31:0] data_in,
input wire write_en,
input wire auth_valid,
output reg [31:0] data_out,
output reg access_denied
);
reg [31:0] SECURE_REG;
localparam SECURE_ADDR = 32'h0000_0F00;
// KORRIGIERT: Keine alternative Adresse existiert
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SECURE_REG <= 32'h0;
access_denied <= 1'b0;
end else begin
access_denied <= 1'b0;
if (address == SECURE_ADDR) begin
if (auth_valid) begin
if (write_en) begin
SECURE_REG <= data_in;
end
data_out <= SECURE_REG;
end else begin
access_denied <= 1'b1;
data_out <= 32'h0;
end
end
// KORRIGIERT: Alle anderen Adressen geben nichts Sensibles zurück
end
end
endmodule
# Korrigiert: Anwendung mit konsistentem Schutz über alle Kanäle
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if not verify_auth_token(request.headers.get('Authorization')):
return jsonify({'error': 'Nicht autorisiert'}), 401
return f(*args, **kwargs)
return decorated
# KORRIGIERT: Auth auf ALLE API-Versionen anwenden
@app.route('/api/v2/users', methods=['GET'])
@app.route('/api/v1/users', methods=['GET']) # KORRIGIERT: Gleicher Schutz
@require_auth
def get_users():
return jsonify(get_all_users())
@app.route('/api/v2/config', methods=['GET', 'POST'])
@app.route('/api/v1/config', methods=['GET', 'POST']) # KORRIGIERT
@require_auth
def config():
if request.method == 'GET':
return jsonify(get_config())
else:
update_config(request.json)
return jsonify({'status': 'updated'})
# KORRIGIERT: Debug-Endpunkte in Produktion deaktivieren
if not app.debug:
@app.route('/debug/<path:path>')
def block_debug(path):
return jsonify({'error': 'Nicht gefunden'}), 404
else:
# Auch im Debug Auth erfordern
@app.route('/debug/dump', methods=['GET'])
@require_auth
def debug_dump():
return jsonify({
'users': get_all_users(),
'config': get_config()
})
# KORRIGIERT: Health-Check ohne sensible Daten
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({
'status': 'ok',
'timestamp': datetime.now().isoformat()
# KORRIGIERT: Keine sensiblen Daten
})
# KORRIGIERT: Detaillierte Health nur für authentifizierte Benutzer
@app.route('/health/detailed', methods=['GET'])
@require_auth
def health_detailed():
return jsonify({
'status': 'ok',
'database': get_db_status(),
'users_count': len(get_all_users())
})
# KORRIGIERT: Alte API-Versionen als veraltet markieren
@app.route('/api/v0/<path:path>')
def deprecated_v0(path):
return jsonify({
'error': 'API v0 ist veraltet. Verwenden Sie v2.'
}), 410 # Gone
// Korrigiert: Java-Dienst mit geschützten Alternativkanalen
@Configuration
public class SecurityConfig {
// KORRIGIERT: JMX sichern
@Bean
public JmxConfigurator jmxConfigurator() {
JmxConfigurator config = new JmxConfigurator();
config.setAuthenticate(true);
config.setPasswordFile("jmxremote.password");
config.setAccessFile("jmxremote.access");
config.setSslEnabled(true);
return config;
}
// KORRIGIERT: Actuator-Endpunkte einschränken
// application.properties:
// management.endpoints.web.exposure.include=health,info
// management.endpoint.health.show-details=when_authorized
// management.endpoints.web.base-path=/internal/actuator
}
@RestController
@RequestMapping("/api/secure")
public class SecureController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/data")
public ResponseEntity<Data> getData() {
return ResponseEntity.ok(dataService.getAllData());
}
}
// KORRIGIERT: JMX mit erforderlicher Authentifizierung
@ManagedResource
public class SecureDataManager {
// KORRIGIERT: JMX erfordert Authentifizierung via Konfiguration
@ManagedOperation
@PreAuthorize("hasRole('ADMIN')") // Zusätzliche Prüfung
public String getAllDataAsJson(String authToken) {
if (!validateToken(authToken)) {
throw new SecurityException("Ungültiges Token");
}
auditLog("JMX-Datenzugriff");
return dataService.getAllData().toJson();
}
}
// KORRIGIERT: Sichere RMI-Konfiguration
@Configuration
public class RmiConfig {
@Bean
public RmiServiceExporter rmiExporter() {
RmiServiceExporter exporter = new RmiServiceExporter();
exporter.setServiceName("DataService");
exporter.setService(secureDataService);
exporter.setServiceInterface(RemoteDataService.class);
// KORRIGIERT: SSL verwenden
RMIClientSocketFactory csf = new SslRMIClientSocketFactory();
RMIServerSocketFactory ssf = new SslRMIServerSocketFactory();
exporter.setClientSocketFactory(csf);
exporter.setServerSocketFactory(ssf);
return exporter;
}
}
// KORRIGIERT: Audit-Logging für alle Kanäle
@Aspect
@Component
public class ChannelAuditAspect {
@Around("@annotation(PreAuthorize)")
public Object auditAccess(ProceedingJoinPoint pjp) throws Throwable {
String channel = determineChannel(); // REST, JMX, RMI, etc.
String user = getCurrentUser();
auditLog(String.format("Zugriff via %s durch %s: %s",
channel, user, pjp.getSignature()));
return pjp.proceed();
}
}
CVE-Beispiele
- CVE-2020-3452: Cisco ASA ungeschütztes WebVPN-Portal ermöglicht Dateilesen.
- CVE-2019-1653: Cisco RV320 ungeschützte Diagnoseschnittstelle.
- CVE-2018-0101: Cisco ASA SSL VPN Alternativpfad-Schwachstelle.
Verwandte CWEs
- CWE-923: Improper Restriction of Communication Channel to Intended Endpoints (Eltern)
- CWE-421: Race Condition During Access to Alternate Channel (Kind)
- CWE-288: Authentication Bypass Using Alternate Path or Channel (Peer)
Referenzen
- MITRE Corporation. "CWE-420: Unprotected Alternate Channel." https://cwe.mitre.org/data/definitions/420.html
- OWASP. "Testing for Alternate Channel"
- NIST. "Security Assessment Guidelines"