Doppelter Schlüssel in assoziativer Liste (Alist)

Beschreibung

Doppelter Schlüssel in assoziativer Liste ist eine Schwachstelle, bei der eine assoziative Liste (Dictionary, Map oder ähnliche Datenstruktur) mehrere Einträge mit demselben Schlüssel enthält. Während doppelte Schlüsseleinträge theoretisch als Konstantzeit-Ersetzungsfunktion dienen könnten, wenn sie ordnungsgemäß entworfen wurden, treten sie oft unbeabsichtigt auf. Diese Mehrdeutigkeit schafft Verwirrung darüber, ob nicht-eindeutige Schlüssel einen tatsächlichen Fehlerzustand darstellen, führt zu unvorhersehbarem Verhalten abhängig davon, welcher Eintrag zugegriffen wird, und kann subtile Sicherheitsprobleme verursachen, wenn der falsche Wert für einen Schlüssel abgerufen wird.

Risiko

Doppelte Schlüssel in assoziativen Listen erzeugen unvorhersehbares Verhalten und potenzielle Sicherheitsschwachstellen. Wenn Code eindeutige Schlüssel erwartet, kann das Abrufen eines Wertes je nach Implementierungsdetails ein unerwartetes Ergebnis liefern. Sortieroperationen werden undefiniert. Sicherheitsentscheidungen basierend auf Abfragen können den falschen Wert verwenden. Konfigurationsanalyse mit doppelten Schlüsseln kann unerwartete Einstellungen anwenden. Bei JSON/XML-Verarbeitung können doppelte Schlüssel Parameterverunreinigungs-Angriffe ermöglichen. Die geringe Wahrscheinlichkeit absichtlicher Ausnutzung wird durch die hohe Wahrscheinlichkeit von Bugs ausgeglichen, die sicherheitsrelevantes falsches Verhalten verursachen.

Lösung

Ersetzen Sie assoziative Listen-Implementierungen durch Hash-Tabellen oder andere Strukturen, die inhärent Schlüsseleindeutigkeit erzwingen. Implementieren Sie Validierung, um Schlüsseleindeutigkeit vor jeder Einfügung zu prüfen. Beim Parsen externer Daten (JSON, Konfigurationsdateien) validieren Sie, dass keine doppelten Schlüssel existieren und lehnen Sie Duplikate ab oder führen Sie sie gemäß einer klaren Richtlinie zusammen. Verwenden Sie statische Analysewerkzeuge, um potenzielle Probleme mit doppelten Schlüsseln zu erkennen. Wenn doppelte Schlüssel legitimerweise benötigt werden, verwenden Sie Multimap-Strukturen, die explizit mehrere Werte pro Schlüssel unterstützen.

Häufige Auswirkungen

AuswirkungDetails
SonstigesUmfang: Sonstiges

Qualitätsverschlechterung - Software verhält sich unvorhersehbar, wenn doppelte Schlüssel existieren, da der abgerufene Wert von Implementierungsdetails abhängt.
SonstigesUmfang: Sonstiges

Variiert je nach Kontext - Sicherheitsauswirkung hängt davon ab, wie die assoziative Liste verwendet wird. Kann falsche Autorisierungsentscheidungen oder Konfigurationsfehler verursachen.

Beispielcode

Anfälliger Code

# Anfällig: Doppelte Schlüssel beim Konfigurationsparsen
class VulnerableConfigParser:
    def parse_config(self, config_lines):
        config = {}

        for line in config_lines:
            key, value = line.strip().split('=', 1)
            # Anfällig: Überschreibt Duplikate stillschweigend
            config[key] = value

        # Angreifer liefert:
        # admin_required=true
        # admin_required=false
        # Letzter Wert gewinnt - Sicherheit umgangen
        return config

    def parse_json_permissive(self, json_string):
        # Anfällig: Python json-Modul verwendet letzter-Wert-gewinnt für Duplikate
        import json
        return json.loads(json_string)

        # JSON: {"role": "user", "role": "admin"}
        # Ergebnis: {"role": "admin"} - Privilegieneskalation

    def process_form_data(self, form_fields):
        # Anfällig: Mehrere Werte für denselben Parameter
        params = {}
        for field in form_fields:
            # Anfällig: Speichert nur letzten Wert
            params[field.name] = field.value

        # HTTP-Parameter-Verunreinigung: ?admin=false&admin=true
        # Kann Sicherheit umgehen abhängig von Implementierung
        return params
