Unzureichende Autorisierung von Index mit sensiblen Informationen

Beschreibung

Unzureichende Autorisierung von Index mit sensiblen Informationen tritt auf, wenn eine Anwendung Suchindizes, Datenbankindizes oder Caching-Strukturen erstellt oder exponiert, die sensible Daten ohne ordnungsgemäße Zugriffskontrollen enthalten. Diese Indizes aggregieren oft Daten aus mehreren Quellen und exponieren potenziell Informationen, die einzelne Datenquellen schützen würden. Suchergebnisse können Datenexistenz oder -inhalt an unbefugte Benutzer offenlegen.

Risiko

Suchindizes können sensible Daten exponieren, die zugrunde liegende Systeme schützen. Autocomplete-Features können Informationen über Daten anderer Benutzer leaken. Indexstrukturen offenbaren Datenmuster und Beziehungen. Cache-Indizes können veraltete sensible Daten an unbefugte Benutzer zurückgeben. Volltextsuche über verschlüsselte Daten kann Klartext offenlegen. Aggregierte Indizes umgehen Row-Level-Security der Quelldaten.

Lösung

Wenden Sie dieselben Zugriffskontrollen auf Indizes wie auf Quelldaten an. Implementieren Sie Autorisierung zur Index-Abfragezeit. Erwägen Sie separate Indizes pro Autorisierungsebene. Bereinigen Sie sensible Daten vor der Indizierung. Implementieren Sie ordnungsgemäße Index-Level-Zugriffskontrollen. Überprüfen Sie Autocomplete-/Vorschlagsfeatures auf Datenlecks. Wenden Sie Field-Level-Security auf Suchergebnisse an.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Informationsoffenlegung

Sensible Daten durch Such-/Index-Abfragen exponiert.
DatenschutzBereich: Personenbezogene Daten-Exposure

In Indizes aggregierte PII werden zugänglich.
AutorisierungBereich: Bypass

Indexzugriff umgeht Quelldaten-Zugriffskontrollen.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Suchindex ohne Zugriffskontrollen
@Service
public class VulnerableSearchService {

    @Autowired
    private SearchIndex globalIndex;

    public List<Document> search(String query) {
        // Kein Benutzerkontext - jeder kann alles suchen!
        return globalIndex.search(query);
    }

    // Index enthält alle Dokumente unabhängig von Berechtigungen
    public void indexDocument(Document doc) {
        globalIndex.add(doc);  // Keine Zugriffsebene gespeichert
    }
}

// VERWUNDBAR: Autocomplete exponiert alle Benutzerdaten
@RestController
public class VulnerableAutocompleteController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping("/api/autocomplete/users")
    public List<String> autocompleteUsers(@RequestParam String prefix) {
        // Gibt ALLE Benutzer zurück, die mit Präfix übereinstimmen!
        // Angreifer kann Benutzernamen enumerieren
        return userRepository.findUsernamesByPrefix(prefix);
    }
}

// VERWUNDBAR: Cache-Index ohne Autorisierung
@Service
public class VulnerableCacheService {

    private Map<String, Object> cache = new ConcurrentHashMap<>();

    public void cacheData(String key, Object data) {
        // Keine Benutzerzuordnung
        cache.put(key, data);
    }

    public Object getData(String key) {
        // Jeder kann alle gecachten Daten abrufen!
        return cache.get(key);
    }
}
# VERWUNDBAR: Elasticsearch ohne Field-Level-Security
from elasticsearch import Elasticsearch

es = Elasticsearch()

def search_vulnerable(query):
    # Gibt alle übereinstimmenden Dokumente einschließlich sensibler Felder zurück
    return es.search(
        index="documents",
        body={
            "query": {
                "match": {"content": query}
            }
        }
    )

