mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
fix(service): hide EnableShowInService=false services from sideband endpoints
GHSA-vrmh-5mmx-hjwx: GET /api/v1/server/:id/service and GET /api/v1/service/:id/history both iterate the raw service list and emit ServiceName / timing for any service that happens to monitor the queried server, ignoring the owner's EnableShowInService=false flag. Both routes are on optionalAuth, so unauthenticated visitors could enumerate hidden services by name and timing. Introduce userCanViewService(c, service): - EnableShowInService=true -> always visible - admin -> always visible - authenticated owner -> visible (HasPermission) - everyone else -> hidden getServiceHistory rejects unknown-or-invisible service with the same 'service not found' message so the endpoint cannot be used as an oracle. listServerServices pre-filters the sorted service list. Owners and admins keep their existing visibility into their own hidden services. Tests: - TestUserCanViewServiceVisibleServiceIsPublic - TestUserCanViewServiceHiddenServiceRejectsGuest - TestUserCanViewServiceHiddenServiceRejectsForeignMember - TestUserCanViewServiceHiddenServiceAllowsOwner - TestUserCanViewServiceHiddenServiceAllowsAdmin Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
This commit is contained in:
@@ -35,6 +35,22 @@ func userCanViewServer(c *gin.Context, server *model.Server) bool {
|
|||||||
return !server.HideForGuest
|
return !server.HideForGuest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userCanViewService(c *gin.Context, service *model.Service) bool {
|
||||||
|
if service == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if service.EnableShowInService {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if callerIsAdmin(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, isMember := c.Get(model.CtxKeyAuthorizedUser); isMember {
|
||||||
|
return service.HasPermission(c)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func assertOwnsNotificationGroup(c *gin.Context, groupID uint64) error {
|
func assertOwnsNotificationGroup(c *gin.Context, groupID uint64) error {
|
||||||
if groupID == 0 {
|
if groupID == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -130,9 +130,8 @@ func getServiceHistory(c *gin.Context) (*model.ServiceHistoryResponse, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查服务是否存在
|
|
||||||
service, ok := singleton.ServiceSentinelShared.Get(serviceID)
|
service, ok := singleton.ServiceSentinelShared.Get(serviceID)
|
||||||
if !ok || service == nil {
|
if !ok || service == nil || !userCanViewService(c, service) {
|
||||||
return nil, singleton.Localizer.ErrorT("service not found")
|
return nil, singleton.Localizer.ErrorT("service not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +284,13 @@ func listServerServices(c *gin.Context) ([]*model.ServiceInfos, error) {
|
|||||||
return nil, singleton.Localizer.ErrorT("unauthorized: only 1d data available for guests")
|
return nil, singleton.Localizer.ErrorT("unauthorized: only 1d data available for guests")
|
||||||
}
|
}
|
||||||
|
|
||||||
services := singleton.ServiceSentinelShared.GetSortedList()
|
allServices := singleton.ServiceSentinelShared.GetSortedList()
|
||||||
|
services := make([]*model.Service, 0, len(allServices))
|
||||||
|
for _, s := range allServices {
|
||||||
|
if userCanViewService(c, s) {
|
||||||
|
services = append(services, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var result []*model.ServiceInfos
|
var result []*model.ServiceInfos
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/nezhahq/nezha/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newServiceVisibilityCtx(viewer *model.User) *gin.Context {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||||
|
if viewer != nil {
|
||||||
|
c.Set(model.CtxKeyAuthorizedUser, viewer)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserCanViewServiceVisibleServiceIsPublic(t *testing.T) {
|
||||||
|
visible := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: true}
|
||||||
|
assert.True(t, userCanViewService(newServiceVisibilityCtx(nil), visible), "guest must see EnableShowInService=true regardless of owner")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserCanViewServiceHiddenServiceRejectsGuest(t *testing.T) {
|
||||||
|
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
|
||||||
|
assert.False(t, userCanViewService(newServiceVisibilityCtx(nil), hidden), "guest must NOT see hidden service via per-server / per-id sideband endpoints")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserCanViewServiceHiddenServiceRejectsForeignMember(t *testing.T) {
|
||||||
|
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
|
||||||
|
foreign := &model.User{Common: model.Common{ID: 200}, Role: model.RoleMember}
|
||||||
|
assert.False(t, userCanViewService(newServiceVisibilityCtx(foreign), hidden), "foreign member must NOT see another user's hidden service")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserCanViewServiceHiddenServiceAllowsOwner(t *testing.T) {
|
||||||
|
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
|
||||||
|
owner := &model.User{Common: model.Common{ID: 100}, Role: model.RoleMember}
|
||||||
|
assert.True(t, userCanViewService(newServiceVisibilityCtx(owner), hidden), "owner must still see their own hidden service")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserCanViewServiceHiddenServiceAllowsAdmin(t *testing.T) {
|
||||||
|
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
|
||||||
|
admin := &model.User{Common: model.Common{ID: 1}, Role: model.RoleAdmin}
|
||||||
|
assert.True(t, userCanViewService(newServiceVisibilityCtx(admin), hidden), "admin must be able to see any hidden service")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user