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  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  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  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  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  223 224 And note the **automatically created properties**: 225 226  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)