# Index enthält sensible Daten ohne Zugriffskontrollen
def index_document_vulnerable(doc):
    es.index(
        index="documents",
        body={
            "title": doc.title,
            "content": doc.content,
            "ssn": doc.ssn,  # Sensibel!
            "salary": doc.salary,  # Sensibel!
            "owner": doc.owner
        }
    )

# VERWUNDBAR: Autocomplete auf sensiblen Daten
@app.route('/api/search/suggest')
def suggest_vulnerable():
    prefix = request.args.get('q', '')

    # Schlägt basierend auf ALLEN indizierten Inhalten vor
    results = es.search(
        index="documents",
        body={
            "suggest": {
                "content-suggest": {
                    "prefix": prefix,
                    "completion": {
                        "field": "content_suggest"
                    }
                }
            }
        }
    )

    return jsonify(results)
// VERWUNDBAR: Client-seitiger Suchindex
const searchIndex = [];

// Alle Daten client-seitig indizieren
function buildIndexVulnerable(allDocuments) {
    allDocuments.forEach(doc => {
        // Alle Dokumente indiziert unabhängig von Benutzerberechtigungen
        searchIndex.push({
            id: doc.id,
            title: doc.title,
            content: doc.content,
            owner: doc.owner,
            sensitiveData: doc.sensitiveData  // Exponiert!
        });
    });
}

// VERWUNDBAR: Suche gibt alle Treffer zurück
function searchVulnerable(query) {
    return searchIndex.filter(doc =>
        doc.content.toLowerCase().includes(query.toLowerCase())
    );
}

Lösungscode

// SICHER: Suche mit Autorisierungskontext
@Service
public class SafeSearchService {

    @Autowired
    private SearchIndex index;

    @Autowired
    private AuthorizationService authService;

    public List<Document> search(String query, User currentUser) {
        // Filter für Benutzerberechtigungen erstellen
        SearchFilter filter = buildPermissionFilter(currentUser);

        // Suche mit Autorisierungsfilter
        return index.search(query, filter);
    }

    private SearchFilter buildPermissionFilter(User user) {
        SearchFilter filter = new SearchFilter();

        // Benutzer kann eigene Dokumente sehen
        filter.addCondition("owner", user.getId());

        // Mit Benutzer geteilte Dokumente hinzufügen
        List<Long> sharedDocIds = getSharedDocumentIds(user);
        filter.addCondition("id", "IN", sharedDocIds);

        // Admin kann alles sehen
        if (user.isAdmin()) {
            filter.clearConditions();
        }

        return filter;
    }

    // Index mit Zugriffsebenen-Metadaten
    public void indexDocument(Document doc) {
        IndexEntry entry = new IndexEntry();
        entry.setContent(doc.getContent());
        entry.setOwnerId(doc.getOwner().getId());
        entry.setAccessLevel(doc.getAccessLevel());
        entry.setSharedWith(doc.getSharedUserIds());

        index.add(entry);
    }
}

// SICHER: Autocomplete mit Autorisierung
@RestController
public class SafeAutocompleteController {

    @Autowired
    private UserService userService;

    @GetMapping("/api/autocomplete/users")
    public List<String> autocompleteUsers(
            @RequestParam String prefix,
            @AuthenticationPrincipal User currentUser) {

        // Nur Benutzer zurückgeben, die dieser Benutzer sehen kann
        return userService.findVisibleUsernamesByPrefix(
            prefix, currentUser
        );
    }
}

// SICHER: Field-Level-Security in Suchergebnissen
@Service
public class SafeFieldFilterService {

    public List<DocumentDTO> filterSensitiveFields(
            List<Document> documents, User user) {

        return documents.stream()
            .map(doc -> {
                DocumentDTO dto = new DocumentDTO();
                dto.setId(doc.getId());
                dto.setTitle(doc.getTitle());

                // Sensible Felder nur einschließen wenn autorisiert
                if (canViewSensitiveData(user, doc)) {
                    dto.setSalary(doc.getSalary());
                    dto.setSsn(doc.getSsn());
                }

                return dto;
            })
            .collect(Collectors.toList());
    }
}
# SICHER: Elasticsearch mit Document-Level-Security
from elasticsearch import Elasticsearch

