CVE-2023-22463: Use of Hard-coded Credentials (CWE-798)
value: KubePi is a k8s panel. The jwt authentication function of KubePi through version 1.6.2 uses hard-coded Jwtsigkeys, resulting in the same Jwtsigkeys for all online projects. This means that an attacker can forge any jwt token to take over the administrator account of any online project. Furthermore, they may use the administrator to take over the k8s cluster of the target enterprise. `session.go`, the use of hard-coded JwtSigKey, allows an attacker to use this value to forge jwt tokens arbitrarily. The JwtSigKey is confidential and should not be hard-coded in the code. The vulnerability has been fixed in 1.6.3. In the patch, JWT key is specified in app.yml. If the user leaves it blank, a random key will be used. There are no workarounds aside from upgrading.
CWE description: The product contains hard-coded credentials, such as a password or cryptographic key.
CVSS v3 base score: 9.8
Published: 2023-01-04T16:15Z
Repository: https://github.com/KubeOperator/KubePi
Language: Go
Files changed: conf/app.yml, internal/api/v1/session/session.go, internal/api/v1/v1.go, internal/config/config.go, internal/model/v1/config/config.go, internal/server/server.go
Commit date: 2023-01-04 17:28:22 +0800
Commit message: fix: 解决 jwt 硬编码导致的 k8s 集群接管漏洞

Diff: diff --git a/conf/app.yml b/conf/app.yml
index 57041b1d..3146c6f1 100644
--- a/conf/app.yml
+++ b/conf/app.yml
@@ -10,6 +10,8 @@ spec:
       certificate:
       certificateKey:
   db:
     path: /var/lib/kubepi/db/kubepi.db
   session:
