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
| Auswirkung | Details |
|---|---|
| Sonstiges | Umfang: Sonstiges Qualitätsverschlechterung - Software verhält sich unvorhersehbar, wenn doppelte Schlüssel existieren, da der abgerufene Wert von Implementierungsdetails abhängt. |
| Sonstiges | Umfang: 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
- MITRE Corporation. "CWE-462: Duplicate Key in Associative List (Alist)." https://cwe.mitre.org/data/definitions/462.html
- CERT C Secure Coding Standard. "ENV02-C. Beware of multiple environment variables with the same effective name."