# Klever-Go Account takeover: `kleverUpdateAccountPermission` authorizes on attacker-controlled `RecipientAddr` instead of the authenticated caller

Klever-Go's KleverUpdateAccountPermission built-in authorized on attacker-controlled RecipientAddr instead of CallerAddr, letting any deployed contract overwrite a victim account's permissions and seize its assets.

- Payout: Research
- Severity: high
- Program: Klever-Go
- Found by: mabdullah22
- Disclosed: 2026-09-23
- CVE: CVE-2026-82405
- Write-up quality: 9/10
- Original: https://github.com/advisories/GHSA-97cv-x867-6xhm
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-97cv-x867-6xhm
- Tags: area:web3 domain:oss domain:web3 impact:ato impact:funds-loss kind:research platform:ghsa sev:high tech:go technique:source-review vuln:access-control vuln:auth-bypass

**Target:** VM built-in function KleverUpdateAccountPermission in klever-go node
**Root cause:** The handler checks contractHasValidPermission against vmInput.RecipientAddr (contract-chosen destination) rather than the authenticated vmInput.CallerAddr; since every permissioned account is its own Owner signer, setting RecipientAddr to the victim satisfies the check.
**Steps:**
1. Deploy an attacker contract that calls `ExecuteOnDestContext(gas, dest=V, value=0, function="KleverUpdateAccountPermission", args=[V, attackerOwnerPermsWithOnlyAttackerKey])`
2. prepareIndirectContractCallInput sets `CallerAddr=attackerContract` (ignored) and `RecipientAddr=V` (attacker-chosen)
3. Handler loads account V and runs `contractHasValidPermission(V.GetPermissions(), vmInput.RecipientAddr)` — V's default Owner permission lists V itself as weight-1 signer >= threshold 1, so it returns true
4. `UpdatePermission(V, attackerContract)` replaces V's permission set with the attacker's Owner permission; no default appended, original owner evicted
5. Attacker now signs all of V's operations with their key
**How found:** Compared the handler against the sibling kleverChangeOwnerAddress (which correctly uses CallerAddr) and the safe native tx path (tx.GetSender()), noticed CallerAddr never referenced in the handler, then traced prepareIndirectContractCallInput to confirm RecipientAddr is contract-controlled and createDefaultOwnerPermission to confirm self-signer default satisfies the check.
**Exploitation:** Attacker deploys a cheap smart contract and invokes the always-active KleverUpdateAccountPermission built-in via ExecuteOnDestContext with dest set to the victim account and arguments [victim, attacker-supplied Owner permission]. No key or signature from the victim is needed; only public on-chain signer addresses. The victim's permission set is fully replaced with the attacker's key as sole Owner signer, granting control of all victim operations and assets.
**Escalation:** Any account with configured permissions (all multisig/advanced-permission accounts) is reachable; takeover yields theft or permanent lock of all account assets.
**Insight:** When auditing VM built-ins, diff each handler's authorization variable against sibling handlers and the native tx path; a handler that authorizes on RecipientAddr/destination instead of CallerAddr is bypassable because indirect contract calls let the caller set the destination freely. Also check whether default permission construction (self-as-signer) makes the wrong-variable check trivially pass.

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

**Attacker contract call overwriting victim permissions**

```
ExecuteOnDestContext(
    gas, dest = V,                       // → RecipientAddr = V
    value = 0,
    function = "KleverUpdateAccountPermission",
    args = [ V, attackerOwnerPermsWithOnlyAttackerKey ],   // Arguments[0]=V, Arguments[1]=new perms
)
```

**PoC test command and takeover output**

```
GOTOOLCHAIN=auto go test ./core/kapp/builtInFunctions/ -run TestPoC_PermTakeover -v
Output:
TAKEOVER CONFIRMED: caller="attacker-contract" (attacker SC) rewrote account V="victim-account-V"; new sole owner signer="attacker-key-EVIL"
--- PASS: TestPoC_PermTakeover
--- PASS: TestPoC_PermTakeover_NoStoredPermsIsSafe
```

