VYPR
Critical severityNVD Advisory· Published Jul 21, 2023· Updated Oct 10, 2024

Privilege Escalation in kubepi

CVE-2023-37917

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.

PackageAffected versionsPatched versions
github.com/KubeOperator/kubepiGo
< 1.6.51.6.5

Affected products

2

Patches

1
1877e5349b55

fix: 解决配置节点亲和性无法保存的问题

https://github.com/1panel-dev/kubepissongliuJul 21, 2023via osv
1 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

News mentions

0

No linked articles in our index yet.