Privilege Escalation in kubepi
Description
In KubePi < 1.6.5, any user with create/update permissions can set the isadmin flag in a request, thereby escalating their privileges to full administrative control.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In KubePi < 1.6.5, any user with create/update permissions can set the `isadmin` flag in a request, thereby escalating their privileges to full administrative control.
Vulnerability
Overview
A privilege escalation vulnerability exists in KubePi, an open-source Kubernetes management panel. In versions prior to 1.6.5, the API that handles user creation and updates does not properly validate or sanitize the isadmin field. A normal user who has been granted permission to create or update other users can arbitrarily set the isadmin value to true in the request payload. This flaw allows any such user to promote themselves or another user to an administrator role without further authorization [1][4].
Exploitation and
Prerequisites
The attack is performed remotely without requiring any special network position, as the vulnerable endpoint is accessible to authenticated users. The only prerequisite is that the attacker must have a valid account with the standard 'create/update users' permission. No additional privileges or user interaction on the victim side are needed. By sending a crafted HTTP request to the user management API with isadmin set to true, the attacker can escalate their own account's privileges or create a new administrative account [4].
Impact
Successful exploitation gives the attacker full administrative control over the KubePi instance. As an admin, they can manage all Kubernetes clusters imported into KubePi, including creating, modifying, and deleting resources across all namespaces. This can lead to a complete compromise of the managed Kubernetes infrastructure, including data exfiltration, service disruption, and lateral movement within the cluster [1][4].
Mitigation
The issue has been addressed in KubePi version 1.6.5, released on July 21, 2023 [2]. Users should upgrade to this version immediately. There are no known workarounds for this vulnerability [1]. Organizations using earlier versions should treat this as a high-priority security update to prevent potential unauthorized administrative access.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/KubeOperator/kubepiGo | < 1.6.5 | 1.6.5 |
Affected products
2- Range: < 1.6.5
Patches
11 file changed · +50 −52
web/dashboard/src/components/ko-workloads/ko-spec/ko-pod-scheduling.vue+50 −52 modified@@ -19,11 +19,6 @@ </el-col> </el-row> <el-row :gutter="20"> - <el-col :span="12"> - <el-form-item label="Namespace"> - <ko-form-item itemType="select2" v-model="item.namespaces" multiple :selections="namespace_list" /> - </el-form-item> - </el-col> <el-col :span="12" v-if="item.priority === 'Preferred'"> <el-form-item :label="$t('business.workload.weight')"> <ko-form-item itemType="number" v-model="item.weight" /> @@ -151,7 +146,6 @@ export default { var item = { type: "Affinity", priority: "Preferred", - namespaces: "", weight: 1, rules: [], labelRules: [], @@ -208,7 +202,6 @@ export default { }, valueTrans(type, priority, s) { - let namespaces = s.namespaces || "" let rules = [] if (s.labelSelector.matchExpressions) { for (const express of s.labelSelector.matchExpressions) { @@ -231,7 +224,6 @@ export default { type: type, priority: priority, weight: s.weight || null, - namespaces: namespaces || "", rules: rules, labelRules: labelRules, topologyKey: topologyKey, @@ -263,8 +255,7 @@ export default { if (this.podSchedulings.length !== 0) { for (const pS of this.podSchedulings) { let itemAdd = {} - itemAdd.namespaces = (pS.namespaces && pS.namespaces.length !== 0) ? pS.namespaces : undefined - itemAdd.topologyKey = pS.topologyKey || undefined + const itemTopologyKey = pS.topologyKey || undefined const matchs = this.getMatchExpress(pS.rules) const labelMatchs = this.getMatchLabels(pS.labelRules) switch (pS.type + "+" + pS.priority) { @@ -274,15 +265,16 @@ export default { } parentFrom.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution = [] itemAdd.labelSelector = { matchExpressions: matchs, matchLabels: labelMatchs } + itemAdd.topologyKey = itemTopologyKey parentFrom.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.push(itemAdd) break case "Affinity+Preferred": if (!parentFrom.affinity.podAffinity) { parentFrom.affinity.podAffinity = {} } parentFrom.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution = [] - itemAdd.podAffinityTerm = { labelSelector: { matchExpressions: matchs, matchLabels: labelMatchs } } - itemAdd.weight = pS.weight + itemAdd.podAffinityTerm = { topologyKey: itemTopologyKey, labelSelector: { matchExpressions: matchs, matchLabels: labelMatchs } } + itemAdd.weight = pS.weight || 1 parentFrom.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution.push(itemAdd) break case "Anti-Affinity+Required": @@ -291,41 +283,47 @@ export default { } parentFrom.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution = [] itemAdd.labelSelector = { matchExpressions: matchs, matchLabels: labelMatchs } + itemAdd.topologyKey = itemTopologyKey parentFrom.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution.push(itemAdd) break case "Anti-Affinity+Preferred": if (!parentFrom.affinity.podAntiAffinity) { parentFrom.affinity.podAntiAffinity = {} } parentFrom.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution = [] - itemAdd.podAffinityTerm = { labelSelector: { matchExpressions: matchs, matchLabels: labelMatchs } } - itemAdd.weight = pS.weight + itemAdd.podAffinityTerm = { topologyKey: itemTopologyKey, labelSelector: { matchExpressions: matchs, matchLabels: labelMatchs } } + itemAdd.weight = pS.weight || 1 parentFrom.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution.push(itemAdd) break } } } - parentFrom.nodeAffinity = {} if (this.nodeSchedulings.length !== 0) { for (const nS of this.nodeSchedulings) { const matchs = this.getMatchExpress(nS.rules) const fields = this.getMatchExpress(nS.fields) let itemAdd = {} switch (nS.priority) { case "Preferred": - parentFrom.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution = [] + if (!parentFrom.affinity.nodeAffinity) { + parentFrom.affinity.nodeAffinity = {} + } + parentFrom.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution = [] itemAdd.weight = nS.weight itemAdd.preference = { matchExpressions: matchs, matchFields: fields } - parentFrom.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.push(itemAdd) + parentFrom.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.push(itemAdd) break case "Required": - if (!parentFrom.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution) { - parentFrom.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution = {} + if (!parentFrom.affinity.nodeAffinity) { + parentFrom.affinity.nodeAffinity = {} } - parentFrom.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms = [] + if (!parentFrom.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution) { + parentFrom.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution = {} + } + parentFrom.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms = [] itemAdd.matchExpressions = matchs itemAdd.matchFields = fields - parentFrom.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.push(itemAdd) + parentFrom.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.push(itemAdd) break } } @@ -369,47 +367,47 @@ export default { } } } - } - if (this.podSchedulingParentObj.nodeAffinity) { - if (this.podSchedulingParentObj.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution) { - if (this.podSchedulingParentObj.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms) { - const schedulings = this.podSchedulingParentObj.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms - for (const s of schedulings) { - let rules = [] - if (s.matchExpressions) { - for (const express of s.matchExpressions) { - rules.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + if (this.podSchedulingParentObj.affinity.nodeAffinity) { + if (this.podSchedulingParentObj.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution) { + if (this.podSchedulingParentObj.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms) { + const schedulings = this.podSchedulingParentObj.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms + for (const s of schedulings) { + let rules = [] + if (s.matchExpressions) { + for (const express of s.matchExpressions) { + rules.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + } } - } - let fields = [] - if (s.matchFields) { - for (const express of s.matchFields) { - fields.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + let fields = [] + if (s.matchFields) { + for (const express of s.matchFields) { + fields.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + } } + this.nodeSchedulings.push({ priority: "Required", rules: rules, fields: fields }) } - this.nodeSchedulings.push({ priority: "Required", rules: rules, fields: fields }) } } - } - if (this.podSchedulingParentObj.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution) { - const schedulings = this.podSchedulingParentObj.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution - for (const s of schedulings) { - let rules = [] - let fields = [] - if (s.preference) { - if (s.preference.matchExpressions) { - for (const express of s.preference.matchExpressions) { - rules.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + if (this.podSchedulingParentObj.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution) { + const schedulings = this.podSchedulingParentObj.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution + for (const s of schedulings) { + let rules = [] + let fields = [] + if (s.preference) { + if (s.preference.matchExpressions) { + for (const express of s.preference.matchExpressions) { + rules.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + } } - } - if (s.preference.matchFields) { - for (const express of s.preference.matchFields) { - fields.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + if (s.preference.matchFields) { + for (const express of s.preference.matchFields) { + fields.push({ key: express.key, operator: express.operator, value: express.values.join(",") }) + } } } + this.nodeSchedulings.push({ priority: "Preferred", rules: rules, fields: fields, weight: s.weight || null }) } - this.nodeSchedulings.push({ priority: "Preferred", rules: rules, fields: fields, weight: s.weight || null }) } } }
Vulnerability mechanics
Root cause
"Missing server-side authorization check allows any user to set the `isadmin` flag on user accounts."
Attack vector
An attacker who already has a normal user account on KubePi sends a crafted HTTP request to the user creation or update API endpoint, including `"isadmin": true` in the JSON body. The server does not validate whether the requester has the right to grant admin privileges, so the request succeeds and the attacker's account is elevated to administrator [CWE-269]. No special network position is required beyond access to the KubePi web interface or API.
Affected code
The vulnerability is not in the supplied patch file; the patch [patch_id=1640674] only fixes a front-end pod-scheduling component (`ko-pod-scheduling.vue`) unrelated to privilege management. The advisory states that a normal user can set the `isadmin` field when creating or updating users, but no server-side code or API endpoint is shown in the bundle.
What the fix does
The supplied patch [patch_id=1640674] does not address the privilege escalation. It only refactors the front-end pod-scheduling Vue component by removing a `namespaces` field and correcting the data path for `nodeAffinity` properties. The actual fix for CVE-2023-37917 would require server-side authorization checks that prevent non-admin users from setting the `isadmin` flag on user objects. The advisory confirms the fix was released in version 1.6.5, but the relevant server-side code is not included in this bundle.
Preconditions
- authAttacker must have a valid normal user account on KubePi.
- networkAttacker must be able to send HTTP requests to the user creation or update API endpoint.
- configThe server must not enforce authorization checks on the isadmin field during user create/update operations.
Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-757p-vx43-fp9rghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-37917ghsaADVISORY
- drive.google.com/file/d/1e8XJbIFIDXaFiL-dqn0a0b6u7o3CwqSG/previewghsaWEB
- github.com/1Panel-dev/KubePi/releases/tag/v1.6.5ghsaWEB
- github.com/1Panel-dev/KubePi/security/advisories/GHSA-757p-vx43-fp9rghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.