From 37932d7fb2d8ec50eb2bea1ebf8269fb0531c404 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:01:47 +0800 Subject: [PATCH] chore: Complete system security upgrade (#11961) --- agent/app/api/v2/container.go | 21 ---- agent/app/service/container.go | 40 +------ agent/app/service/database_common.go | 5 + agent/app/service/device.go | 16 +-- agent/app/service/disk.go | 19 +-- agent/app/service/file.go | 15 ++- agent/app/service/host_tool.go | 4 + agent/app/service/mcp_server.go | 21 +++- agent/app/service/runtime.go | 6 +- agent/app/service/ssh.go | 62 ++++++++-- agent/app/service/website.go | 26 +++- agent/app/service/website_auth_basic.go | 21 +++- agent/app/service/website_lb.go | 23 +++- agent/app/service/website_proxy.go | 30 ++++- agent/app/service/website_rewrite.go | 15 ++- agent/cron/cron.go | 15 ++- agent/router/ro_container.go | 1 - agent/utils/common/common.go | 33 +++--- agent/utils/compose/compose.go | 18 +-- agent/utils/encrypt/encrypt.go | 26 +++- agent/utils/nginx/validate.go | 102 ++++++++++++++++ agent/utils/re/re.go | 4 + core/app/dto/setting.go | 1 - core/app/service/script_library.go | 15 ++- core/cmd/server/cmd/update.go | 2 +- core/middleware/api_auth.go | 23 ++-- core/utils/common/common.go | 26 ++-- core/utils/encrypt/encrypt.go | 25 +++- frontend/src/api/modules/container.ts | 3 - .../container/container/command/index.vue | 111 ------------------ 30 files changed, 435 insertions(+), 294 deletions(-) create mode 100644 agent/utils/nginx/validate.go delete mode 100644 frontend/src/views/container/container/command/index.vue diff --git a/agent/app/api/v2/container.go b/agent/app/api/v2/container.go index 9d8d26e0e..c031b2952 100644 --- a/agent/app/api/v2/container.go +++ b/agent/app/api/v2/container.go @@ -306,27 +306,6 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) { helper.Success(c) } -// @Tags Container -// @Summary Create container by command -// @Accept json -// @Param request body dto.ContainerCreateByCommand true "request" -// @Success 200 -// @Security ApiKeyAuth -// @Security Timestamp -// @Router /containers/command [post] -func (b *BaseApi) ContainerCreateByCommand(c *gin.Context) { - var req dto.ContainerCreateByCommand - if err := helper.CheckBindAndValidate(&req, c); err != nil { - return - } - - if err := containerService.ContainerCreateByCommand(req); err != nil { - helper.InternalServer(c, err) - return - } - helper.Success(c) -} - // @Tags Container // @Summary Upgrade container // @Accept json diff --git a/agent/app/service/container.go b/agent/app/service/container.go index d7f4789ca..3856b6a4b 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -43,7 +43,6 @@ import ( "github.com/docker/go-connections/nat" "github.com/gin-gonic/gin" v1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/mem" ) @@ -69,7 +68,6 @@ type IContainerService interface { ComposeLogClean(req dto.ComposeLogClean) error ContainerCreate(req dto.ContainerOperate, inThread bool) error - ContainerCreateByCommand(req dto.ContainerCreateByCommand) error ContainerUpdate(req dto.ContainerOperate) error ContainerUpgrade(req dto.ContainerUpgrade) error ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) @@ -104,7 +102,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro if err != nil { return 0, nil, err } - defer client.Close() + defer func() { _ = client.Close() }() options := container.ListOptions{All: true} if len(req.Filters) != 0 { options.Filters = filters.NewArgs() @@ -302,42 +300,6 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error return datas, nil } -func (u *ContainerService) ContainerCreateByCommand(req dto.ContainerCreateByCommand) error { - if cmd.CheckIllegal(req.Command) { - return buserr.New("ErrCmdIllegal") - } - if !strings.HasPrefix(strings.TrimSpace(req.Command), "docker run ") { - return errors.New("error command format") - } - containerName := "" - commands := strings.Split(req.Command, " ") - for index, val := range commands { - if val == "--name" && len(commands) > index+1 { - containerName = commands[index+1] - } - } - if !strings.Contains(req.Command, " -d ") { - req.Command = strings.ReplaceAll(req.Command, "docker run", "docker run -d") - } - if len(containerName) == 0 { - containerName = fmt.Sprintf("1Panel-%s-%s", common.RandStr(5), common.RandStrAndNum(4)) - req.Command += fmt.Sprintf(" --name %s", containerName) - } - taskItem, err := task.NewTaskWithOps(containerName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1) - if err != nil { - global.LOG.Errorf("new task for create container failed, err: %v", err) - return err - } - go func() { - taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", containerName), func(t *task.Task) error { - cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem), cmd.WithTimeout(5*time.Minute)) - return cmdMgr.RunBashC(req.Command) - }, nil) - _ = taskItem.Execute() - }() - return nil -} - func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { client, err := docker.NewDockerClient() if err != nil { diff --git a/agent/app/service/database_common.go b/agent/app/service/database_common.go index a303434ec..4d5fc9007 100644 --- a/agent/app/service/database_common.go +++ b/agent/app/service/database_common.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "path/filepath" "strings" "github.com/1Panel-dev/1Panel/agent/app/dto" @@ -43,6 +44,10 @@ func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.D func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) { filePath := "" + safeName := filepath.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return "", buserr.New("ErrInvalidParams") + } switch req.Type { case "mysql-cluster-conf": filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql-cluster/%s/conf/my.cnf", req.Name)) diff --git a/agent/app/service/device.go b/agent/app/service/device.go index 579509817..8702c4063 100644 --- a/agent/app/service/device.go +++ b/agent/app/service/device.go @@ -109,7 +109,7 @@ func (u *DeviceService) CheckDNS(key, value string) (bool, error) { if err != nil { return false, err } - defer conn.Close() + defer func() { _ = conn.Close() }() return true, nil } @@ -132,7 +132,7 @@ func (u *DeviceService) Update(key, value string) error { if cmd.CheckIllegal(value) { return buserr.New("ErrCmdIllegal") } - if err := cmd.RunDefaultBashCf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value); err != nil { + if err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run("hostnamectl", "set-hostname", value); err != nil { return err } _, _ = psutil.HOST.GetHostInfo(true) @@ -245,7 +245,7 @@ func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error { } cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem)) if !req.IsNew { - if err := cmdMgr.RunBashCf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path); err != nil { + if err := cmdMgr.Run("swapoff", req.Path); err != nil { return fmt.Errorf("handle swapoff %s failed, %v", req.Path, err) } } @@ -256,22 +256,22 @@ func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error { return operateSwapWithFile(true, req) } taskItem.LogStart(i18n.GetMsgByKey("CreateSwap")) - if err := cmdMgr.RunBashCf("%s dd if=/dev/zero of=%s bs=1024 count=%d", cmd.SudoHandleCmd(), req.Path, req.Size); err != nil { + if err := cmdMgr.Run("dd", "if=/dev/zero", fmt.Sprintf("of=%s", req.Path), "bs=1024", fmt.Sprintf("count=%d", req.Size)); err != nil { return fmt.Errorf("handle dd %s failed, %v", req.Path, err) } taskItem.Log("chmod 0600 " + req.Path) - if err := cmdMgr.RunBashCf("%s chmod 0600 %s", cmd.SudoHandleCmd(), req.Path); err != nil { + if err := cmdMgr.Run("chmod", "0600", req.Path); err != nil { return fmt.Errorf("handle chmod 0600 %s failed, %v", req.Path, err) } taskItem.LogStart(i18n.GetMsgByKey("FormatSwap")) - if err := cmdMgr.RunBashCf("%s mkswap -f %s", cmd.SudoHandleCmd(), req.Path); err != nil { + if err := cmdMgr.Run("mkswap", "-f", req.Path); err != nil { return fmt.Errorf("handle mkswap -f %s failed, %v", req.Path, err) } taskItem.LogStart(i18n.GetMsgByKey("EnableSwap")) - if err := cmdMgr.RunBashCf("%s swapon %s", cmd.SudoHandleCmd(), req.Path); err != nil { - _ = cmdMgr.RunBashCf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path) + if err := cmdMgr.Run("swapon", req.Path); err != nil { + _ = cmdMgr.Run("swapoff", req.Path) return fmt.Errorf("handle swapoff %s failed, %v", req.Path, err) } return operateSwapWithFile(false, req) diff --git a/agent/app/service/disk.go b/agent/app/service/disk.go index 71ceed4d5..30563343a 100644 --- a/agent/app/service/disk.go +++ b/agent/app/service/disk.go @@ -2,13 +2,14 @@ package service import ( "fmt" + "os" + "strings" + "time" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" - "os" - "strings" - "time" "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/response" @@ -65,19 +66,19 @@ func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, e } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) - if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil { + if err := cmdMgr.Run("partprobe", req.Device); err != nil { return "", buserr.WithErr("PartitionDiskErr", err) } - if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mklabel gpt", req.Device)); err != nil { + if err := cmdMgr.Run("parted", "-s", req.Device, "mklabel", "gpt"); err != nil { return "", buserr.WithErr("PartitionDiskErr", err) } - if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mkpart primary 1MiB 100%%", req.Device)); err != nil { + if err := cmdMgr.Run("parted", "-s", req.Device, "mkpart", "primary", "1MiB", "100%"); err != nil { return "", buserr.WithErr("PartitionDiskErr", err) } - if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil { + if err := cmdMgr.Run("partprobe", req.Device); err != nil { return "", buserr.WithErr("PartitionDiskErr", err) } partition := req.Device + "1" @@ -133,7 +134,7 @@ func (s *DiskService) MountDisk(req request.DiskMountRequest) error { } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Minute)) - if err := cmdMgr.RunBashC(fmt.Sprintf("mount -t %s %s %s", req.Filesystem, req.Device, req.MountPoint)); err != nil { + if err := cmdMgr.Run("mount", "-t", req.Filesystem, req.Device, req.MountPoint); err != nil { return buserr.WithErr("MountDiskErr", err) } if req.AutoMount { @@ -156,7 +157,7 @@ func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error { if !isPointMounted(req.MountPoint) { return buserr.New("MountDiskErr") } - if err := cmd.RunDefaultBashC(fmt.Sprintf("umount -f %s", req.MountPoint)); err != nil { + if err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run("umount", "-f", req.MountPoint); err != nil { return buserr.WithErr("MountDiskErr", err) } if err := removeFromFstab(req.MountPoint); err != nil { diff --git a/agent/app/service/file.go b/agent/app/service/file.go index a8a154d77..1c48e72e8 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -263,7 +263,7 @@ func (f *FileService) Create(op request.FileCreate) error { func (f *FileService) Delete(op request.FileDelete) error { if op.IsDir { excludeDir := global.Dir.DataDir - if filepath.Base(op.Path) == ".1panel_clash" || op.Path == excludeDir { + if path.Base(op.Path) == ".1panel_clash" || op.Path == excludeDir { return buserr.New("ErrPathNotDelete") } } @@ -592,6 +592,12 @@ func (f *FileService) DepthDirSize(req request.DirSizeReq) ([]response.DepthDirS func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) { logFilePath := "" taskStatus := "" + if len(req.Name) != 0 { + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return nil, buserr.New("ErrInvalidParams") + } + } switch req.Type { case constant.TypeWebsite: website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) @@ -649,9 +655,9 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi logFilePath = taskModel.LogFile taskStatus = taskModel.Status case "mysql-slow-logs": - logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name)) + logFilePath = path.Join(global.Dir.DataDir, "apps", "mysql", req.Name, "data", "1Panel-slow.log") case "mariadb-slow-logs": - logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name)) + logFilePath = path.Join(global.Dir.DataDir, "apps", "mariadb", req.Name, "db", "data", "1Panel-slow.log") case "php-fpm-slow-logs": php, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) if err != nil { @@ -666,8 +672,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi } logFilePath, _ = ini_conf.GetIniValue(configPath, "supervisord", "logfile") case constant.Supervisor: - logDir := path.Join(global.Dir.DataDir, "tools", "supervisord", "log") - logFilePath = path.Join(logDir, req.Name) + logFilePath = path.Join(global.Dir.DataDir, "tools", "supervisord", "log", req.Name) } file, err := os.Open(logFilePath) diff --git a/agent/app/service/host_tool.go b/agent/app/service/host_tool.go index d9ba89623..cf91d8528 100644 --- a/agent/app/service/host_tool.go +++ b/agent/app/service/host_tool.go @@ -278,6 +278,10 @@ func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcess } func handleProcess(supervisordDir string, req request.SupervisorProcessConfig, containerName string) error { + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } var ( fileOp = files.NewFileOp() logDir = path.Join(supervisordDir, "log") diff --git a/agent/app/service/mcp_server.go b/agent/app/service/mcp_server.go index efb6cf1a4..a6ee67b86 100644 --- a/agent/app/service/mcp_server.go +++ b/agent/app/service/mcp_server.go @@ -4,6 +4,10 @@ import ( "context" "errors" "fmt" + "path" + "strconv" + "strings" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/response" @@ -23,9 +27,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" "github.com/subosito/gotenv" "gopkg.in/yaml.v3" - "path" - "strconv" - "strings" ) type McpServerService struct{} @@ -458,7 +459,12 @@ func addProxy(server *model.McpServer) { } else { proxyPath = server.StreamableHttpPath } - location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)}) + safePass, err := nginx.NginxSafeString(fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath), nginx.ModeURL) + if err != nil { + global.LOG.Errorf("mcp add proxy failed, err: %v", err) + return + } + location.UpdateDirective("proxy_pass", []string{safePass}) location.ChangePath("^~", proxyPath) if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { global.LOG.Errorf("write config failed, err: %v", buserr.WithErr("ErrUpdateBuWebsite", err)) @@ -516,7 +522,12 @@ func addMCPProxy(websiteID uint) error { } else { proxyPath = server.StreamableHttpPath } - location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)}) + safePass, err := nginx.NginxSafeString(fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath), nginx.ModeURL) + if err != nil { + global.LOG.Errorf("mcp add proxy failed, err: %v", err) + return err + } + location.UpdateDirective("proxy_pass", []string{safePass}) location.ChangePath("^~", proxyPath) if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return buserr.WithErr("ErrUpdateBuWebsite", err) diff --git a/agent/app/service/runtime.go b/agent/app/service/runtime.go index 57d861f9e..b37e9faab 100644 --- a/agent/app/service/runtime.go +++ b/agent/app/service/runtime.go @@ -677,7 +677,7 @@ func (r *RuntimeService) GetPHPExtensions(runtimeID uint) (response.PHPExtension return res, err } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) - out, err := cmdMgr.RunWithStdoutBashCf("docker exec -i %s php -m", runtime.ContainerName) + out, err := cmdMgr.RunWithStdout("docker", "exec", "-i", runtime.ContainerName, "php", "-m") if err != nil { return res, err } @@ -722,7 +722,7 @@ func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq) } installTask.AddSubTask("", func(t *task.Task) error { err = cmd.NewCommandMgr(cmd.WithTask(*installTask), cmd.WithTimeout(20*time.Minute)). - RunBashCf("docker exec -i %s install-ext %s", runtime.ContainerName, req.Name) + Run("docker", "exec", "-i", runtime.ContainerName, "install-ext", req.Name) if err != nil { return err } @@ -736,7 +736,7 @@ func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq) return err } err = cmd.NewCommandMgr(cmd.WithTask(*installTask), cmd.WithTimeout(15*time.Minute)). - RunBashCf("docker commit %s %s", runtime.ContainerName, runtime.Image) + Run("docker", "commit", runtime.ContainerName, runtime.Image) if err != nil { return err } diff --git a/agent/app/service/ssh.go b/agent/app/service/ssh.go index 49dc40665..e89b87807 100644 --- a/agent/app/service/ssh.go +++ b/agent/app/service/ssh.go @@ -269,16 +269,26 @@ func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error { publicPath := fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name) authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys" - authFileItem, _ := os.ReadFile(authFilePath) - authFile := string(authFileItem) - if authFile != "" && !strings.HasSuffix(authFile, "\n") { - file, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) + if info, err := os.Stat(authFilePath); err == nil && info.Size() > 0 { + f, err := os.Open(authFilePath) if err != nil { return err } - defer file.Close() - if _, err := file.WriteString("\n"); err != nil { - return err + defer func() { _ = f.Close() }() + + if _, err := f.Seek(-1, 2); err == nil { + buf := make([]byte, 1) + if _, err := f.Read(buf); err == nil && buf[0] != '\n' { + appendFile, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return err + } + if _, err := appendFile.Write([]byte("\n")); err != nil { + _ = appendFile.Close() + return err + } + _ = appendFile.Close() + } } } @@ -290,17 +300,43 @@ func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error { return err } } else { - command := fmt.Sprintf("ssh-keygen -t %s -f %s/.ssh/%s -N ''", req.EncryptionMode, currentUser.HomeDir, req.Name) - if len(req.PassPhrase) != 0 { - command = fmt.Sprintf("ssh-keygen -t %s -P %s -f %s/.ssh/%s | echo y", req.EncryptionMode, req.PassPhrase, currentUser.HomeDir, req.Name) + tmpPrivatePath := privatePath + ".tmp" + tmpPublicPath := privatePath + ".tmp.pub" + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Minute)) + args := []string{ + "-t", req.EncryptionMode, + "-f", tmpPrivatePath, + "-N", req.PassPhrase, + "-q", } - if err := cmd.RunDefaultBashC(command); err != nil { + + if err := cmdMgr.Run("ssh-keygen", args...); err != nil { + _ = os.Remove(tmpPrivatePath) + _ = os.Remove(tmpPublicPath) return fmt.Errorf("generate failed, %v", err) } + if err := os.Rename(tmpPrivatePath, privatePath); err != nil { + return fmt.Errorf("replace private key failed, %v", err) + } + if err := os.Rename(tmpPublicPath, publicPath); err != nil { + return fmt.Errorf("replace public key failed, %v", err) + } } - if err := cmd.RunDefaultBashCf("cat %s >> %s", publicPath, authFilePath); err != nil { - return fmt.Errorf("generate failed, %v", err) + publicKeyBytes, err := os.ReadFile(publicPath) + if err != nil { + return fmt.Errorf("read public key failed, %v", err) + } + + cleanKey := strings.TrimRight(string(publicKeyBytes), "\n") + "\n" + authFile, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return fmt.Errorf("open authorized_keys failed, %v", err) + } + defer func() { _ = authFile.Close() }() + + if _, err := authFile.Write([]byte(cleanKey)); err != nil { + return fmt.Errorf("append authorized_keys failed, %v", err) } cert.PrivateKeyPath = privatePath diff --git a/agent/app/service/website.go b/agent/app/service/website.go index 25a514a6a..c121e589c 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -1906,6 +1906,10 @@ func (w WebsiteService) UpdateRedirectFile(req request.NginxRedirectUpdate) (err if err != nil { return err } + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } absolutePath := path.Join(GetSitePath(website, SiteRedirectDir), req.Name+".conf") fileOp := files.NewFileOp() oldRewriteContent, err = fileOp.GetContent(absolutePath) @@ -2333,9 +2337,25 @@ func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error { siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR")) execDir := strings.ReplaceAll(req.Dir, siteDir.Value, "/www") composerTask.AddSubTask("", func(t *task.Task) error { - cmdStr := fmt.Sprintf("docker exec -u %s %s sh -c 'composer config -g repo.packagist composer %s && composer %s --working-dir=%s'", req.User, runtime.ContainerName, req.Mirror, command, execDir) - err = cmdMgr.RunBashC(cmdStr) - if err != nil { + if err := cmdMgr.Run("docker", "exec", + "-u", req.User, + runtime.ContainerName, + "composer", + "config", "-g", + "repo.packagist", + "composer", + req.Mirror, + ); err != nil { + return err + } + + if err := cmdMgr.Run("docker", "exec", + "-u", req.User, + runtime.ContainerName, + "composer", + command, + "--working-dir="+execDir, + ); err != nil { return err } return nil diff --git a/agent/app/service/website_auth_basic.go b/agent/app/service/website_auth_basic.go index 3d1c80352..7740db349 100644 --- a/agent/app/service/website_auth_basic.go +++ b/agent/app/service/website_auth_basic.go @@ -3,6 +3,10 @@ package service import ( "bufio" "fmt" + "os" + "path" + "strings" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/response" @@ -11,14 +15,12 @@ import ( "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/nginx" "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" "golang.org/x/crypto/bcrypt" - "os" - "path" - "strings" ) func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) { @@ -260,6 +262,10 @@ func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) err if !fileOp.Stat(passDir) { _ = fileOp.CreateDir(passDir, constant.DirPerm) } + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } confPath := path.Join(authDir, fmt.Sprintf("%s.conf", req.Name)) passPath := path.Join(passDir, fmt.Sprintf("%s.pass", req.Name)) var config *components.Config @@ -289,7 +295,12 @@ func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) err config.FilePath = confPath directives := config.Directives location, _ := directives[0].(*components.Location) - location.UpdateDirective("auth_basic_user_file", []string{fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name))}) + safePass, err := nginx.NginxSafeString(fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name)), nginx.ModePath) + if err != nil { + global.LOG.Errorf("invalid auth_basic_user_file path, err: %v", err) + return err + } + location.UpdateDirective("auth_basic_user_file", []string{safePass}) location.ChangePath("~*", fmt.Sprintf("^%s", req.Path)) var passwdHash []byte passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) @@ -306,7 +317,7 @@ func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) err } nginxInclude := fmt.Sprintf("/www/sites/%s/path_auth/*.conf", website.Alias) if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { - return nil + return err } return nil } diff --git a/agent/app/service/website_lb.go b/agent/app/service/website_lb.go index 9094d5961..32e2d1aab 100644 --- a/agent/app/service/website_lb.go +++ b/agent/app/service/website_lb.go @@ -3,6 +3,10 @@ package service import ( "bytes" "fmt" + "os" + "path" + "strings" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/repo" @@ -13,9 +17,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/utils/nginx" "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" - "os" - "path" - "strings" ) func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) { @@ -87,6 +88,10 @@ func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error { if !fileOp.Stat(includeDir) { _ = fileOp.CreateDir(includeDir, constant.DirPerm) } + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) if fileOp.Stat(filePath) { return buserr.New("ErrNameIsExist") @@ -133,6 +138,10 @@ func (w WebsiteService) UpdateLoadBalance(req request.WebsiteLBUpdate) error { } includeDir := GetSitePath(website, SiteUpstreamDir) fileOp := files.NewFileOp() + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) if !fileOp.Stat(filePath) { return nil @@ -191,6 +200,10 @@ func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error { includeDir := GetSitePath(website, SiteUpstreamDir) fileOp := files.NewFileOp() + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) if !fileOp.Stat(filePath) { return nil @@ -211,6 +224,10 @@ func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) e return err } includeDir := GetSitePath(website, SiteUpstreamDir) + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) fileOp := files.NewFileOp() oldContent, err := fileOp.GetContent(filePath) diff --git a/agent/app/service/website_proxy.go b/agent/app/service/website_proxy.go index 0d79a5257..ba5bd196d 100644 --- a/agent/app/service/website_proxy.go +++ b/agent/app/service/website_proxy.go @@ -107,8 +107,16 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) err = errors.New("invalid proxy config, no location found") return } - location.UpdateDirective("proxy_pass", []string{req.ProxyPass}) - location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost}) + safePass, err := nginx.NginxSafeString(req.ProxyPass, nginx.ModeURL) + if err != nil { + return err + } + safeHeader, err := nginx.NginxSafeString(req.ProxyHost, nginx.ModeHost) + if err != nil { + return err + } + location.UpdateDirective("proxy_pass", []string{safePass}) + location.UpdateDirective("proxy_set_header", []string{"Host", safeHeader}) location.ChangePath(req.Modifier, req.Match) // Server Cache Settings if req.Cache { @@ -147,14 +155,26 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) } // CORS Settings if req.Cors { - location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"}) + safeAllowOrigins, err := nginx.NginxSafeString(req.AllowOrigins, nginx.ModeGeneric) + if err != nil { + return err + } + location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", safeAllowOrigins, "always"}) if req.AllowMethods != "" { - location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"}) + safeAllowMethods, err := nginx.NginxSafeString(req.AllowMethods, nginx.ModeAllowMethods) + if err != nil { + return err + } + location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", safeAllowMethods, "always"}) } else { location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"}) } if req.AllowHeaders != "" { - location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"}) + safeAllowHeaders, err := nginx.NginxSafeString(req.AllowHeaders, nginx.ModeGeneric) + if err != nil { + return err + } + location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", safeAllowHeaders, "always"}) } else { location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"}) } diff --git a/agent/app/service/website_rewrite.go b/agent/app/service/website_rewrite.go index 04b22e1af..b2263c490 100644 --- a/agent/app/service/website_rewrite.go +++ b/agent/app/service/website_rewrite.go @@ -4,6 +4,10 @@ import ( "bytes" "context" "fmt" + "os" + "path" + "strings" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/response" @@ -12,9 +16,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/utils/files" - "os" - "path" - "strings" ) func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error { @@ -73,6 +74,10 @@ func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response contentByte, _ = nginx_conf.Rewrites.ReadFile(rewriteFile) if contentByte == nil { customRewriteDir := GetOpenrestyDir(DefaultRewriteDir) + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return nil, buserr.New("ErrInvalidParams") + } customRewriteFile := path.Join(customRewriteDir, fmt.Sprintf("%s.conf", strings.ToLower(req.Name))) contentByte, err = files.NewFileOp().GetContent(customRewriteFile) } @@ -90,6 +95,10 @@ func (w WebsiteService) OperateCustomRewrite(req request.CustomRewriteOperate) e return err } } + safeName := path.Base(req.Name) + if safeName != req.Name || strings.Contains(safeName, "..") { + return buserr.New("ErrInvalidParams") + } rewriteFile := path.Join(rewriteDir, fmt.Sprintf("%s.conf", req.Name)) switch req.Operate { case "create": diff --git a/agent/cron/cron.go b/agent/cron/cron.go index 70ed24d0b..78c3c887b 100644 --- a/agent/cron/cron.go +++ b/agent/cron/cron.go @@ -1,8 +1,9 @@ package cron import ( + "crypto/rand" "fmt" - mathRand "math/rand" + "math/big" "time" "github.com/1Panel-dev/1Panel/agent/app/model" @@ -43,7 +44,17 @@ func Run() { if _, err := global.Cron.AddJob("@daily", job.NewSSLJob()); err != nil { global.LOG.Errorf("can not add ssl corn job: %s", err.Error()) } - if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), job.NewAppStoreJob()); err != nil { + minuteRand, err := rand.Int(rand.Reader, big.NewInt(60)) + if err != nil { + global.LOG.Errorf("generate random minute failed: %v", err) + minuteRand = big.NewInt(0) + } + hourRand, err := rand.Int(rand.Reader, big.NewInt(3)) + if err != nil { + global.LOG.Errorf("generate random hour failed: %v", err) + hourRand = big.NewInt(0) + } + if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", minuteRand.Int64(), hourRand.Int64()), job.NewAppStoreJob()); err != nil { global.LOG.Errorf("can not add appstore corn job: %s", err.Error()) } if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil { diff --git a/agent/router/ro_container.go b/agent/router/ro_container.go index 112d64be0..b997b551b 100644 --- a/agent/router/ro_container.go +++ b/agent/router/ro_container.go @@ -15,7 +15,6 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) { baRouter.GET("/stats/:id", baseApi.ContainerStats) baRouter.POST("", baseApi.ContainerCreate) - baRouter.POST("command", baseApi.ContainerCreateByCommand) baRouter.POST("/update", baseApi.ContainerUpdate) baRouter.POST("/upgrade", baseApi.ContainerUpgrade) baRouter.POST("/info", baseApi.ContainerInfo) diff --git a/agent/utils/common/common.go b/agent/utils/common/common.go index 4f2d61537..a49dda605 100644 --- a/agent/utils/common/common.go +++ b/agent/utils/common/common.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "fmt" "io" - mathRand "math/rand" + "math/big" "net" "os/exec" "reflect" @@ -135,13 +135,6 @@ func isDigit(r rune) bool { return r >= '0' && r <= '9' } -func max(x, y int) int { - if x > y { - return x - } - return y -} - func GetSortedVersions(versions []string) []string { sort.Slice(versions, func(i, j int) bool { return CompareVersion(versions[i], versions[j]) @@ -169,21 +162,29 @@ var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456 func RandStr(n int) string { b := make([]rune, n) + max := big.NewInt(int64(len(letters))) for i := range b { - b[i] = letters[mathRand.Intn(len(letters))] + num, err := rand.Int(rand.Reader, max) + if err != nil { + return "" + } + b[i] = letters[num.Int64()] } return string(b) } func RandStrAndNum(n int) string { - source := mathRand.NewSource(time.Now().UnixNano()) - randGen := mathRand.New(source) const charset = "abcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, n) + max := big.NewInt(int64(len(charset))) for i := range b { - b[i] = charset[randGen.Intn(len(charset)-1)] + num, err := rand.Int(rand.Reader, max) + if err != nil { + return "" + } + b[i] = charset[num.Int64()] } - return (string(b)) + return string(b) } func ScanPort(port int) bool { @@ -191,7 +192,7 @@ func ScanPort(port int) bool { if err != nil { return true } - defer ln.Close() + defer func() { _ = ln.Close() }() return false } @@ -200,7 +201,7 @@ func ScanUDPPort(port int) bool { if err != nil { return true } - defer ln.Close() + defer func() { _ = ln.Close() }() return false } @@ -225,7 +226,7 @@ func ScanPortWithIP(ip string, port int) bool { } return false } - defer conn.Close() + defer func() { _ = conn.Close() }() return true } diff --git a/agent/utils/compose/compose.go b/agent/utils/compose/compose.go index fc9c2e876..b370600f9 100644 --- a/agent/utils/compose/compose.go +++ b/agent/utils/compose/compose.go @@ -32,14 +32,14 @@ func Up(filePath string) (string, error) { if err := checkCmd(); err != nil { return "", err } - return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath)) + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), "up", "-d") } func UpWithTask(filePath string, task *task.Task, forcePull bool) error { if err := pullComposeImages(filePath, forcePull, task); err != nil { return err } - return cmd.NewCommandMgr(cmd.WithTask(*task)).RunBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath)) + return cmd.NewCommandMgr(cmd.WithTask(*task)).Run(global.CONF.DockerConfig.Command, loadFiles(filePath), "up", "-d") } func pullComposeImages(filePath string, forcePull bool, task *task.Task) error { @@ -123,7 +123,7 @@ func getComposeImagesByCommand(filePath string) ([]string, error) { return nil, err } stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(5*time.Minute)). - RunWithStdoutBashCf("%s %s config --images", global.CONF.DockerConfig.Command, loadFiles(filePath)) + RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), "config", "--images") if err != nil { return nil, fmt.Errorf("run compose config --images failed, std: %s, err: %v", stdout, err) } @@ -151,28 +151,28 @@ func Down(filePath string) (string, error) { if err := checkCmd(); err != nil { return "", err } - return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s down --remove-orphans", global.CONF.DockerConfig.Command, loadFiles(filePath)) + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), "down", "--remove-orphans") } func Stop(filePath string) (string, error) { if err := checkCmd(); err != nil { return "", err } - return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s stop", global.CONF.DockerConfig.Command, loadFiles(filePath)) + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), "stop") } func Restart(filePath string) (string, error) { if err := checkCmd(); err != nil { return "", err } - return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s restart", global.CONF.DockerConfig.Command, loadFiles(filePath)) + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), "restart") } func Operate(filePath, operation string) (string, error) { if err := checkCmd(); err != nil { return "", err } - return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s %s", global.CONF.DockerConfig.Command, loadFiles(filePath), operation) + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), operation) } func DownAndUp(filePath string) (string, error) { @@ -180,11 +180,11 @@ func DownAndUp(filePath string) (string, error) { return "", err } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Minute)) - stdout, err := cmdMgr.RunWithStdoutBashCf("%s %s down", global.CONF.DockerConfig.Command, loadFiles(filePath)) + stdout, err := cmdMgr.RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), "down") if err != nil { return stdout, err } - stdout, err = cmdMgr.RunWithStdoutBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath)) + stdout, err = cmdMgr.RunWithStdout(global.CONF.DockerConfig.Command, loadFiles(filePath), "up", "-d") return stdout, err } diff --git a/agent/utils/encrypt/encrypt.go b/agent/utils/encrypt/encrypt.go index 9a48e493f..882f3e8c4 100644 --- a/agent/utils/encrypt/encrypt.go +++ b/agent/utils/encrypt/encrypt.go @@ -118,10 +118,24 @@ func padding(plaintext []byte, blockSize int) []byte { return append(plaintext, padtext...) } -func unPadding(origData []byte) []byte { +func unPadding(origData []byte) ([]byte, error) { length := len(origData) + if length == 0 { + return nil, fmt.Errorf("invalid padding size") + } + unpadding := int(origData[length-1]) - return origData[:(length - unpadding)] + if unpadding == 0 || unpadding > length { + return nil, fmt.Errorf("invalid padding") + } + + for i := 0; i < unpadding; i++ { + if origData[length-1-i] != byte(unpadding) { + return nil, fmt.Errorf("invalid padding") + } + } + + return origData[:(length - unpadding)], nil } func aesEncryptWithSalt(key, plaintext []byte) ([]byte, error) { @@ -152,6 +166,10 @@ func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) { ciphertext = ciphertext[aes.BlockSize:] cbc := cipher.NewCBCDecrypter(block, iv) cbc.CryptBlocks(ciphertext, ciphertext) - ciphertext = unPadding(ciphertext) - return ciphertext, nil + + unpadded, err := unPadding(ciphertext) + if err != nil { + return nil, err + } + return unpadded, nil } diff --git a/agent/utils/nginx/validate.go b/agent/utils/nginx/validate.go new file mode 100644 index 000000000..8a56d193e --- /dev/null +++ b/agent/utils/nginx/validate.go @@ -0,0 +1,102 @@ +package nginx + +import ( + "errors" + "net/url" + "strings" + "unicode" + + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +type Mode int + +const ( + ModeGeneric Mode = iota + ModeHost + ModeURL + ModePath + ModeAllowMethods +) + +func NginxSafeString(input string, mode Mode) (string, error) { + if input == "" { + return "", errors.New("empty value not allowed") + } + + for _, r := range input { + if unicode.IsControl(r) { + return "", errors.New("control characters not allowed") + } + } + + if strings.ContainsAny(input, ";\n\r{}#`$") { + return "", errors.New("illegal nginx syntax characters") + } + + switch mode { + case ModeHost: + return validateHost(input) + case ModeURL: + return validateURL(input) + case ModePath: + return validatePath(input) + case ModeAllowMethods: + return validateAllowMethods(input) + default: + return input, nil + } +} + +func validateHost(host string) (string, error) { + if !re.GetRegex(re.NginxHostPattern).MatchString(host) { + return "", errors.New("invalid host format") + } + return host, nil +} + +func validateURL(raw string) (string, error) { + u, err := url.Parse(raw) + if err != nil { + return "", errors.New("invalid url") + } + + if u.Scheme != "http" && u.Scheme != "https" { + return "", errors.New("unsupported scheme") + } + + if u.Host == "" { + return "", errors.New("missing host") + } + + return raw, nil +} + +func validatePath(p string) (string, error) { + if !re.GetRegex(re.NginxPathPattern).MatchString(p) { + return "", errors.New("invalid path") + } + return p, nil +} + +var allowedMethods = map[string]bool{ + "GET": true, + "POST": true, + "PUT": true, + "DELETE": true, + "PATCH": true, + "OPTIONS": true, + "HEAD": true, +} + +func validateAllowMethods(input string) (string, error) { + parts := strings.Split(input, ",") + for i, method := range parts { + method = strings.TrimSpace(strings.ToUpper(method)) + if !allowedMethods[method] { + return "", errors.New("invalid HTTP method: " + method) + } + parts[i] = method + } + return strings.Join(parts, ", "), nil +} diff --git a/agent/utils/re/re.go b/agent/utils/re/re.go index 3c556e597..c3702e772 100644 --- a/agent/utils/re/re.go +++ b/agent/utils/re/re.go @@ -29,6 +29,8 @@ const ( AnsiEscapePattern = "\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>" RecycleBinFilePattern = `_1p_file_1p_(.+)_p_(\d+)_(\d+)` OrderByValidationPattern = `^[a-zA-Z_][a-zA-Z0-9_]*$` + NginxHostPattern = `^[a-zA-Z0-9.-]+(:[0-9]+)?$` + NginxPathPattern = `^/[a-zA-Z0-9._/\-]*$` ) var regexMap = make(map[string]*regexp.Regexp) @@ -58,6 +60,8 @@ func Init() { AnsiEscapePattern, RecycleBinFilePattern, OrderByValidationPattern, + NginxHostPattern, + NginxPathPattern, } for _, pattern := range patterns { diff --git a/core/app/dto/setting.go b/core/app/dto/setting.go index 4b4eb4ecb..1b5621005 100644 --- a/core/app/dto/setting.go +++ b/core/app/dto/setting.go @@ -33,7 +33,6 @@ type SettingInfo struct { ExpirationTime string `json:"expirationTime"` ComplexityVerification string `json:"complexityVerification"` MFAStatus string `json:"mfaStatus"` - MFASecret string `json:"mfaSecret"` MFAInterval string `json:"mfaInterval"` AppStoreVersion string `json:"appStoreVersion"` diff --git a/core/app/service/script_library.go b/core/app/service/script_library.go index 93a91df3a..35fb32ebc 100644 --- a/core/app/service/script_library.go +++ b/core/app/service/script_library.go @@ -1,9 +1,10 @@ package service import ( + "crypto/rand" "encoding/json" "fmt" - mathRand "math/rand" + "math/big" "net/http" "os" "path" @@ -172,7 +173,17 @@ func StartSync() { service := NewIScriptService() scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync") if !global.CONF.Base.IsOffLine && scriptSync == constant.StatusEnable { - id, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), service) + minuteRand, err := rand.Int(rand.Reader, big.NewInt(60)) + if err != nil { + global.LOG.Errorf("generate random minute failed: %v", err) + minuteRand = big.NewInt(0) + } + hourRand, err := rand.Int(rand.Reader, big.NewInt(3)) + if err != nil { + global.LOG.Errorf("generate random hour failed: %v", err) + hourRand = big.NewInt(0) + } + id, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", minuteRand.Int64(), hourRand.Int64()), service) if err != nil { global.LOG.Errorf("[core] can not add script sync corn job: %s", err.Error()) } diff --git a/core/cmd/server/cmd/update.go b/core/cmd/server/cmd/update.go index 4f7802dab..afdf2c6f2 100644 --- a/core/cmd/server/cmd/update.go +++ b/core/cmd/server/cmd/update.go @@ -258,7 +258,7 @@ func isValidPassword(password string) bool { } } - if len(password) < 8 && len(password) > 30 { + if len(password) < 8 || len(password) > 30 { return false } if (numCount == 0 && alphaCount == 0) || (alphaCount == 0 && specialCount == 0) || (numCount == 0 && specialCount == 0) { diff --git a/core/middleware/api_auth.go b/core/middleware/api_auth.go index 1a1b92c95..2fbb91b27 100644 --- a/core/middleware/api_auth.go +++ b/core/middleware/api_auth.go @@ -1,17 +1,19 @@ package middleware import ( - "crypto/md5" + "crypto/hmac" + "crypto/sha256" "encoding/hex" + "net" + "strconv" + "strings" + "time" + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/utils/common" "github.com/gin-gonic/gin" - "net" - "strconv" - "strings" - "time" ) func ApiAuth() gin.HandlerFunc { @@ -76,7 +78,8 @@ func isValid1PanelTimestamp(panelTimestamp string) bool { func isValid1PanelToken(panelToken string, panelTimestamp string) bool { system1PanelToken := global.Api.ApiKey - return panelToken == GenerateMD5("1panel"+system1PanelToken+panelTimestamp) + expected := GenerateHMAC(system1PanelToken, panelTimestamp) + return hmac.Equal([]byte(panelToken), []byte(expected)) } func isIPInWhiteList(clientIP string) bool { @@ -111,8 +114,8 @@ func isIPInWhiteList(clientIP string) bool { return false } -func GenerateMD5(param string) string { - hash := md5.New() - hash.Write([]byte(param)) - return hex.EncodeToString(hash.Sum(nil)) +func GenerateHMAC(systemToken, timestamp string) string { + mac := hmac.New(sha256.New, []byte(systemToken)) + mac.Write([]byte("1panel" + timestamp)) + return hex.EncodeToString(mac.Sum(nil)) } diff --git a/core/utils/common/common.go b/core/utils/common/common.go index a17e6324d..85aea82e5 100644 --- a/core/utils/common/common.go +++ b/core/utils/common/common.go @@ -2,9 +2,10 @@ package common import ( "crypto/md5" + "crypto/rand" "encoding/hex" "fmt" - mathRand "math/rand" + "math/big" "net" "os" "path" @@ -22,20 +23,29 @@ var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456 func RandStr(n int) string { b := make([]rune, n) + max := big.NewInt(int64(len(letters))) for i := range b { - b[i] = letters[mathRand.Intn(len(letters))] + num, err := rand.Int(rand.Reader, max) + if err != nil { + return "" + } + b[i] = letters[num.Int64()] } return string(b) } + func RandStrAndNum(n int) string { - source := mathRand.NewSource(time.Now().UnixNano()) - randGen := mathRand.New(source) const charset = "abcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, n) + max := big.NewInt(int64(len(charset))) for i := range b { - b[i] = charset[randGen.Intn(len(charset)-1)] + num, err := rand.Int(rand.Reader, max) + if err != nil { + return "" + } + b[i] = charset[num.Int64()] } - return (string(b)) + return string(b) } func Md5(val string) string { @@ -68,7 +78,7 @@ func ScanPort(port int) bool { if err != nil { return true } - defer ln.Close() + defer func() { _ = ln.Close() }() return false } @@ -81,7 +91,7 @@ func CheckPort(host string, port string, timeout time.Duration) bool { } return strings.Contains(err.Error(), "connection refused") } - defer conn.Close() + defer func() { _ = conn.Close() }() return true } diff --git a/core/utils/encrypt/encrypt.go b/core/utils/encrypt/encrypt.go index 753fdad12..7b8d979b0 100644 --- a/core/utils/encrypt/encrypt.go +++ b/core/utils/encrypt/encrypt.go @@ -163,14 +163,31 @@ func aesDecrypt(ciphertext, key, iv []byte) ([]byte, error) { } mode := cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(ciphertext, ciphertext) - ciphertext = pkcs7Unpad(ciphertext) - return ciphertext, nil + unpadded, err := pkcs7Unpad(ciphertext) + if err != nil { + return nil, err + } + return unpadded, nil } -func pkcs7Unpad(data []byte) []byte { +func pkcs7Unpad(data []byte) ([]byte, error) { length := len(data) + if length == 0 { + return nil, errors.New("invalid padding size") + } + padLength := int(data[length-1]) - return data[:length-padLength] + if padLength == 0 || padLength > length { + return nil, errors.New("invalid padding") + } + + for i := 0; i < padLength; i++ { + if data[length-1-i] != byte(padLength) { + return nil, errors.New("invalid padding") + } + } + + return data[:length-padLength], nil } func DecryptPassword(encryptedData string, privateKey *rsa.PrivateKey) (string, error) { diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index 37b994b76..8a2c21b50 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -24,9 +24,6 @@ export const loadResourceLimit = () => { export const createContainer = (params: Container.ContainerHelper) => { return http.post(`/containers`, params, TimeoutEnum.T_10M); }; -export const createContainerByCommand = (command: string, taskID: string) => { - return http.post(`/containers/command`, { command: command, taskID: taskID }); -}; export const updateContainer = (params: Container.ContainerHelper) => { return http.post(`/containers/update`, params, TimeoutEnum.T_10M); }; diff --git a/frontend/src/views/container/container/command/index.vue b/frontend/src/views/container/container/command/index.vue deleted file mode 100644 index 5e270c95d..000000000 --- a/frontend/src/views/container/container/command/index.vue +++ /dev/null @@ -1,111 +0,0 @@ - - -