CVE-2022-22845: Use of Hard-coded Credentials (CWE-798)
value: QXIP SIPCAPTURE homer-app before 1.4.28 for HOMER 7.x has the same 167f0db2-f83e-4baa-9736-d56064a5b415 JWT secret key across different customers' installations.
CWE description: The product contains hard-coded credentials, such as a password or cryptographic key.
CVSS v3 base score: 9.8
Published: 2022-01-10T14:12Z
Repository: https://github.com/sipcapture/homer-app
Language: Go
Files changed: auth/claims.go, auth/constants.go, config/config.go, etc/webapp_config.json, main.go, version.go
Commit date: 2022-01-07 16:24:41 +0100
Commit message: added jwt generator.

Many thanks to Omri Baso and Fabien Aunay for the report! Great job guys!

Diff: diff --git a/auth/claims.go b/auth/claims.go
index 9f327e3..84284bd 100644
--- a/auth/claims.go
+++ b/auth/claims.go
@@ -2,10 +2,11 @@ package auth
 
 import (
 	"time"
 
 	"github.com/golang-jwt/jwt"
+	"github.com/sipcapture/homer-app/config"
 	"github.com/sipcapture/homer-app/model"
 	"github.com/sipcapture/homer-app/utils/logger"
 )
 
 // jwt token claims which contains info regarding user
