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

Leak password hash of any user

CVE-2023-37916

Description

KubePi password hash disclosure via users search endpoint allows attackers to crack credentials and gain admin access.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

KubePi password hash disclosure via users search endpoint allows attackers to crack credentials and gain admin access.

Vulnerability

Overview

CVE-2023-37916 is an information disclosure vulnerability in KubePi, an open-source Kubernetes management panel. The endpoint /kubepi/api/v1/users/search?pageNum=1&&pageSize=10 leaks the password hash of any user, including the admin account [1]. This occurs because the application returns sensitive authentication data in the API response without proper access controls.

Exploitation

Prerequisites

An attacker can exploit this vulnerability by sending an HTTP GET request to the vulnerable endpoint, which does not require prior authentication or special privileges [4]. The endpoint is accessible over the network, and the only requirement is that KubePi is reachable. The attacker receives the password hashes in the response, which can then be extracted for offline cracking.

Impact

A successful attacker can crack the stolen password hashes using standard techniques (e.g., dictionary or brute-force attacks). Gaining access to any user's account, especially the admin, provides full control over the KubePi panel and consequently over the managed Kubernetes clusters [1][4]. This could lead to data theft, service disruption, or further compromise of the underlying infrastructure.

Mitigation

The vulnerability is fixed in KubePi version 1.6.5 [2]. Users must upgrade to this release immediately. There are no known workarounds [1]. Organizations should also consider rotating credentials for any accounts that may have been exposed if an older version was in use.

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

"The /kubepi/api/v1/users/search endpoint returns password hash fields in its response without restricting access to authorized users only."

Attack vector

An attacker sends a GET request to `/kubepi/api/v1/users/search?pageNum=1&&pageSize=10` without any special authentication beyond a valid user session. The endpoint returns a list of users including the password hash (likely bcrypt or similar) for each user, including the administrator. An attacker can then attempt offline cracking of the leaked hashes to recover plaintext passwords [CWE-200].

Affected code

The vulnerability is in the `/kubepi/api/v1/users/search` endpoint, which returns password hashes in its response. The patch file `web/dashboard/src/components/ko-workloads/ko-spec/ko-pod-scheduling.vue` is unrelated to this issue — it fixes a node affinity configuration bug. The advisory does not specify the exact backend file or function where the sensitive data leak occurs.

What the fix does

The supplied patch [patch_id=1640675] does not address the password hash leak — it fixes a Kubernetes pod scheduling affinity form in the frontend. The advisory states the fix was released in version 1.6.5, but the corresponding backend patch is not included in this bundle. The fix likely involves modifying the `/kubepi/api/v1/users/search` endpoint to exclude the password hash field from the API response, or to require administrator-level authorization before returning user credential data.

Preconditions

  • authAttacker must have a valid authenticated session on the KubePi panel.
  • networkThe /kubepi/api/v1/users/search endpoint must be reachable over the network.

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.