security: harden WebDAV overwrite and multi-root routing
This commit is contained in:
@@ -800,9 +800,28 @@ func (s *Server) webdav(w http.ResponseWriter, r *http.Request) {
|
||||
inner = &webdavStorageFS{store: s.Store, removeMode: s.RemoveMode}
|
||||
}
|
||||
fs := &webdavAuthFS{inner: inner, account: account(r, s)}
|
||||
if (r.Method == "COPY" || r.Method == "MOVE") && !sameWebDAVDestinationHost(r) {
|
||||
writeJSONError(w, http.StatusBadRequest, "invalid destination")
|
||||
return
|
||||
}
|
||||
(&webdav.Handler{FileSystem: fs}).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func sameWebDAVDestinationHost(r *http.Request) bool {
|
||||
dest := strings.TrimSpace(r.Header.Get("Destination"))
|
||||
if dest == "" {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(dest)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if u.Host == "" {
|
||||
return true
|
||||
}
|
||||
return strings.EqualFold(u.Host, r.Host)
|
||||
}
|
||||
|
||||
type webdavStorageFS struct {
|
||||
store storage.Storage
|
||||
removeMode int
|
||||
@@ -1306,6 +1325,11 @@ func (f *webdavAuthFS) Copy(ctx context.Context, name, dest string, opts *webdav
|
||||
if err := f.allow(parent, auth.Write); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if opts == nil || !opts.NoOverwrite {
|
||||
if err := f.authorizeOverwrite(ctx, dest); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return f.inner.Copy(ctx, name, dest, opts)
|
||||
}
|
||||
func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav.MoveOptions) (bool, error) {
|
||||
@@ -1322,9 +1346,31 @@ func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav
|
||||
if err := f.allow(parent, auth.Write); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if opts == nil || !opts.NoOverwrite {
|
||||
if err := f.authorizeOverwrite(ctx, dest); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return f.inner.Move(ctx, name, dest, opts)
|
||||
}
|
||||
|
||||
func (f *webdavAuthFS) authorizeOverwrite(ctx context.Context, dest string) error {
|
||||
info, err := f.inner.Stat(ctx, dest)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := f.allow(dest, auth.Delete); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir {
|
||||
return f.checkTree(ctx, dest, auth.Delete)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *webdavAuthFS) checkTree(ctx context.Context, name string, p auth.PermMask) error {
|
||||
info, err := f.inner.Stat(ctx, name)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user