daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

9200-pentesting-elasticsearch.md (17536B)


      1 ---
      2 title: "9200 - Pentesting Elasticsearch"
      3 section: "Network Services"
      4 sectionSlug: "network-services-pentesting"
      5 sourcePath: "src/network-services-pentesting/9200-pentesting-elasticsearch.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/9200-pentesting-elasticsearch.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # 9200 - Pentesting Elasticsearch
     14 
     15 ## Basic information
     16 
     17 Elasticsearch is a **distributed**, **open source** search and analytics engine for **all types of data**. It is known for its **speed**, **scalability**, and **simple REST APIs**. Built on Apache Lucene, it was first released in 2010 by Elasticsearch N.V. (now known as Elastic). Elasticsearch is the core component of the Elastic Stack, a collection of open source tools for data ingestion, enrichment, storage, analysis, and visualization. This stack, commonly referred to as the ELK Stack, also includes Logstash and Kibana, and now has lightweight data shipping agents called Beats.
     18 
     19 ### What is an Elasticsearch index?
     20 
     21 An Elasticsearch **index** is a collection of **related documents** stored as **JSON**. Each document consists of **keys** and their corresponding **values** (strings, numbers, booleans, dates, arrays, geolocations, etc.).
     22 
     23 Elasticsearch uses an efficient data structure called an **inverted index** to facilitate fast full-text searches. This index lists every unique word in the documents and identifies the documents in which each word appears.
     24 
     25 During the indexing process, Elasticsearch stores the documents and constructs the inverted index, allowing for near real-time searching. The **index API** is used to add or update JSON documents within a specific index.
     26 
     27 **Default port**: 9200/tcp
     28 
     29 ## Manual Enumeration
     30 
     31 ### Banner
     32 
     33 The protocol used to access Elasticsearch is **HTTP**. When you access it via HTTP you will find some interesting information: `http://10.10.10.115:9200/`
     34 
     35 ![Manual Enumeration - Banner: The protocol used to access Elasticsearch is HTTP . When you access it via HTTP you will find some interesting information: http://10.10.10.115:9200/](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28294%29.png)
     36 
     37 If you don't see that response accessing `/` see the following section.
     38 
     39 ### Authentication
     40 
     41 Do **not** assume that a new deployment is unauthenticated. Current self-managed releases automatically generate TLS for the HTTP and transport layers, set a password for the `elastic` superuser, and create a short-lived Kibana enrollment token on first startup. Automatic setup may be skipped when Elasticsearch detects an existing configuration or cluster, while legacy and deliberately unsecured deployments may still expose the API without authentication.<sup>[[2]](#references)</sup>
     42 
     43 Probe both schemes and inspect the status, `WWW-Authenticate`, and `X-Elastic-Product` headers:
     44 
     45 ```bash
     46 curl -skD- -o /dev/null https://ELASTICSEARCH-SERVER:9200/
     47 curl -sD- -o /dev/null http://ELASTICSEARCH-SERVER:9200/
     48 curl -sk https://ELASTICSEARCH-SERVER:9200/_security/_authenticate
     49 ```
     50 
     51 **However**, if you send a request to `/` and receive a response like the following one:
     52 
     53 ```bash
     54 {"error":{"root_cause":[{"type":"security_exception","reason":"missing authentication credentials for REST request [/]","header":{"WWW-Authenticate":"Basic realm=\"security\" charset=\"UTF-8\""}}],"type":"security_exception","reason":"missing authentication credentials for REST request [/]","header":{"WWW-Authenticate":"Basic realm=\"security\" charset=\"UTF-8\""}},"status":401}
     55 ```
     56 
     57 This means that authentication is configured and **valid credentials are needed**. Elasticsearch REST authentication can use Basic, API key, or Bearer credentials. A `401` usually means no acceptable credential was supplied; a `403` normally means an authenticated (possibly anonymous) principal lacks the requested privilege, so keep testing lower-privileged endpoints.<sup>[[1]](#references)</sup>
     58 
     59 You can [**try to bruteforce Basic authentication**](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-hacking/brute-force.md#elasticsearch) where permitted. Built-in usernames include _**elastic** (superuser), remote_monitoring_user, beats_system, logstash_system, kibana_system,_ and _apm_system_. Modern installations do not assign a shared default password; very old versions used **changeme**.
     60 
     61 ```bash
     62 curl -sk -u 'user:password' https://IP:9200/
     63 curl -sk -H "Authorization: ApiKey $API_KEY" https://IP:9200/_security/_authenticate
     64 curl -sk -H "Authorization: Bearer $TOKEN" https://IP:9200/_security/_authenticate
     65 ```
     66 
     67 ### Basic User Enumeration
     68 
     69 ```bash
     70 #List all roles on the system:
     71 curl -X GET "ELASTICSEARCH-SERVER:9200/_security/role"
     72 
     73 #List all users on the system:
     74 curl -X GET "ELASTICSEARCH-SERVER:9200/_security/user"
     75 
     76 #Get more information about the rights of an user:
     77 curl -X GET "ELASTICSEARCH-SERVER:9200/_security/user/<USERNAME>"
     78 ```
     79 
     80 The user/role listing APIs commonly require administrative security privileges. With any valid credential, `/_security/_authenticate` is a better first request because it returns the effective username, roles, authentication realm/type, and API-key metadata. Any user can also test **its own** privileges without performing a destructive operation:<sup>[[1]](#references)</sup>
     81 
     82 ```bash
     83 curl -sk -u 'user:password' -H 'Content-Type: application/json' \
     84   https://ELASTICSEARCH-SERVER:9200/_security/user/_has_privileges -d '{
     85   "cluster": ["monitor", "manage", "manage_security", "read_pipeline", "manage_pipeline"],
     86   "index": [{"names": ["*"], "privileges": ["view_index_metadata", "read", "write", "delete_index"]}]
     87 }'
     88 ```
     89 
     90 The response reports every requested privilege separately. Test concrete index patterns as well as `*`: roles may grant access only to a tenant prefix, data stream, or alias.
     91 
     92 ### Elastic Info
     93 
     94 Here are some endpoints that you can **access via GET** to **obtain** some **information** about elasticsearch:
     95 
     96 | \_cat                          | /\_cluster                    | /\_security              |
     97 | ------------------------------ | ----------------------------- | ------------------------ |
     98 | /\_cat/segments                | /\_cluster/allocation/explain | /\_security/user         |
     99 | /\_cat/shards                  | /\_cluster/settings           | /\_security/privilege    |
    100 | /\_cat/repositories            | /\_cluster/health             | /\_security/role_mapping |
    101 | /\_cat/recovery                | /\_cluster/state              | /\_security/role         |
    102 | /\_cat/plugins                 | /\_cluster/stats              | /\_security/api_key      |
    103 | /\_cat/pending_tasks           | /\_cluster/pending_tasks      |                          |
    104 | /\_cat/nodes                   | /\_nodes                      |                          |
    105 | /\_cat/tasks                   | /\_nodes/usage                |                          |
    106 | /\_cat/templates               | /\_nodes/hot_threads          |                          |
    107 | /\_cat/thread_pool             | /\_nodes/stats                |                          |
    108 | /\_cat/ml/trained_models       | /\_tasks                      |                          |
    109 | /\_cat/transforms/\_all        | /\_remote/info                |                          |
    110 | /\_cat/aliases                 |                               |                          |
    111 | /\_cat/allocation              |                               |                          |
    112 | /\_cat/ml/anomaly_detectors    |                               |                          |
    113 | /\_cat/count                   |                               |                          |
    114 | /\_cat/ml/data_frame/analytics |                               |                          |
    115 | /\_cat/ml/datafeeds            |                               |                          |
    116 | /\_cat/fielddata               |                               |                          |
    117 | /\_cat/health                  |                               |                          |
    118 | /\_cat/indices                 |                               |                          |
    119 | /\_cat/master                  |                               |                          |
    120 | /\_cat/nodeattrs               |                               |                          |
    121 | /\_cat/nodes                   |                               |                          |
    122 
    123 These endpoints were [**taken from the documentation**](https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html) where you can **find more**.<sup>[[1]](#references)</sup>\
    124 Also, if you access `/_cat` the response will contain the `/_cat/*` endpoints supported by the instance.
    125 
    126 In `/_security/user` (if auth enabled) you can see which user has role `superuser`.
    127 
    128 ### Hidden indices, aliases and data streams
    129 
    130 `/_cat/indices` alone is incomplete: wildcard expansion may omit hidden targets, and applications commonly access aliases or data streams rather than concrete indices. The resolve API returns matching indices, aliases, and data streams; mappings and field capabilities then show searchable field names without first downloading documents.<sup>[[1]](#references)</sup>
    131 
    132 ```bash
    133 ES=https://ELASTICSEARCH-SERVER:9200
    134 curl -sk "$ES/_resolve/index/*?expand_wildcards=all&pretty"
    135 curl -sk "$ES/_data_stream/*?expand_wildcards=all&pretty"
    136 curl -sk "$ES/_alias/*?expand_wildcards=all&pretty"
    137 curl -sk "$ES/*/_mapping?expand_wildcards=all&pretty"
    138 curl -sk "$ES/*/_field_caps?fields=*&expand_wildcards=all&pretty"
    139 ```
    140 
    141 Also enumerate objects that disclose data flow, external storage, or other clusters. Success and `403` responses help map the credential's cluster privileges:
    142 
    143 ```bash
    144 curl -sk "$ES/_ingest/pipeline?pretty"
    145 curl -sk "$ES/_index_template?pretty"
    146 curl -sk "$ES/_component_template?pretty"
    147 curl -sk "$ES/_snapshot?pretty"
    148 curl -sk "$ES/_remote/info?pretty"
    149 ```
    150 
    151 ### Indices
    152 
    153 You can **gather all the indices** accessing `http://10.10.10.115:9200/_cat/indices?v`
    154 
    155 ```text
    156 health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
    157 green  open   .kibana 6tjAYZrgQ5CwwR0g6VOoRg   1   0          1            0        4kb            4kb
    158 yellow open   quotes  ZG2D1IqkQNiNZmi2HRImnQ   5   1        253            0    262.7kb        262.7kb
    159 yellow open   bank    eSVpNfCfREyYoVigNWcrMw   5   1       1000            0    483.2kb        483.2kb
    160 ```
    161 
    162 To obtain **information about which kind of data is saved inside an index** you can access: `http://host:9200/<index>` from example in this case `http://10.10.10.115:9200/bank`
    163 
    164 ![Elasticsearch bank index metadata response showing mappings and stored fields](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28342%29.png)
    165 
    166 ### Dump index
    167 
    168 If you want to **dump all the contents** of an index you can access: `http://host:9200/<index>/_search?pretty=true` like `http://10.10.10.115:9200/bank/_search?pretty=true`
    169 
    170 ![Elasticsearch search response dumping documents from the bank index](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28914%29.png)
    171 
    172 _Take a moment to compare the contents of the each document (entry) inside the bank index and the fields of this index that we saw in the previous section._
    173 
    174 The `hits.total` field indicates the number of matches, but only 10 hits are returned by default. Use `track_total_hits=true` when an exact count matters. A larger `size` works only up to the index's `index.max_result_window` (10,000 by default), so a single request is not a reliable complete dump.<sup>[[1]](#references)</sup>
    175 
    176 For a consistent dump beyond that window, create a **point in time (PIT)** and repeatedly use the last hit's `sort` array as `search_after`. Always use the newest `pit_id` returned by the previous response, renew `keep_alive`, and close the PIT when finished:<sup>[[1]](#references)</sup>
    177 
    178 ```bash
    179 PIT=$(curl -skXPOST "$ES/bank/_pit?keep_alive=2m" | jq -r .id)
    180 curl -skXPOST "$ES/_search" -H 'Content-Type: application/json' -d \
    181   "{\"size\":1000,\"track_total_hits\":true,\"query\":{\"match_all\":{}},\"pit\":{\"id\":\"$PIT\",\"keep_alive\":\"2m\"},\"sort\":[{\"_shard_doc\":\"asc\"}]}"
    182 # Save the returned pit_id, then repeat with: "search_after": [<last hit sort values>]
    183 # Set PIT to the most recently returned pit_id before closing it.
    184 curl -skXDELETE "$ES/_pit" -H 'Content-Type: application/json' -d "{\"id\":\"$PIT\"}"
    185 ```
    186 
    187 ### Dump all
    188 
    189 In order to dump all you can just go to the **same path as before but without indicating any index**`http://host:9200/_search?pretty=true` like `http://10.10.10.115:9200/_search?pretty=true`\
    190 Remember that in this case the **default limit of 10** results will be applied. You can use the `size` parameter to dump a **bigger amount of results**. Read the previous section for more information.
    191 
    192 ### Search
    193 
    194 If you are looking for some information you can do a **raw search on all the indices** going to `http://host:9200/_search?pretty=true&q=<search_term>` like in `http://10.10.10.115:9200/_search?pretty=true&q=Rockwell`
    195 
    196 ![Elasticsearch raw search response across indices for the Rockwell query](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28335%29.png)
    197 
    198 If you want just to **search on an index** you can just **specify** it on the **path**: `http://host:9200/<index>/_search?pretty=true&q=<search_term>`
    199 
    200 The `q` parameter uses Lucene query-string syntax, including `/regexp/` expressions when expensive queries are allowed. Prefer a JSON Query DSL body for precise field selection and escaping.
    201 
    202 You can also use something like [https://github.com/misalabs/horuz](https://github.com/misalabs/horuz) to fuzz an elasticsearch service.
    203 
    204 ### Write permissions
    205 
    206 You can check your write permissions trying to create a new document inside a new index running something like the following:
    207 
    208 ```bash
    209 curl -X PUT 'http://10.10.10.115:9200/bookindex/_doc/A00-3?refresh=true' \
    210   -H 'Content-Type: application/json' -d '{
    211     "bookId": "A00-3",
    212     "author": "Sankaran",
    213     "publisher": "Mcgrahill",
    214     "name": "how to get a job"
    215   }'
    216 ```
    217 
    218 This creates a **new index** called `bookindex` and a document with ID `A00-3`. Current Elasticsearch versions use the typeless `/_doc/<ID>` endpoint; paths such as `/bookindex/books` are only relevant to old releases. Prefer `/_security/user/_has_privileges` for a non-destructive permission check, because this request may auto-create an index and persist data.<sup>[[1]](#references)</sup>
    219 
    220 Notice how the **new index appears now in the list**:
    221 
    222 ![Search - Write permissions: Notice how the new index appears now in the list](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28130%29.png)
    223 
    224 And note the **automatically created properties**:
    225 
    226 ![Search - Write permissions: And note the automatically created properties](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28434%29.png)
    227 
    228 ### Ingest pipelines and file-parser attack surface
    229 
    230 Inspect `/_ingest/pipeline` for `attachment` processors and note their input field, `indexed_chars`, and failure handlers. The attachment processor base64-decodes attacker-controlled files and passes formats such as PDF and Office documents to Apache Tika on an ingest node, making an otherwise ordinary indexing permission a server-side parser entry point.<sup>[[3]](#references)</sup>
    231 
    232 A recent example was an XXE in Tika's PDF parser: when affected Elasticsearch versions processed a crafted XFA document through an attachment pipeline, an authenticated attacker could cause requests to internal/third-party services or read sensitive data. This required the attachment processor and an affected release, so validate both the pipeline and exact version before testing; patched versions and older Java-Security-Manager-based branches were not equally exposed.<sup>[[3]](#references)</sup>
    233 
    234 Use a harmless file first to prove that a reachable index invokes the processor. If the credential has `read_pipeline` (or broader pipeline-management) rights, the simulate API avoids persisting a document:<sup>[[1]](#references)</sup>
    235 
    236 ```bash
    237 DATA=$(base64 -w0 harmless.pdf)
    238 curl -skXPOST "$ES/_ingest/pipeline/PIPELINE_ID/_simulate" \
    239   -H 'Content-Type: application/json' \
    240   -d "{\"docs\":[{\"_source\":{\"data\":\"$DATA\"}}]}"
    241 ```
    242 
    243 ## Automatic Enumeration
    244 
    245 Some tools will obtain some of the data presented before:
    246 
    247 ```bash
    248 msf > use auxiliary/scanner/elasticsearch/indices_enum
    249 ```
    250 
    251 
    252 [Nmap Elasticsearch Nse](https%3A//github.com/theMiddleBlue/nmap-elasticsearch-nse)
    253 
    254 ## Shodan
    255 
    256 - `port:9200 elasticsearch`
    257 
    258 
    259 ## References
    260 
    261 - [1] [Elasticsearch REST APIs documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html)
    262 - [2] [Elastic Docs - Automatic security setup](https://www.elastic.co/docs/deploy-manage/security/self-auto-setup)
    263 - [3] [Elastic security advisory ESA-2025-14 - Attachment processor XXE](https://discuss.elastic.co/t/elasticsearch-8-18-6-8-19-3-9-0-6-and-9-1-3-security-update-esa-2025-14-cve-2025-54988/381427)