Leak password hash of any user
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.
| 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
"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- github.com/advisories/GHSA-87f6-8gr7-pc6hghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-37916ghsaADVISORY
- drive.google.com/file/d/1ksdawJ1vShRJyT3wAgpqVmz-Ls6hMA7M/previewghsaWEB
- github.com/1Panel-dev/KubePi/releases/tag/v1.6.5ghsaWEB
- github.com/1Panel-dev/KubePi/security/advisories/GHSA-87f6-8gr7-pc6hghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.