es = Elasticsearch()

def search_safe(query, current_user):
    # Autorisierungsfilter erstellen
    auth_filter = build_auth_filter(current_user)

    return es.search(
        index="documents",
        body={
            "query": {
                "bool": {
                    "must": {
                        "match": {"content": query}
                    },
                    "filter": auth_filter
                }
            },
            "_source": {
                # Sensible Felder aus Ergebnissen ausschließen
                "excludes": ["ssn", "raw_password"]
            }
        }
    )

def build_auth_filter(user):
    if user.is_admin:
        return {"match_all": {}}

    return {
        "bool": {
            "should": [
                {"term": {"owner": user.id}},
                {"terms": {"shared_with": [user.id]}},
                {"term": {"public": True}}
            ]
        }
    }

# SICHER: Separate Indizes nach Sicherheitsebene
def index_document_safe(doc, security_level):
    # Verschiedene Indizes für verschiedene Zugriffsebenen
    index_name = f"documents_{security_level}"

    # Sensible Felder nicht indizieren
    body = {
        "title": doc.title,
        "content": doc.content,
        "owner": doc.owner,
        "access_level": security_level
        # SSN und Gehalt NICHT indiziert
    }

    es.index(index=index_name, body=body)

# SICHER: Autocomplete mit Autorisierung
@app.route('/api/search/suggest')
@require_auth
def suggest_safe():
    prefix = request.args.get('q', '')
    user = get_current_user()

    # Vorschläge nach Autorisierung filtern
    auth_filter = build_auth_filter(user)

    results = es.search(
        index="documents",
        body={
            "query": {
                "bool": {
                    "filter": auth_filter
                }
            },
            "suggest": {
                "content-suggest": {
                    "prefix": prefix,
                    "completion": {
                        "field": "content_suggest",
                        "contexts": {
                            "access_level": user.access_levels
                        }
                    }
                }
            }
        }
    )

    return jsonify(filter_sensitive_suggestions(results, user))
// SICHER: Server-seitige Suche mit Autorisierung
async function searchSafe(query) {
    // Server wendet Autorisierung an
    const response = await fetch('/api/search', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${getToken()}`
        },
        body: JSON.stringify({ query })
    });

    // Ergebnisse bereits vom Server gefiltert
    return response.json();
}

// SICHER: Server-seitiges Autocomplete
async function autocompleteSafe(prefix) {
    const response = await fetch(
        `/api/autocomplete?q=${encodeURIComponent(prefix)}`,
        {
            headers: {
                'Authorization': `Bearer ${getToken()}`
            }
        }
    );

    // Server gibt nur autorisierte Vorschläge zurück
    return response.json();
}

Ausgenutzt in der Praxis

Such-basierte Daten-Exposure

Unternehmens-Suchsysteme exponierten Dokumente, auf die Benutzer keinen Zugriff haben sollten.

Autocomplete-Datenlecks

Autocomplete-Features offenbarten Benutzernamen, E-Mail-Adressen und andere sensible Daten.

Cache-Poisoning/-Lecks

Geteilte Caches gaben sensible Daten anderer Benutzer zurück.


Tools zum Testen/Ausnutzen

  • Suchabfrage-Manipulationstools
  • Autocomplete-Enumerationsskripte
  • Cache-Analysetools
  • Elasticsearch Security-Audit-Tools

CVE-Beispiele

  • CVEs durch Suchindex-Daten-Exposure
  • Autocomplete-Enumerationsschwachstellen

Referenzen

  1. MITRE. "CWE-612: Improper Authorization of Index Containing Sensitive Information." https://cwe.mitre.org/data/definitions/612.html
  2. Elasticsearch Security Documentation.