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

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Privilegien erlangen oder Identität annehmen - Angreifer können Schutzmechanismen umgehen, indem sie den ungeschützten Alternativkanal ausnutzen.
AutorisierungUmfang: 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

  1. MITRE Corporation. "CWE-420: Unprotected Alternate Channel." https://cwe.mitre.org/data/definitions/420.html
  2. OWASP. "Testing for Alternate Channel"
  3. NIST. "Security Assessment Guidelines"