Improper Authorization of Index Containing Sensitive Information

Description

Improper Authorization of Index Containing Sensitive Information occurs when an application creates or exposes search indexes, database indexes, or caching structures that contain sensitive data without proper access controls. These indexes often aggregate data from multiple sources, potentially exposing information that individual data sources would protect. Search results may reveal data existence or content to unauthorized users.

Risk

Search indexes may expose sensitive data that underlying systems protect. Autocomplete features can leak information about other users' data. Index structures reveal data patterns and relationships. Cache indexes may return stale sensitive data to unauthorized users. Full-text search over encrypted data may reveal plaintext. Aggregated indexes bypass row-level security of source data.

Solution

Apply same access controls to indexes as to source data. Implement authorization at index query time. Consider separate indexes per authorization level. Sanitize sensitive data before indexing. Implement proper index-level access controls. Review autocomplete/suggestion features for data leakage. Apply field-level security to search results.

Common Consequences

ImpactDetails
ConfidentialityScope: Information Disclosure

Sensitive data exposed through search/index queries.
PrivacyScope: Personal Data Exposure

PII aggregated in indexes becomes accessible.
AuthorizationScope: Bypass

Index access bypasses source data access controls.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: Search index without access controls
@Service
public class VulnerableSearchService {

    @Autowired
    private SearchIndex globalIndex;

    public List<Document> search(String query) {
        // No user context - anyone can search anything!
        return globalIndex.search(query);
    }

    // Index includes all documents regardless of permissions
    public void indexDocument(Document doc) {
        globalIndex.add(doc);  // No access level stored
    }
}

// VULNERABLE: Autocomplete exposes all users' data
@RestController
public class VulnerableAutocompleteController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping("/api/autocomplete/users")
    public List<String> autocompleteUsers(@RequestParam String prefix) {
        // Returns ALL users matching prefix!
        // Attacker can enumerate usernames
        return userRepository.findUsernamesByPrefix(prefix);
    }
}

// VULNERABLE: Cache index without authorization
@Service
public class VulnerableCacheService {

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

    public void cacheData(String key, Object data) {
        // No user association
        cache.put(key, data);
    }

    public Object getData(String key) {
        // Anyone can retrieve any cached data!
        return cache.get(key);
    }
}
# VULNERABLE: Elasticsearch without field-level security
from elasticsearch import Elasticsearch

es = Elasticsearch()

def search_vulnerable(query):
    # Returns all matching documents including sensitive fields
    return es.search(
        index="documents",
        body={
            "query": {
                "match": {"content": query}
            }
        }
    )

# Index contains sensitive data without access controls
def index_document_vulnerable(doc):
    es.index(
        index="documents",
        body={
            "title": doc.title,
            "content": doc.content,
            "ssn": doc.ssn,  # Sensitive!
            "salary": doc.salary,  # Sensitive!
            "owner": doc.owner
        }
    )

# VULNERABLE: Autocomplete on sensitive data
@app.route('/api/search/suggest')
def suggest_vulnerable():
    prefix = request.args.get('q', '')

    # Suggests based on ALL indexed content
    results = es.search(
        index="documents",
        body={
            "suggest": {
                "content-suggest": {
                    "prefix": prefix,
                    "completion": {
                        "field": "content_suggest"
                    }
                }
            }
        }
    )

    return jsonify(results)
// VULNERABLE: Client-side search index
const searchIndex = [];

// Index all data client-side
function buildIndexVulnerable(allDocuments) {
    allDocuments.forEach(doc => {
        // All documents indexed regardless of user permissions
        searchIndex.push({
            id: doc.id,
            title: doc.title,
            content: doc.content,
            owner: doc.owner,
            sensitiveData: doc.sensitiveData  // Exposed!
        });
    });
}

// VULNERABLE: Search returns all matches
function searchVulnerable(query) {
    return searchIndex.filter(doc =>
        doc.content.toLowerCase().includes(query.toLowerCase())
    );
}

Fixed Code

// SAFE: Search with authorization context
@Service
public class SafeSearchService {

    @Autowired
    private SearchIndex index;

    @Autowired
    private AuthorizationService authService;

    public List<Document> search(String query, User currentUser) {
        // Build filter for user's permissions
        SearchFilter filter = buildPermissionFilter(currentUser);

        // Search with authorization filter
        return index.search(query, filter);
    }

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

        // User can see their own documents
        filter.addCondition("owner", user.getId());

        // Add documents shared with user
        List<Long> sharedDocIds = getSharedDocumentIds(user);
        filter.addCondition("id", "IN", sharedDocIds);

        // Admin can see all
        if (user.isAdmin()) {
            filter.clearConditions();
        }

        return filter;
    }

    // Index with access level metadata
    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);
    }
}

// SAFE: Autocomplete with authorization
@RestController
public class SafeAutocompleteController {

    @Autowired
    private UserService userService;

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

        // Only return users this user can see
        return userService.findVisibleUsernamesByPrefix(
            prefix, currentUser
        );
    }
}

// SAFE: Field-level security in search results
@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());

                // Include sensitive fields only if authorized
                if (canViewSensitiveData(user, doc)) {
                    dto.setSalary(doc.getSalary());
                    dto.setSsn(doc.getSsn());
                }

                return dto;
            })
            .collect(Collectors.toList());
    }
}
# SAFE: Elasticsearch with document-level security
from elasticsearch import Elasticsearch

es = Elasticsearch()

def search_safe(query, current_user):
    # Build authorization filter
    auth_filter = build_auth_filter(current_user)

    return es.search(
        index="documents",
        body={
            "query": {
                "bool": {
                    "must": {
                        "match": {"content": query}
                    },
                    "filter": auth_filter
                }
            },
            "_source": {
                # Exclude sensitive fields from results
                "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}}
            ]
        }
    }

# SAFE: Separate indexes by security level
def index_document_safe(doc, security_level):
    # Different indexes for different access levels
    index_name = f"documents_{security_level}"

    # Don't index sensitive fields
    body = {
        "title": doc.title,
        "content": doc.content,
        "owner": doc.owner,
        "access_level": security_level
        # SSN and salary NOT indexed
    }

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

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

    # Filter suggestions by authorization
    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))
// SAFE: Server-side search with authorization
async function searchSafe(query) {
    // Server applies authorization
    const response = await fetch('/api/search', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${getToken()}`
        },
        body: JSON.stringify({ query })
    });

    // Results already filtered by server
    return response.json();
}

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

    // Server returns only authorized suggestions
    return response.json();
}

Exploited in the Wild

Search-Based Data Exposure

Enterprise search systems exposed documents users shouldn't access.

Autocomplete Data Leakage

Autocomplete features revealed usernames, email addresses, and other sensitive data.

Cache Poisoning/Leakage

Shared caches returned other users' sensitive data.


Tools to test/exploit


CVE Examples

  • CVEs from search index data exposure.

  • Autocomplete enumeration vulnerabilities.


References

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

  2. Elasticsearch Security Documentation.