mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-08 02:36:37 +08:00
fix(backend): enforce port range restrictions in federation mode (#125)
Added dual-layer port range enforcement for federation sharing: Server-side (Provider): - federationRuntimeApplyRole: validate runtime.Port against share range - validateFederationCommandPorts: hardened against malformed JSON bypass - New helpers: validateRemoteNodePort, remoteNodePortRange Client-side (Consumer): - prepareTunnelCreateState: pre-check ports for remote nodes - tunnelCreate type=1: validate targetPort for remote entry - forwardCreate/Update/BatchChangeTunnel: port range validation Prevents consumers from using arbitrary ports outside provider's allowed range. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -544,6 +544,28 @@ func (h *Handler) federationRemoteUsageList(w http.ResponseWriter, r *http.Reque
|
|||||||
response.WriteJSON(w, response.OK(items))
|
response.WriteJSON(w, response.OK(items))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func remoteNodePortRange(node *nodeRecord) (int, int) {
|
||||||
|
if node == nil || node.IsRemote != 1 || node.RemoteConfig == "" {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
_, _, _, _, portRangeStart, portRangeEnd := parseRemoteShareUsageConfig(node.RemoteConfig)
|
||||||
|
return portRangeStart, portRangeEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRemoteNodePort(node *nodeRecord, port int) error {
|
||||||
|
if node == nil || node.IsRemote != 1 || port <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start, end := remoteNodePortRange(node)
|
||||||
|
if start <= 0 || end <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if port < start || port > end {
|
||||||
|
return fmt.Errorf("远程节点端口 %d 超出允许范围 %d-%d", port, start, end)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseRemoteShareUsageConfig(raw string) (int64, int64, int64, int64, int, int) {
|
func parseRemoteShareUsageConfig(raw string) (int64, int64, int64, int64, int, int) {
|
||||||
raw = strings.TrimSpace(raw)
|
raw = strings.TrimSpace(raw)
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
@@ -980,6 +1002,13 @@ func (h *Handler) federationRuntimeApplyRole(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if share.PortRangeStart > 0 && share.PortRangeEnd > 0 && runtime.Port > 0 {
|
||||||
|
if runtime.Port < share.PortRangeStart || runtime.Port > share.PortRangeEnd {
|
||||||
|
response.WriteJSON(w, response.Err(403, fmt.Sprintf("port %d out of allowed range %d-%d", runtime.Port, share.PortRangeStart, share.PortRangeEnd)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node, err := h.getNodeRecord(share.NodeID)
|
node, err := h.getNodeRecord(share.NodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.WriteJSON(w, response.ErrDefault(err.Error()))
|
response.WriteJSON(w, response.ErrDefault(err.Error()))
|
||||||
@@ -1271,35 +1300,35 @@ func validateFederationCommandPorts(share *sqlite.PeerShare, data interface{}) e
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
services, ok := dataMap["services"]
|
|
||||||
if !ok {
|
if services, ok := dataMap["services"]; ok {
|
||||||
return nil
|
serviceList, ok := services.([]interface{})
|
||||||
}
|
|
||||||
serviceList, ok := services.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, svc := range serviceList {
|
|
||||||
svcMap, ok := svc.(map[string]interface{})
|
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
return fmt.Errorf("invalid services format")
|
||||||
}
|
}
|
||||||
addr, ok := svcMap["addr"].(string)
|
for _, svc := range serviceList {
|
||||||
if !ok || addr == "" {
|
svcMap, ok := svc.(map[string]interface{})
|
||||||
continue
|
if !ok {
|
||||||
}
|
return fmt.Errorf("invalid service entry format")
|
||||||
_, portStr, err := net.SplitHostPort(addr)
|
}
|
||||||
if err != nil {
|
addr, ok := svcMap["addr"].(string)
|
||||||
continue
|
if !ok || addr == "" {
|
||||||
}
|
continue
|
||||||
port, err := strconv.Atoi(portStr)
|
}
|
||||||
if err != nil || port <= 0 {
|
_, portStr, err := net.SplitHostPort(addr)
|
||||||
continue
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("invalid service address: %s", addr)
|
||||||
if port < share.PortRangeStart || port > share.PortRangeEnd {
|
}
|
||||||
return fmt.Errorf("port %d out of allowed range %d-%d", port, share.PortRangeStart, share.PortRangeEnd)
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil || port <= 0 {
|
||||||
|
return fmt.Errorf("invalid port in service address: %s", addr)
|
||||||
|
}
|
||||||
|
if port < share.PortRangeStart || port > share.PortRangeEnd {
|
||||||
|
return fmt.Errorf("port %d out of allowed range %d-%d", port, share.PortRangeStart, share.PortRangeEnd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -545,6 +545,11 @@ func (h *Handler) tunnelCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if targetPort > 0 && targetAddr != "" {
|
if targetPort > 0 && targetAddr != "" {
|
||||||
|
inNodeRec := runtimeState.Nodes[firstNodeID]
|
||||||
|
if err := validateRemoteNodePort(inNodeRec, targetPort); err != nil {
|
||||||
|
response.WriteJSON(w, response.ErrDefault(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
domainCfg, _ := h.repo.GetConfigByName("panel_domain")
|
domainCfg, _ := h.repo.GetConfigByName("panel_domain")
|
||||||
localDomain := ""
|
localDomain := ""
|
||||||
if domainCfg != nil {
|
if domainCfg != nil {
|
||||||
@@ -1105,6 +1110,17 @@ func (h *Handler) forwardCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
if port <= 0 {
|
if port <= 0 {
|
||||||
port = 10000
|
port = 10000
|
||||||
}
|
}
|
||||||
|
entryNodes, _ := h.tunnelEntryNodeIDs(tunnelID)
|
||||||
|
for _, nodeID := range entryNodes {
|
||||||
|
node, nodeErr := h.getNodeRecord(nodeID)
|
||||||
|
if nodeErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := validateRemoteNodePort(node, port); err != nil {
|
||||||
|
response.WriteJSON(w, response.ErrDefault(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
inx := nextIndex(h.repo.DB(), "forward")
|
inx := nextIndex(h.repo.DB(), "forward")
|
||||||
var userName string
|
var userName string
|
||||||
@@ -1126,7 +1142,6 @@ func (h *Handler) forwardCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
response.WriteJSON(w, response.Err(-2, err.Error()))
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entryNodes, _ := h.tunnelEntryNodeIDs(tunnelID)
|
|
||||||
for _, nodeID := range entryNodes {
|
for _, nodeID := range entryNodes {
|
||||||
_, _ = tx.Exec(`INSERT INTO forward_port(forward_id, node_id, port) VALUES(?, ?, ?)`, forwardID, nodeID, port)
|
_, _ = tx.Exec(`INSERT INTO forward_port(forward_id, node_id, port) VALUES(?, ?, ?)`, forwardID, nodeID, port)
|
||||||
}
|
}
|
||||||
@@ -1216,6 +1231,17 @@ func (h *Handler) forwardUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
port = h.pickTunnelPort(tunnelID)
|
port = h.pickTunnelPort(tunnelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fwdEntryNodes, _ := h.tunnelEntryNodeIDs(tunnelID)
|
||||||
|
for _, nodeID := range fwdEntryNodes {
|
||||||
|
node, nodeErr := h.getNodeRecord(nodeID)
|
||||||
|
if nodeErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := validateRemoteNodePort(node, port); err != nil {
|
||||||
|
response.WriteJSON(w, response.ErrDefault(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
_, err = h.repo.DB().Exec(`
|
_, err = h.repo.DB().Exec(`
|
||||||
UPDATE forward SET name = ?, tunnel_id = ?, remote_addr = ?, strategy = ?, updated_time = ? WHERE id = ?
|
UPDATE forward SET name = ?, tunnel_id = ?, remote_addr = ?, strategy = ?, updated_time = ? WHERE id = ?
|
||||||
@@ -1540,6 +1566,22 @@ func (h *Handler) forwardBatchChangeTunnel(w http.ResponseWriter, r *http.Reques
|
|||||||
if p <= 0 {
|
if p <= 0 {
|
||||||
p = h.pickTunnelPort(req.TargetTunnelID)
|
p = h.pickTunnelPort(req.TargetTunnelID)
|
||||||
}
|
}
|
||||||
|
bctEntryNodes, _ := h.tunnelEntryNodeIDs(req.TargetTunnelID)
|
||||||
|
portRangeOk := true
|
||||||
|
for _, nid := range bctEntryNodes {
|
||||||
|
nd, ndErr := h.getNodeRecord(nid)
|
||||||
|
if ndErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if validateRemoteNodePort(nd, p) != nil {
|
||||||
|
portRangeOk = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !portRangeOk {
|
||||||
|
fail++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err := h.replaceForwardPorts(id, req.TargetTunnelID, p); err != nil {
|
if err := h.replaceForwardPorts(id, req.TargetTunnelID, p); err != nil {
|
||||||
h.rollbackForwardMutation(forward, oldPorts)
|
h.rollbackForwardMutation(forward, oldPorts)
|
||||||
fail++
|
fail++
|
||||||
@@ -2235,6 +2277,19 @@ func (h *Handler) prepareTunnelCreateState(tx *store.Tx, req map[string]interfac
|
|||||||
state.Nodes[nodeID] = node
|
state.Nodes[nodeID] = node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, outNode := range state.OutNodes {
|
||||||
|
if err := validateRemoteNodePort(state.Nodes[outNode.NodeID], outNode.Port); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, hop := range state.ChainHops {
|
||||||
|
for _, chainNode := range hop {
|
||||||
|
if err := validateRemoteNodePort(state.Nodes[chainNode.NodeID], chainNode.Port); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user