diff --git a/adapter/outbound/ssh.go b/adapter/outbound/ssh.go index 2f08bdbc..d6c546f3 100644 --- a/adapter/outbound/ssh.go +++ b/adapter/outbound/ssh.go @@ -136,7 +136,11 @@ func NewSsh(option SshOption) (*Ssh, error) { if strings.Contains(option.PrivateKey, "PRIVATE KEY") { b = []byte(option.PrivateKey) } else { - b, err = os.ReadFile(C.Path.Resolve(option.PrivateKey)) + path := C.Path.Resolve(option.PrivateKey) + if !C.Path.IsSafePath(path) { + return nil, fmt.Errorf("path is not subpath of home directory: %s", path) + } + b, err = os.ReadFile(path) if err != nil { return nil, err } diff --git a/component/ca/config.go b/component/ca/config.go index d9899dfa..4b37f762 100644 --- a/component/ca/config.go +++ b/component/ca/config.go @@ -81,7 +81,11 @@ func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) var certificate []byte var err error if len(customCA) > 0 { - certificate, err = os.ReadFile(C.Path.Resolve(customCA)) + path := C.Path.Resolve(customCA) + if !C.Path.IsSafePath(path) { + return nil, fmt.Errorf("path is not subpath of home directory: %s", path) + } + certificate, err = os.ReadFile(path) if err != nil { return nil, fmt.Errorf("load ca error: %w", err) } diff --git a/component/ca/keypair.go b/component/ca/keypair.go index fd279bfc..51555f6e 100644 --- a/component/ca/keypair.go +++ b/component/ca/keypair.go @@ -16,6 +16,7 @@ import ( type Path interface { Resolve(path string) string + IsSafePath(path string) bool } // LoadTLSKeyPair loads a TLS key pair from the provided certificate and private key data or file paths, supporting fallback resolution. @@ -40,7 +41,12 @@ func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate, certificate = path.Resolve(certificate) privateKey = path.Resolve(privateKey) - cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey) + var loadErr error + if path.IsSafePath(certificate) && path.IsSafePath(privateKey) { + cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey) + } else { + loadErr = fmt.Errorf("path is not subpath of home directory") + } if loadErr != nil { return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) } diff --git a/config/config.go b/config/config.go index 165c8d70..f6b193d4 100644 --- a/config/config.go +++ b/config/config.go @@ -754,6 +754,9 @@ func parseGeneral(cfg *RawConfig) (*General, error) { } func parseController(cfg *RawConfig) (*Controller, error) { + if path := cfg.ExternalUI; path != "" && !C.Path.IsSafePath(path) { + return nil, fmt.Errorf("path is not subpath of home directory: %s", path) + } return &Controller{ ExternalController: cfg.ExternalController, ExternalUI: cfg.ExternalUI, diff --git a/constant/path.go b/constant/path.go index 1594441c..f78159f4 100644 --- a/constant/path.go +++ b/constant/path.go @@ -84,7 +84,7 @@ func (p *path) IsSafePath(path string) bool { return false } - return !strings.Contains(rel, "..") + return filepath.IsLocal(rel) } func (p *path) GetPathByHash(prefix, name string) string { diff --git a/hub/route/configs.go b/hub/route/configs.go index b23a35a5..56a9a53d 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -373,10 +373,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } else { if req.Path == "" { req.Path = C.Path.Config() - } - if !filepath.IsAbs(req.Path) { + } else if !filepath.IsLocal(req.Path) { render.Status(r, http.StatusBadRequest) - render.JSON(w, r, newError("path is not a absolute path")) + render.JSON(w, r, newError("path is not a valid absolute path")) return }