// Anfällig: Doppelte Schlüssel bei Property-Behandlung
public class VulnerablePropertyHandler {

    public Properties loadProperties(InputStream input) throws IOException {
        Properties props = new Properties();
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));

        String line;
        while ((line = reader.readLine()) != null) {
            if (line.contains("=")) {
                String[] parts = line.split("=", 2);
                // Anfällig: Spätere Werte überschreiben frühere
                props.setProperty(parts[0], parts[1]);
            }
        }

        return props;
    }

    // Anfällig: XML mit doppelten Attributen
    public Map<String, String> parseXMLAttributes(Element element) {
        Map<String, String> attrs = new HashMap<>();

        NamedNodeMap nodeMap = element.getAttributes();
        for (int i = 0; i < nodeMap.getLength(); i++) {
            Node attr = nodeMap.item(i);
            // Anfällig: Hängt von Parser-Behandlung von Duplikaten ab
            attrs.put(attr.getNodeName(), attr.getNodeValue());
        }

        return attrs;
    }
}
// Anfällig: Doppelte Einträge in Lookup-Tabelle
#include <stdio.h>
#include <string.h>

typedef struct {
    char* key;
    char* value;
} Entry;

typedef struct {
    Entry* entries;
    int count;
    int capacity;
} AssocList;

// Anfällig: Keine Duplikatprüfung beim Einfügen
void vulnerable_insert(AssocList* list, const char* key, const char* value) {
    if (list->count >= list->capacity) {
        // Array erweitern...
    }

    // Anfällig: Fügt Eintrag blind hinzu, auch wenn Schlüssel existiert
    list->entries[list->count].key = strdup(key);
    list->entries[list->count].value = strdup(value);
    list->count++;

    // Liste kann jetzt doppelte Schlüssel haben
}

// Anfällig: Erste-Übereinstimmung-Abfrage
const char* vulnerable_lookup(AssocList* list, const char* key) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->entries[i].key, key) == 0) {
            return list->entries[i].value;  // Gibt erste Übereinstimmung zurück
        }
    }
    return NULL;

    // Wenn Duplikate existieren, wird immer erster Wert zurückgegeben
    // Angreifer fügt frühen Eintrag ein, um spätere Sicherheitseinstellungen zu überschreiben
}

// Anfällig: Umgebungsvariablen-Parsing
void parse_env_config(AssocList* config) {
    // Anfällig: /proc/*/environ kann doppelte Einträge haben
    // Angreifer-kontrollierte Umgebung kann Duplikate haben
    FILE* env_file = fopen("/proc/self/environ", "r");
    // ... parsen und einfügen ohne Duplikatprüfung
}
// Anfällig: Doppelte Schlüssel bei Objekt-Behandlung
class VulnerableObjectHandler {

    parseQueryString(queryString) {
        const params = {};

        queryString.split('&').forEach(pair => {
            const [key, value] = pair.split('=');
            // Anfällig: Spätere Werte überschreiben
            params[decodeURIComponent(key)] = decodeURIComponent(value);
        });

        // ?role=user&role=admin -> {role: "admin"}
        return params;
    }

    mergeObjects(obj1, obj2) {
        // Anfällig: obj2-Schlüssel überschreiben obj1 stillschweigend
        return {...obj1, ...obj2};

        // Wenn obj2 Angreifer-kontrolliert ist, kann er jeden Schlüssel überschreiben
    }

    processHeaders(headerArray) {
        const headers = {};

        for (const header of headerArray) {
            // Anfällig: Doppelte Header nicht konsistent behandelt
            headers[header.name.toLowerCase()] = header.value;

            // HTTP erlaubt mehrere Header mit gleichem Namen
            // Anderes Verhalten als Browser/Server möglicherweise erwarten
        }

        return headers;
    }
}

Korrigierter Code

