chore: the updateConfigs api also adds a check for SAFE_PATHS

This commit is contained in:
wwqgtxx 2025-05-12 11:24:56 +08:00
parent a4fcd3af07
commit 2116640886
8 changed files with 43 additions and 22 deletions

View File

@ -138,7 +138,7 @@ func NewSsh(option SshOption) (*Ssh, error) {
} else {
path := C.Path.Resolve(option.PrivateKey)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("path is not subpath of home directory: %s", path)
return nil, C.Path.ErrNotSafePath(path)
}
b, err = os.ReadFile(path)
if err != nil {

View File

@ -17,7 +17,6 @@ import (
var (
errVehicleType = errors.New("unsupport vehicle type")
errSubPath = errors.New("path is not subpath of home directory")
)
type healthCheckSchema struct {
@ -115,7 +114,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
if schema.Path != "" {
path = C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
return nil, C.Path.ErrNotSafePath(path)
}
}
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)

View File

@ -83,7 +83,7 @@ func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error)
if len(customCA) > 0 {
path := C.Path.Resolve(customCA)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("path is not subpath of home directory: %s", path)
return nil, C.Path.ErrNotSafePath(path)
}
certificate, err = os.ReadFile(path)
if err != nil {

View File

@ -17,6 +17,7 @@ import (
type Path interface {
Resolve(path string) string
IsSafePath(path string) bool
ErrNotSafePath(path string) error
}
// LoadTLSKeyPair loads a TLS key pair from the provided certificate and private key data or file paths, supporting fallback resolution.
@ -42,10 +43,12 @@ func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate,
certificate = path.Resolve(certificate)
privateKey = path.Resolve(privateKey)
var loadErr error
if path.IsSafePath(certificate) && path.IsSafePath(privateKey) {
cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey)
if !path.IsSafePath(certificate) {
loadErr = path.ErrNotSafePath(certificate)
} else if !path.IsSafePath(privateKey) {
loadErr = path.ErrNotSafePath(privateKey)
} else {
loadErr = fmt.Errorf("path is not subpath of home directory")
cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey)
}
if loadErr != nil {
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())

View File

@ -755,7 +755,7 @@ 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 nil, C.Path.ErrNotSafePath(path)
}
return &Controller{
ExternalController: cfg.ExternalController,

View File

@ -1,6 +1,7 @@
package constant
import (
"fmt"
"os"
P "path"
"path/filepath"
@ -87,10 +88,8 @@ func (p *path) IsSafePath(path string) bool {
if p.allowUnsafePath || features.CMFA {
return true
}
homedir := p.HomeDir()
path = p.Resolve(path)
safePaths := append([]string{homedir}, p.safePaths...) // add homedir to safePaths
for _, safePath := range safePaths {
for _, safePath := range p.SafePaths() {
if rel, err := filepath.Rel(safePath, path); err == nil {
if filepath.IsLocal(rel) {
return true
@ -100,6 +99,23 @@ func (p *path) IsSafePath(path string) bool {
return false
}
func (p *path) SafePaths() []string {
return append([]string{p.homeDir}, p.safePaths...) // add homedir to safePaths
}
func (p *path) ErrNotSafePath(path string) error {
return ErrNotSafePath{Path: path, SafePaths: p.SafePaths()}
}
type ErrNotSafePath struct {
Path string
SafePaths []string
}
func (e ErrNotSafePath) Error() string {
return fmt.Sprintf("path is not subpath of home directory or SAFE_PATHS: %s \n allowed paths: %s", e.Path, e.SafePaths)
}
func (p *path) GetPathByHash(prefix, name string) string {
hash := utils.MakeHash([]byte(name))
filename := hash.String()

View File

@ -371,12 +371,20 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
return
}
} else {
if req.Path == "" {
if req.Path == "" { // default path unneeded any safe check
req.Path = C.Path.Config()
} else if !filepath.IsAbs(req.Path) {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("path is not a absolute path"))
return
} else {
if !filepath.IsAbs(req.Path) {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("path is not a absolute path"))
return
}
if !C.Path.IsSafePath(req.Path) {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(C.Path.ErrNotSafePath(req.Path).Error()))
return
}
}
cfg, err = executor.ParseWithPath(req.Path)

View File

@ -1,7 +1,6 @@
package provider
import (
"errors"
"fmt"
"time"
@ -12,10 +11,6 @@ import (
"github.com/metacubex/mihomo/rules/common"
)
var (
errSubPath = errors.New("path is not subpath of home directory")
)
type ruleProviderSchema struct {
Type string `provider:"type"`
Behavior string `provider:"behavior"`
@ -53,7 +48,7 @@ func ParseRuleProvider(name string, mapping map[string]any, parse common.ParseRu
if schema.Path != "" {
path = C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
return nil, C.Path.ErrNotSafePath(path)
}
}
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil, resource.DefaultHttpTimeout, schema.SizeLimit)