GHSA-r3hx-x5rh-p9vv django-haystack: Remote Code Execution via `eval()` in Elasticsearch Result Deserialization
Remote Code Execution via `eval()` in Elasticsearch Result DeserializationSummaryThe Elasticsearch backend in django-haystack calls `eval()` on raw field values returned from Elasticsearch when a `SearchField` is declared with an `index_fieldname` alias that differs from the logical field name. During result processing, the backend looks up fields by logical name but Elasticsearch stores them under the alias key; the lookup fails and the value falls through to `_to_python()` → `eval()`. An attacker who can control content that is indexed into Elasticsearch—and can trigger or wait for a search that returns it—achieves arbitrary code execution in the Django application process. CVSS 3.1 Base Score: **8.5 (High)**.Details**Sink — `haystack/backends/elasticsearch_backend.py:865`:**`_to_python()` (line ~850) attempts to parse a string value by calling `eval()` before performing any type-safety check. If the value is an attacker-controlled Python expression such as `__import__('os').system(...)`, the expression is executed unconditionally.**Root cause — `haystack/backends/elasticsearch_backend.py:727–737`:**`index.fields` is keyed by the *logical* field name (e.g. `"name"`), but Elasticsearch stores the document under the `index_fieldname` alias (e.g. `"name_s"`). Because `"name_s" not in index.fields`, the branch falls through to `self._to_python(value)`.**Data flow (source → sink):**`haystack/indexes.py:226` — `self.prepared_data[field.index_fieldname] = field.prepare(obj)` stores data under the alias.`haystack/backends/elasticsearch_backend.py:218` — prepared data copied into `final_data`.`haystack/backends/elasticsearch_backend.py:236` — `bulk(...)` writes the document to Elasticsearch under the alias key.`haystack/backends/elasticsearch_backend.py:574` — search reads attacker-influenced `_source` back from Elasticsearch.`haystack/backends/elasticsearch_backend.py:720` — `_process_results()` takes `raw_result["_source"]`.`haystack/backends/elasticsearch_backend.py:730` — lookup `string_key in index.fields` fails for alias keys.`haystack/backends/elasticsearch_backend.py:737` — unmatched value passed to `_to_python(value)`.`haystack/backends/elasticsearch_backend.py:865` — **sink**: `converted_value = eval(value)`.**Missing fix:** The Solr backend correctly remaps aliases at `haystack/backends/solr_backend.py:535–539` using `index.field_map` before performing the `index.fields` lookup. The Elasticsearch backend has no equivalent remapping.**Preconditions:**The application uses the Elasticsearch backend.At least one `SearchField` in a `SearchIndex` is declared with `index_fieldname` set to a value different from the logical attribute name.The attacker can write content that is indexed (e.g. via a form, API, or any user-controlled field included in the index).The attacker can trigger or wait for a search that returns the malicious document.PoC**Environment setup (Docker):****Dockerfile** (`vuln-001/Dockerfile`):**PoC script** (`vuln-001/poc.py`) — key sections:**Observed output (Phase 2 dynamic reproduction):****Recommended remediation:**ImpactThis is a **Remote Code Execution (RCE)** vulnerability. Any attacker who can submit content that is stored and indexed in Elasticsearch—then retrieved via a search—can execute arbitrary Python (and shell) commands in the Django application process with the privileges of the web server. Full confidentiality, integrity, and availability of the server are at risk. Because Haystack is a reusable search library, the vulnerability affects all Django applications that use the Elasticsearch backend with `index_fieldname` aliasing, regardless of how authentication is configured by the application.Reproduction artifacts`Dockerfile``poc.py`
CVSS v4.0 score: 8.7