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
| Impact | Details |
|---|---|
| Confidentiality | Scope: Information Disclosure Sensitive data exposed through search/index queries. |
| Privacy | Scope: Personal Data Exposure PII aggregated in indexes becomes accessible. |
| Authorization | Scope: 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
-
Search query manipulation tools.
-
Autocomplete enumeration scripts.
-
Cache analysis tools.
CVE Examples
-
CVEs from search index data exposure.
-
Autocomplete enumeration vulnerabilities.
References
-
MITRE. "CWE-612: Improper Authorization of Index Containing Sensitive Information." https://cwe.mitre.org/data/definitions/612.html
-
Elasticsearch Security Documentation.