Browse Source

Merge pull request #3528 from MHSanaei/security

Security issue fixed
Sanaei 2 days ago
parent
commit
806ecbd7c5
5 changed files with 65 additions and 15 deletions
  1. 3 0
      .github/workflows/docker.yml
  2. 5 5
      config/config.go
  3. 1 1
      main.go
  4. 13 3
      util/random/random.go
  5. 43 6
      web/service/server.go

+ 3 - 0
.github/workflows/docker.yml

@@ -1,4 +1,7 @@
 name: Release 3X-UI for Docker
+permissions:
+  contents: read
+  packages: write
 on:
   workflow_dispatch:
   push:

+ 5 - 5
config/config.go

@@ -23,11 +23,11 @@ type LogLevel string
 
 // Logging level constants
 const (
-	Debug  LogLevel = "debug"
-	Info   LogLevel = "info"
-	Notice LogLevel = "notice"
-	Warn   LogLevel = "warn"
-	Error  LogLevel = "error"
+	Debug   LogLevel = "debug"
+	Info    LogLevel = "info"
+	Notice  LogLevel = "notice"
+	Warning LogLevel = "warning"
+	Error   LogLevel = "error"
 )
 
 // GetVersion returns the version string of the 3x-ui application.

+ 1 - 1
main.go

@@ -35,7 +35,7 @@ func runWebServer() {
 		logger.InitLogger(logging.INFO)
 	case config.Notice:
 		logger.InitLogger(logging.NOTICE)
-	case config.Warn:
+	case config.Warning:
 		logger.InitLogger(logging.WARNING)
 	case config.Error:
 		logger.InitLogger(logging.ERROR)

+ 13 - 3
util/random/random.go

@@ -2,7 +2,8 @@
 package random
 
 import (
-	"math/rand"
+	"crypto/rand"
+	"math/big"
 )
 
 var (
@@ -40,12 +41,21 @@ func init() {
 func Seq(n int) string {
 	runes := make([]rune, n)
 	for i := 0; i < n; i++ {
-		runes[i] = allSeq[rand.Intn(len(allSeq))]
+		idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(allSeq))))
+		if err != nil {
+			panic("crypto/rand failed: " + err.Error())
+		}
+		runes[i] = allSeq[idx.Int64()]
 	}
 	return string(runes)
 }
 
 // Num generates a random integer between 0 and n-1.
 func Num(n int) int {
-	return rand.Intn(n)
+	bn := big.NewInt(int64(n))
+	r, err := rand.Int(rand.Reader, bn)
+	if err != nil {
+		panic("crypto/rand failed: " + err.Error())
+	}
+	return int(r.Int64())
 }

+ 43 - 6
web/service/server.go

@@ -697,14 +697,39 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
 	var lines []string
 
 	if syslog == "true" {
-		cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
-		// Run the command
-		cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
+		// Check if running on Windows - journalctl is not available
+		if runtime.GOOS == "windows" {
+			return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
+		}
+
+		// Validate and sanitize count parameter
+		countInt, err := strconv.Atoi(count)
+		if err != nil || countInt < 1 || countInt > 10000 {
+			return []string{"Invalid count parameter - must be a number between 1 and 10000"}
+		}
+
+		// Validate level parameter - only allow valid syslog levels
+		validLevels := map[string]bool{
+			"0": true, "emerg": true,
+			"1": true, "alert": true,
+			"2": true, "crit": true,
+			"3": true, "err": true,
+			"4": true, "warning": true,
+			"5": true, "notice": true,
+			"6": true, "info": true,
+			"7": true, "debug": true,
+		}
+		if !validLevels[level] {
+			return []string{"Invalid level parameter - must be a valid syslog level"}
+		}
+
+		// Use hardcoded command with validated parameters
+		cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
 		var out bytes.Buffer
 		cmd.Stdout = &out
-		err := cmd.Run()
+		err = cmd.Run()
 		if err != nil {
-			return []string{"Failed to run journalctl command!"}
+			return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
 		}
 		lines = strings.Split(out.String(), "\n")
 	} else {
@@ -983,7 +1008,19 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
 		{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
 		{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
 	}
-
+	// Strict allowlist check to avoid writing uncontrolled files
+	if fileName != "" {
+		isAllowed := false
+		for _, file := range files {
+			if fileName == file.FileName {
+				isAllowed = true
+				break
+			}
+		}
+		if !isAllowed {
+			return common.NewErrorf("Invalid geofile name: %s", fileName)
+		}
+	}
 	downloadFile := func(url, destPath string) error {
 		resp, err := http.Get(url)
 		if err != nil {