fix : Uncontrolled data used in path expression

Co-Authored-By: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
Sanaei 2026-02-07 22:34:06 +01:00 committed by MHSanaei
parent 491e3f9f8b
commit 5bb87fd3d4
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
2 changed files with 22 additions and 49 deletions

View file

@ -3,7 +3,6 @@ package job
import ( import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"log" "log"
"os" "os"
@ -388,7 +387,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
// disconnectClientTemporarily removes and re-adds a client to force disconnect old connections // disconnectClientTemporarily removes and re-adds a client to force disconnect old connections
func (j *CheckClientIpJob) disconnectClientTemporarily(inbound *model.Inbound, clientEmail string, clients []model.Client) { func (j *CheckClientIpJob) disconnectClientTemporarily(inbound *model.Inbound, clientEmail string, clients []model.Client) {
var xrayAPI xray.XrayAPI var xrayAPI xray.XrayAPI
// Get panel settings for API port // Get panel settings for API port
db := database.GetDB() db := database.GetDB()
var apiPort int var apiPort int
@ -396,7 +395,7 @@ func (j *CheckClientIpJob) disconnectClientTemporarily(inbound *model.Inbound, c
if err := db.Where("key = ?", "xrayApiPort").First(&apiPortSetting).Error; err == nil { if err := db.Where("key = ?", "xrayApiPort").First(&apiPortSetting).Error; err == nil {
apiPort, _ = strconv.Atoi(apiPortSetting.Value) apiPort, _ = strconv.Atoi(apiPortSetting.Value)
} }
if apiPort == 0 { if apiPort == 0 {
apiPort = 10085 // Default API port apiPort = 10085 // Default API port
} }

View file

@ -1056,35 +1056,23 @@ func (s *ServerService) IsValidGeofileName(filename string) bool {
} }
func (s *ServerService) UpdateGeofile(fileName string) error { func (s *ServerService) UpdateGeofile(fileName string) error {
files := []struct { type geofileEntry struct {
URL string URL string
FileName string FileName string
}{ }
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"}, geofileAllowlist := map[string]geofileEntry{
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"}, "geoip.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"}, "geosite.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"}, "geoip_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"}, "geosite_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"}, "geoip_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
"geosite_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 // Strict allowlist check to avoid writing uncontrolled files
if fileName != "" { if fileName != "" {
// Use the centralized validation function if _, ok := geofileAllowlist[fileName]; !ok {
if !s.IsValidGeofileName(fileName) { return common.NewErrorf("Invalid geofile name: %q not in allowlist", fileName)
return common.NewErrorf("Invalid geofile name: contains unsafe path characters: %s", fileName)
}
// Ensure the filename matches exactly one from our allowlist
isAllowed := false
for _, file := range files {
if fileName == file.FileName {
isAllowed = true
break
}
}
if !isAllowed {
return common.NewErrorf("Invalid geofile name: %s not in allowlist", fileName)
} }
} }
@ -1159,32 +1147,18 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
var errorMessages []string var errorMessages []string
if fileName == "" { if fileName == "" {
for _, file := range files { // Download all geofiles
// Sanitize the filename from our allowlist as an extra precaution for _, entry := range geofileAllowlist {
destPath := filepath.Join(config.GetBinFolderPath(), filepath.Base(file.FileName)) destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
if err := downloadFile(file.URL, destPath); err != nil { if err := downloadFile(entry.URL, destPath); err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", file.FileName, err)) errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
} }
} }
} else { } else {
// Use filepath.Base to ensure we only get the filename component, no path traversal entry := geofileAllowlist[fileName]
safeName := filepath.Base(fileName) destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
destPath := filepath.Join(config.GetBinFolderPath(), safeName) if err := downloadFile(entry.URL, destPath); err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
var fileURL string
for _, file := range files {
if file.FileName == fileName {
fileURL = file.URL
break
}
}
if fileURL == "" {
errorMessages = append(errorMessages, fmt.Sprintf("File '%s' not found in the list of Geofiles", fileName))
} else {
if err := downloadFile(fileURL, destPath); err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", fileName, err))
}
} }
} }