@@ -47,11 +48,11 @@ func Token(user model.TableUser) (string, error) {
 
 	// Create token with claims
 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 
 	// Generate encoded token and send it as response.
-	t, err := token.SignedString([]byte(JwtSecret))
+	t, err := token.SignedString([]byte(config.Setting.AUTH_SETTINGS.JwtSecret))
 	if err != nil {
 		return "", err
 	}
 
 	return t, nil
diff --git a/auth/constants.go b/auth/constants.go
index 3cc43b8..9edd09a 100644
--- a/auth/constants.go
+++ b/auth/constants.go
@@ -1,9 +1,7 @@
 package auth
 
-const JwtSecret = "167f0db2-f83e-4baa-9736-d56064a5b415"
-
 /* our expire time */
 var TokenExpiryTime = 1200
 
 // token header
 const TokenHeader = "Auth-Token"
diff --git a/config/config.go b/config/config.go
index f780373..d332b4e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -42,10 +42,14 @@ type HomerSettingServer struct {
 
 	DASHBOARD_SETTINGS struct {
 		ExternalHomeDashboard string `default:""`
 	}
 
+	AUTH_SETTINGS struct {
+		JwtSecret string `default:""`
+	}
+
 	OAUTH2_SETTINGS struct {
 		Enable               bool     `default:"false"`
 		ClientID             string   `default:"1234565"`
 		ClientSecret         string   `default:"FAKE"`
 		ProjectID            string   `default:"Homer Oauth2"`
diff --git a/etc/webapp_config.json b/etc/webapp_config.json
index 67644d0..c02d3dd 100644
--- a/etc/webapp_config.json
+++ b/etc/webapp_config.json
@@ -101,10 +101,11 @@
     "dashboard_home": "/usr/local/homer/etc/dashboard_home.json"
   },
   "auth_settings": {
     "_comment": "The type param can be internal, ldap, http_auth",
     "type": "internal",
+    "jwt_secret": "167f0db2-f83e-4baa-9736-d56064a5b415",
     "gravatar": false,
     "gravatar_url": "https://www.gravatar.com/avatar/%s.jpg",
     "token_expire": 1200,
     "user_groups": ["admin", "user", "support"]
   },
diff --git a/main.go b/main.go
index c11beea..5cb911d 100644
--- a/main.go
+++ b/main.go
@@ -128,10 +128,11 @@ type CommandLineFlags struct {
 	LogPathWebApp             *string    `json:"path_log_webapp"`
 	LogName                   *string    `json:"log_name_webapp"`
 	APIPrefix                 *string    `json:"api_prefix"`
 	WatchConfig               *bool      `json:"watch_config"`
 	ShowCurrentConfig         *bool      `json:"show_current_config"`
+	GenerateJwtSecret         *bool      `json:"generate_jwt_secret"`
 }
 
 //params for  Services
 type ServicesObject struct {
 	configDBSession   *gorm.DB
@@ -193,10 +194,13 @@ func initFlags() {
 	appFlags.LogPathWebApp = flag.String("webapp-log-path", "", "the path for the log file.")
 	appFlags.APIPrefix = flag.String("webapp-api-prefix", "", "API prefix.")
 	appFlags.WatchConfig = flag.Bool("watch-config", false, "Watch the configuration for changes")
 	appFlags.ShowCurrentConfig = flag.Bool("show-current-config", false, "print out the current config and exit")
 
+	//Jwt
+	appFlags.GenerateJwtSecret = flag.Bool("generate-jwt-secret", false, "generate jwt secret")
+
 	flag.Parse()
 }
 
 func main() {
 
@@ -247,10 +251,23 @@ func main() {
 		migration.UpdateHomerUser(servicesObject.configDBSession, nameHomerConfig, *appFlags.UpdateUIUser, *appFlags.UpdateUIPassword)
 
 		os.Exit(0)
 	}
 
+	if *appFlags.GenerateJwtSecret {
+		logger.Info("Generating jwt secret...")
+		config.Setting.AUTH_SETTINGS.JwtSecret = uuid.NewV4().String()
+		viper.Set("auth_settings.jwt_secret", config.Setting.AUTH_SETTINGS.JwtSecret)
+		err := viper.WriteConfig()
+		if err != nil {
+			fmt.Println("No configuration file loaded: ", err)
+			logger.Error("No configuration file loaded - using defaults")
+		}
+
+		os.Exit(0)
+	}
+
 	//http client
 	initHttpClient()
 
 	configureServiceObjects()
 
@@ -865,11 +882,11 @@ func performV1APIRouting(e *echo.Echo) {
 	// restricted web services will fall in this group
 	res := e.Group(prefix + "/api/v3")
 	// Configure middleware with the custom claims type
 	config := middleware.JWTConfig{
 		Claims:     &auth.JwtUserClaim{},
-		SigningKey: []byte(auth.JwtSecret),
+		SigningKey: []byte(config.Setting.AUTH_SETTINGS.JwtSecret),
 	}
 
 	res.Use(middleware.JWTWithConfig(config))
 	res.Use(auth.MiddlewareRes)
 
@@ -1163,10 +1180,18 @@ func updateVersionApplication(configDBSession *gorm.DB) bool {
 		recordApp.GUID = uuid.NewV4().String()
 		viper.Set("system_settings.uuid", recordApp.GUID)
 		saveConfig = true
 	}
 
+	//generate JWT
+	config.Setting.AUTH_SETTINGS.JwtSecret = viper.GetString("auth_settings.jwt_secret")
+	if config.Setting.AUTH_SETTINGS.JwtSecret == "" {
+		config.Setting.AUTH_SETTINGS.JwtSecret = uuid.NewV4().String()
+		viper.Set("auth_settings.jwt_secret", config.Setting.AUTH_SETTINGS.JwtSecret)
+		saveConfig = true
+	}
+
 	if saveConfig {
 		err := viper.WriteConfig()
 		if err != nil {
 			fmt.Println("No configuration file loaded: ", err)
 			logger.Error("No configuration file loaded - using defaults")
diff --git a/version.go b/version.go
index 251bd4c..e07a2fb 100644
--- a/version.go
+++ b/version.go
@@ -1,9 +1,9 @@
 package main
 
 //VERSION
-var VERSION_APPLICATION = "1.4.27"
+var VERSION_APPLICATION = "1.4.28"
 
 //NAME
 var NAME_APPLICATION = "homer-app"
 
 func getVersion() string {

Vulnerable code: 	t, err := token.SignedString([]byte(JwtSecret))
const JwtSecret = "167f0db2-f83e-4baa-9736-d56064a5b415"

		SigningKey: []byte(auth.JwtSecret),
var VERSION_APPLICATION = "1.4.27"
Fixed code: 	"github.com/sipcapture/homer-app/config"
	t, err := token.SignedString([]byte(config.Setting.AUTH_SETTINGS.JwtSecret))
	AUTH_SETTINGS struct {
		JwtSecret string `default:""`
	}

    "jwt_secret": "167f0db2-f83e-4baa-9736-d56064a5b415",
	GenerateJwtSecret         *bool      `json:"generate_jwt_secret"`
	//Jwt
	appFlags.GenerateJwtSecret = flag.Bool("generate-jwt-secret", false, "generate jwt secret")

	if *appFlags.GenerateJwtSecret {
		logger.Info("Generating jwt secret...")
		config.Setting.AUTH_SETTINGS.JwtSecret = uuid.NewV4().String()
		viper.Set("auth_settings.jwt_secret", config.Setting.AUTH_SETTINGS.JwtSecret)
		err := viper.WriteConfig()
		if err != nil {
			fmt.Println("No configuration file loaded: ", err)
			logger.Error("No configuration file loaded - using defaults")
		}

		os.Exit(0)
	}

		SigningKey: []byte(config.Setting.AUTH_SETTINGS.JwtSecret),
	//generate JWT
	config.Setting.AUTH_SETTINGS.JwtSecret = viper.GetString("auth_settings.jwt_secret")
	if config.Setting.AUTH_SETTINGS.JwtSecret == "" {
		config.Setting.AUTH_SETTINGS.JwtSecret = uuid.NewV4().String()
		viper.Set("auth_settings.jwt_secret", config.Setting.AUTH_SETTINGS.JwtSecret)
		saveConfig = true
	}

var VERSION_APPLICATION = "1.4.28"