# Korrigiert: Duplikatschlüssel-Erkennung und -Behandlung
class SecureConfigParser:
    def parse_config(self, config_lines, allow_duplicates=False):
        config = {}
        seen_keys = set()

        for line_num, line in enumerate(config_lines, 1):
            if '=' not in line:
                continue

            key, value = line.strip().split('=', 1)

            # Korrigiert: Duplikate erkennen
            if key in seen_keys:
                if not allow_duplicates:
                    raise ConfigError(
                        f"Doppelter Schlüssel '{key}' in Zeile {line_num}"
                    )
                else:
                    # Korrigiert: Explizite Richtlinie für Duplikate
                    # Option 1: Ersten Wert behalten
                    continue
                    # Option 2: In Liste zusammenführen
                    # if not isinstance(config[key], list):
                    #     config[key] = [config[key]]
                    # config[key].append(value)

            config[key] = value
            seen_keys.add(key)

        return config

    def parse_json_strict(self, json_string):
        import json

        # Korrigiert: Doppelte Schlüssel in JSON erkennen
        def detect_duplicates(pairs):
            seen = set()
            result = {}
            for key, value in pairs:
                if key in seen:
                    raise ValueError(f"Doppelter Schlüssel in JSON: {key}")
                seen.add(key)
                result[key] = value
            return result

        return json.loads(json_string, object_pairs_hook=detect_duplicates)

    def process_form_data(self, form_fields):
        # Korrigiert: Mehrere Werte explizit behandeln
        from collections import defaultdict

        params = defaultdict(list)
        for field in form_fields:
            params[field.name].append(field.value)

        # Korrigiert: Rückgabe mit expliziter Mehrfachwert-Unterstützung
        # Oder einzelne Werte konvertieren: {k: v[0] if len(v)==1 else v for k,v in params.items()}
        return dict(params)
// Korrigiert: Duplikatschlüssel-Verhinderung
public class SecurePropertyHandler {

    public Map<String, String> loadProperties(InputStream input) throws IOException {
        Map<String, String> props = new HashMap<>();
        Set<String> seenKeys = new HashSet<>();
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));

        String line;
        int lineNumber = 0;
        while ((line = reader.readLine()) != null) {
            lineNumber++;
            if (line.contains("=")) {
                String[] parts = line.split("=", 2);
                String key = parts[0].trim();

                // Korrigiert: Auf Duplikate prüfen
                if (seenKeys.contains(key)) {
                    throw new DuplicateKeyException(
                        "Doppelter Schlüssel '" + key + "' in Zeile " + lineNumber
                    );
                }

                props.put(key, parts[1]);
                seenKeys.add(key);
            }
        }

        return Collections.unmodifiableMap(props);
    }

    // Korrigiert: Eindeutigkeit nach Parsing validieren
    public void validateUniqueKeys(Map<String, ?> map, String context) {
        // Für Maps, die möglicherweise aus Quellen erstellt wurden, die Duplikate erlauben
        // Quelle vor Konvertierung validieren
    }

    // Korrigiert: MultiMap für legitime Mehrfachwert-Fälle verwenden
    public Multimap<String, String> parseHeaders(List<Header> headers) {
        Multimap<String, String> result = ArrayListMultimap.create();

        for (Header header : headers) {
            result.put(header.getName().toLowerCase(), header.getValue());
        }

        // Korrigiert: Unterstützt explizit mehrere Werte pro Schlüssel
        return result;
    }
}
// Korrigiert: Assoziative Liste mit Duplikat-Verhinderung
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    char* key;
    char* value;
} Entry;

typedef struct {
    Entry* entries;
    int count;
    int capacity;
} AssocList;

// Korrigiert: Vor Einfügen auf Duplikate prüfen
int secure_insert(AssocList* list, const char* key, const char* value) {
    // Korrigiert: Prüfen ob Schlüssel bereits existiert
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->entries[i].key, key) == 0) {
            // Korrigiert: Fehler für Duplikat zurückgeben
            return -1;  // Schlüssel existiert bereits
        }
    }

    if (list->count >= list->capacity) {
        // Array erweitern...
        int new_capacity = list->capacity * 2;
        Entry* new_entries = realloc(list->entries,
                                     new_capacity * sizeof(Entry));
        if (!new_entries) return -2;
        list->entries = new_entries;
        list->capacity = new_capacity;
    }

    list->entries[list->count].key = strdup(key);
    list->entries[list->count].value = strdup(value);
    list->count++;

    return 0;  // Erfolg
}