-    expires: 24
\ No newline at end of file
+    expires: 24
+  jwt:
+    key:
\ No newline at end of file
diff --git a/internal/api/v1/session/session.go b/internal/api/v1/session/session.go
index 45888aa4..59914019 100644
--- a/internal/api/v1/session/session.go
+++ b/internal/api/v1/session/session.go
@@ -30,11 +30,10 @@ import (
 	"golang.org/x/crypto/bcrypt"
 	v1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-var JwtSigKey = []byte("signature_hmac_secret_shared_key")
 var jwtMaxAge = 10 * time.Minute
 
 type Handler struct {
 	userService        user.Service
 	roleService        role.Service
@@ -49,11 +48,11 @@ func NewHandler() *Handler {
 		clusterService:     cluster.NewService(),
 		userService:        user.NewService(),
 		roleService:        role.NewService(),
 		rolebindingService: rolebinding.NewService(),
 		ldapService:        ldap.NewService(),
-		jwtSigner:          jwt.NewSigner(jwt.HS256, JwtSigKey, jwtMaxAge),
+		jwtSigner:          jwt.NewSigner(jwt.HS256, server.Config().Spec.Jwt.Key, jwtMaxAge),
 	}
 }
 
 func (h *Handler) IsLogin() iris.Handler {
 	return func(ctx *context.Context) {
diff --git a/internal/api/v1/v1.go b/internal/api/v1/v1.go
index 68855088..176e366f 100644
--- a/internal/api/v1/v1.go
+++ b/internal/api/v1/v1.go
@@ -399,12 +399,11 @@ func resourceNameInvalidHandler() iris.Handler {
 		ctx.Next()
 	}
 }
 
 func WarpedJwtHandler() iris.Handler {
-
-	verifier := jwt.NewVerifier(jwt.HS256, session.JwtSigKey)
+	verifier := jwt.NewVerifier(jwt.HS256, server.Config().Spec.Jwt.Key)
 	verifier.WithDefaultBlocklist()
 	verifyMiddleware := verifier.Verify(func() interface{} {
 		return new(session.UserProfile)
 	})
 	return func(ctx *context.Context) {
diff --git a/internal/config/config.go b/internal/config/config.go
index 5342c045..ab9c40a2 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -1,25 +1,28 @@
 package config
 
 import (
+	"crypto/rand"
 	"encoding/json"
 	"fmt"
 	"github.com/KubeOperator/kubepi/internal/model/v1/config"
 	"github.com/KubeOperator/kubepi/pkg/file"
 	"github.com/coreos/etcd/pkg/fileutil"
 	"github.com/spf13/viper"
+	"math/big"
+	"strconv"
 )
 
 const configNotFoundSkipErr = "config file not found in %s, skip"
 const configReadErr = "can not read config file %s ,%s"
 const configMergeErr = "can not merge config file, %s"
 
 var configFilePaths = []string{
 	"/etc/kubepi",
 }
 
-func ReadConfig(c *config.Config, path ...string)  error {
+func ReadConfig(c *config.Config, path ...string) error {
 	v := viper.New()
 	v.SetConfigName("app")
 	v.SetConfigType("yaml")
 
 	for i := range path {
@@ -39,21 +42,38 @@ func ReadConfig(c *config.Config, path ...string)  error {
 			continue
 		}
 		if err := v.MergeInConfig(); err != nil {
 			fmt.Println(fmt.Sprintf(configMergeErr, configFilePaths))
 		}
+
 	}
 
 	var configMap map[string]interface{}
 	if err := v.Unmarshal(&configMap); err != nil {
-		return  err
+		return err
 	}
 	str, err := json.Marshal(&configMap)
 	if err != nil {
-		return  err
+		return err
 	}
 	if err := json.Unmarshal(str, &c); err != nil {
-		return  nil
+		return nil
+	}
+	if c.Spec.Jwt.Key == "" {
+		v.Set("spec.jwt.key", generate(32))
+		if err := v.WriteConfig(); err != nil {
+			return err
+		}
 	}
-	return  nil
+	return nil
 }
 
+func generate(length int) string {
+	const base = 36
+	size := big.NewInt(base)
+	n := make([]byte, length)
+	for i := range n {
+		c, _ := rand.Int(rand.Reader, size)
+		n[i] = strconv.FormatInt(c.Int64(), base)[0]
+	}
+	return string(n)
+}
diff --git a/internal/model/v1/config/config.go b/internal/model/v1/config/config.go
index 37221bc5..c377532c 100644
--- a/internal/model/v1/config/config.go
+++ b/internal/model/v1/config/config.go
@@ -10,10 +10,11 @@ type Config struct {
 type Spec struct {
 	Server  ServerConfig  `json:"server"`
 	DB      DBConfig      `json:"db"`
 	Session SessionConfig `json:"session"`
 	Logger  LoggerConfig  `json:"logger"`
+	Jwt     JwtConfig     `json:"jwt"`
 	AppId   string        `json:"appId"`
 }
 
 type ServerConfig struct {
 	Bind BindConfig `json:"bind"`
@@ -40,5 +41,9 @@ type DBConfig struct {
 }
 
 type SessionConfig struct {
 	Expires int `json:"expires"`
 }
+
+type JwtConfig struct {
+	Key string `json:"key"`
+}
diff --git a/internal/server/server.go b/internal/server/server.go
index b70e3014..1949393e 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -338,8 +338,9 @@ func getDefaultConfig() *v1Config.Config {
 			},
 			Session: v1Config.SessionConfig{
 				Expires: 72,
 			},
 			Logger: v1Config.LoggerConfig{Level: "debug"},
+			Jwt:    v1Config.JwtConfig{},
 		},
 	}
 }

Vulnerable code:     expires: 24
var JwtSigKey = []byte("signature_hmac_secret_shared_key")
		jwtSigner:          jwt.NewSigner(jwt.HS256, JwtSigKey, jwtMaxAge),

	verifier := jwt.NewVerifier(jwt.HS256, session.JwtSigKey)
func ReadConfig(c *config.Config, path ...string)  error {
		return  err
		return  err
		return  nil
	return  nil
Fixed code:     expires: 24
  jwt:
    key:
		jwtSigner:          jwt.NewSigner(jwt.HS256, server.Config().Spec.Jwt.Key, jwtMaxAge),
	verifier := jwt.NewVerifier(jwt.HS256, server.Config().Spec.Jwt.Key)
	"crypto/rand"
	"math/big"
	"strconv"
func ReadConfig(c *config.Config, path ...string) error {

		return err
		return err
		return nil
	}
	if c.Spec.Jwt.Key == "" {
		v.Set("spec.jwt.key", generate(32))
		if err := v.WriteConfig(); err != nil {
			return err
		}
	return nil
func generate(length int) string {
	const base = 36
	size := big.NewInt(base)
	n := make([]byte, length)
	for i := range n {
		c, _ := rand.Int(rand.Reader, size)
		n[i] = strconv.FormatInt(c.Int64(), base)[0]
	}
	return string(n)
}
	Jwt     JwtConfig     `json:"jwt"`

type JwtConfig struct {
	Key string `json:"key"`
}
			Jwt:    v1Config.JwtConfig{},