Listen to this Post
The VM built-in function KleverUpdateAccountPermission is always-active and rewrites an account’s entire permission set.
It is registered in creator.go lines 381-390 and core/vmconstants.go line 234.
Its authorization check uses vmInput.RecipientAddr, which is attacker-controlled.
The check should use the authenticated vmInput.CallerAddr.
The sibling handler kleverChangeOwnerAddress.go line 86 uses vmInput.CallerAddr correctly.
The safe pattern exists in-repo, but this handler deviates.
The native transaction path txProcess.go line 833 is safe because it uses tx.GetSender().
CallerAddr is never referenced in the vulnerable handler.
Authorization calls contractHasValidPermission(target.GetPermissions(), RecipientAddr).
That function returns true if RecipientAddr is a signer with Weight >= Threshold.
The permission must grant UpdateAccountPermissionContractType.
When a contract calls a built-in via ExecuteOnDestContextWithTypedArgs, baseOps.go line 1967 prepares indirect input.
prepareIndirectContractCallInput at baseOps.go line 2485 sets RecipientAddr = destination.
The destination is contract-chosen and attacker-controlled.
It sets CallerAddr = the calling contract.
The blockchain hook blockChainHook.go lines 454/467 dispatches on input.Function.
It passes the input through unchanged.
No guard forces RecipientAddr == CallerAddr.
There is no SC-destination validation on this path.
createDefaultOwnerPermission at accounts.go line 1848 makes an account its own signer.
The default signer has weight 1, threshold 1, Owner type.
CheckPermissionGrantedForContracts returns true for Owner.
Thus contractHasValidPermission(V.perms, V) == true.
More generally RecipientAddr can be any of V’s signer addresses meeting threshold.
Those addresses are public on-chain.
Accounts with no stored permissions have empty GetPermissions() and are immune.
UpdatePermission at accounts.go line 1863 replaces V’s permission set with attacker-supplied signers.
If the attacker supplies an Owner-type permission, no default is appended.
V’s prior control is fully evicted.
The attacker never held a key of V and provided no signature from V.
DailyCVE Form:
Platform: Klever Go
Version: Not Provided
Vulnerability: Permission Takeover
Severity: Critical
date: Not Provided
Prediction: Unknown Not Provided
(end of form)
What Undercode Say:
Analytics:
cd klever-go GOTOOLCHAIN=auto go test ./core/kapp/builtInFunctions/ -run TestPoC_PermTakeover -v
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
POC Code: https://gist.github.com/mabdullah22/a41f90aa5ba86bbebf121f739bd5f5e9
func (e kleverUpdateAccountPermission) ProcessBuiltinFunction(vmInput vmcommon.ContractCallInput) (vmcommon.VMOutput, error) {
...
address := vmInput.NextArg()
contract, err := e.getUpdateAccountPermissionContract(vmInput)
...
acc, err := e.accountsCacher.LoadUser(address)
...
if !e.contractHasValidPermission(acc.GetPermissions(), vmInput.RecipientAddr) { // L91
return nil, errors.New("invalid permission operation")
}
resultCode, err := e.kappController.GetAccountsKApp().UpdatePermission(address, contract)
...
}
func (e kleverUpdateAccountPermission) contractHasValidPermission(permissions []state.Permission, recipientAddr []byte) bool {
for _, permission := range permissions {
for _, signer := range permission.Signers {
if !bytes.Equal(signer.Address, recipientAddr) {
continue
}
if signer.Weight >= permission.Threshold &&
permission.CheckPermissionGrantedForContracts(transaction.TXContract_UpdateAccountPermissionContractType) {
return true
}
}
}
return false
}
contractCallInput := &vmcommon.ContractCallInput{
VMInput: vmcommon.VMInput{
CallerAddr: sender,
Arguments: data,
...
},
RecipientAddr: destination,
Function: string(function),
}
return &state.Permission{
Type: state.Permission_Owner,
Threshold: 1,
Signers: []state.Key{
{ Address: ownerAcc.AddressBytes(), Weight: 1 },
},
}
callerAddress := vmInput.CallerAddr
...
if !bytes.Equal(callerAddress, acc.GetOwnerAddress()) {
return nil, ErrOperationNotPermitted
}
ExecuteOnDestContext( gas, dest = V, value = 0, function = "KleverUpdateAccountPermission", args = [ V, attackerOwnerPermsWithOnlyAttackerKey ], )
how Exploit: (Educational Purposes!)
ExecuteOnDestContext( gas, dest = V, value = 0, function = "KleverUpdateAccountPermission", args = [ V, attackerOwnerPermsWithOnlyAttackerKey ], )
CallerAddr = attackerContract (ignored) RecipientAddr = V contractHasValidPermission(V.perms, V) == true V's permissions overwritten with attacker's key as sole Owner signer
Protection: from this CVE
if !e.contractHasValidPermission(acc.GetPermissions(), vmInput.CallerAddr) { ... }
Reconcile the SC-call authority model: on the built-in path CallerAddr is the calling contract, so a contract should only be able to update permissions of accounts that legitimately list it as an authorized signer — never an arbitrary victim.
Consider also requiring the target account (Arguments[bash]) to equal the authorized caller's account, matching the native tx.GetSender() model.
Impact:
Full takeover of any account that has configured permissions i.e. every multisig / advanced-permission account, by an attacker who deploys a cheap smart contract and supplies only public on-chain addresses (no keys, no signatures from the victim). After takeover the attacker controls all of the victim's operations → theft or permanent lock of all the account's assets. Reachable via a permissionlessly-deployed contract (the plain-tx path is safe, so it is Critical-via-contract, not fully no-contract). No fork flag gates it. Severity Critical: Impact High (full account/asset compromise)
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