// Korrigiert: Existierenden aktualisieren oder neuen einfügen
int secure_upsert(AssocList* list, const char* key, const char* value) {
    // Zuerst versuchen, existierenden zu aktualisieren
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->entries[i].key, key) == 0) {
            free(list->entries[i].value);
            list->entries[i].value = strdup(value);
            return 1;  // Existierenden aktualisiert
        }
    }

    // Nicht gefunden, neuen einfügen
    return secure_insert(list, key, value);
}

// Korrigiert: Hash-Tabelle für O(1) Duplikaterkennung verwenden
#include <uthash.h>  // Oder ähnliche Hash-Tabellen-Bibliothek

typedef struct {
    char* key;
    char* value;
    UT_hash_handle hh;
} HashEntry;

typedef struct {
    HashEntry* table;
} SecureMap;

int secure_map_insert(SecureMap* map, const char* key, const char* value) {
    HashEntry* existing = NULL;
    HASH_FIND_STR(map->table, key, existing);

    if (existing) {
        return -1;  // Doppelter Schlüssel
    }

    HashEntry* entry = malloc(sizeof(HashEntry));
    entry->key = strdup(key);
    entry->value = strdup(value);
    HASH_ADD_KEYPTR(hh, map->table, entry->key, strlen(entry->key), entry);

    return 0;
}
// Korrigiert: Duplikatschlüssel-Behandlung
class SecureObjectHandler {

    parseQueryString(queryString, options = {}) {
        const { allowDuplicates = false, mergeStrategy = 'array' } = options;
        const params = new Map();
        const seen = new Set();

        queryString.split('&').forEach(pair => {
            const [key, value] = pair.split('=').map(decodeURIComponent);

            if (seen.has(key)) {
                if (!allowDuplicates) {
                    throw new Error(`Doppelter Query-Parameter: ${key}`);
                }

                // Korrigiert: Explizite Zusammenführungsstrategie
                if (mergeStrategy === 'array') {
                    const existing = params.get(key);
                    if (Array.isArray(existing)) {
                        existing.push(value);
                    } else {
                        params.set(key, [existing, value]);
                    }
                } else if (mergeStrategy === 'first') {
                    // Ersten Wert behalten
                } else if (mergeStrategy === 'last') {
                    params.set(key, value);
                }
            } else {
                params.set(key, value);
                seen.add(key);
            }
        });

        return Object.fromEntries(params);
    }

    mergeObjects(target, source, options = {}) {
        const { onConflict = 'error' } = options;
        const result = {...target};

        for (const [key, value] of Object.entries(source)) {
            if (key in result) {
                // Korrigiert: Konflikte explizit behandeln
                switch (onConflict) {
                    case 'error':
                        throw new Error(`Schlüsselkonflikt: ${key}`);
                    case 'keep':
                        break;  // Zielwert behalten
                    case 'override':
                        result[key] = value;
                        break;
                    case 'merge':
                        if (typeof result[key] === 'object' && typeof value === 'object') {
                            result[key] = this.mergeObjects(result[key], value, options);
                        } else {
                            result[key] = value;
                        }
                        break;
                }
            } else {
                result[key] = value;
            }
        }

        return result;
    }

    processHeaders(headerArray) {
        // Korrigiert: Map verwenden, die alle Werte bewahrt
        const headers = new Map();

        for (const header of headerArray) {
            const name = header.name.toLowerCase();

            if (headers.has(name)) {
                // Korrigiert: An existierenden anhängen (HTTP-Spec erlaubt mehrere)
                const existing = headers.get(name);
                headers.set(name, `${existing}, ${header.value}`);
            } else {
                headers.set(name, header.value);
            }
        }

        return headers;
    }
}

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgeführt. Das Muster ist jedoch verwandt mit:

  • HTTP-Parameter-Verunreinigungs-Angriffen
  • JSON-Injection via doppelte Schlüssel
  • Konfigurationsanalyse-Schwachstellen

Referenzen

  1. MITRE Corporation. "CWE-462: Duplicate Key in Associative List (Alist)." https://cwe.mitre.org/data/definitions/462.html
  2. CERT C Secure Coding Standard. "ENV02-C. Beware of multiple environment variables with the same effective name."