mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-15 15:50:59 +00:00
fix(db): harden unrestricted freedom outbounds (#6184)
This commit is contained in:
+12
-1
@@ -1628,6 +1628,14 @@ func isLegacyPrivateOnlyFinalRules(v any) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUnrestrictedFreedomFinalRules(v any, present bool) bool {
|
||||||
|
if !present || v == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
rules, ok := v.([]any)
|
||||||
|
return ok && len(rules) == 0
|
||||||
|
}
|
||||||
|
|
||||||
func hardenFreedomFinalRules() error {
|
func hardenFreedomFinalRules() error {
|
||||||
var setting model.Setting
|
var setting model.Setting
|
||||||
err := db.Model(model.Setting{}).Where("key = ?", "xrayTemplateConfig").First(&setting).Error
|
err := db.Model(model.Setting{}).Where("key = ?", "xrayTemplateConfig").First(&setting).Error
|
||||||
@@ -1680,7 +1688,10 @@ func rewriteFreedomFinalRulesPrivateEgress(raw string) (string, bool, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !isAllowOnlyFinalRules(settings["finalRules"]) && !isLegacyPrivateOnlyFinalRules(settings["finalRules"]) {
|
finalRules, present := settings["finalRules"]
|
||||||
|
if !isUnrestrictedFreedomFinalRules(finalRules, present) &&
|
||||||
|
!isAllowOnlyFinalRules(finalRules) &&
|
||||||
|
!isLegacyPrivateOnlyFinalRules(finalRules) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
settings["finalRules"] = []any{
|
settings["finalRules"] = []any{
|
||||||
|
|||||||
@@ -23,6 +23,24 @@ func TestRewriteFreedomFinalRulesPrivateEgress(t *testing.T) {
|
|||||||
wantChanged: true,
|
wantChanged: true,
|
||||||
wantRules: hardened,
|
wantRules: hardened,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "missing finalRules is hardened",
|
||||||
|
raw: `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs"},"tag":"direct"}]}`,
|
||||||
|
wantChanged: true,
|
||||||
|
wantRules: hardened,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "null finalRules is hardened",
|
||||||
|
raw: `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs","finalRules":null},"tag":"direct"}]}`,
|
||||||
|
wantChanged: true,
|
||||||
|
wantRules: hardened,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty finalRules is hardened",
|
||||||
|
raw: `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs","finalRules":[]},"tag":"direct"}]}`,
|
||||||
|
wantChanged: true,
|
||||||
|
wantRules: hardened,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "legacy private-only allow is hardened",
|
name: "legacy private-only allow is hardened",
|
||||||
raw: `{"outbounds":[{"protocol":"freedom","settings":{"finalRules":[{"action":"allow","ip":["geoip:private"]}]},"tag":"direct"}]}`,
|
raw: `{"outbounds":[{"protocol":"freedom","settings":{"finalRules":[{"action":"allow","ip":["geoip:private"]}]},"tag":"direct"}]}`,
|
||||||
@@ -76,6 +94,40 @@ func TestRewriteFreedomFinalRulesPrivateEgress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRewriteFreedomFinalRulesPreservesSplitRouting(t *testing.T) {
|
||||||
|
const raw = `{
|
||||||
|
"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs"},"tag":"direct"}],
|
||||||
|
"routing":{"domainStrategy":"AsIs","rules":[
|
||||||
|
{"type":"field","domain":["regexp:.*\\.ru$"],"outboundTag":"direct"},
|
||||||
|
{"type":"field","network":"tcp,udp","outboundTag":"proxy"}
|
||||||
|
]}
|
||||||
|
}`
|
||||||
|
updated, changed, err := rewriteFreedomFinalRulesPrivateEgress(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("rewrite: %v", err)
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
t.Fatal("missing finalRules must be hardened")
|
||||||
|
}
|
||||||
|
var before, after map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(raw), &before); err != nil {
|
||||||
|
t.Fatalf("decode before: %v", err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(updated), &after); err != nil {
|
||||||
|
t.Fatalf("decode after: %v", err)
|
||||||
|
}
|
||||||
|
beforeRouting, _ := json.Marshal(before["routing"])
|
||||||
|
afterRouting, _ := json.Marshal(after["routing"])
|
||||||
|
if string(afterRouting) != string(beforeRouting) {
|
||||||
|
t.Fatalf("split routing changed:\n got %s\nwant %s", afterRouting, beforeRouting)
|
||||||
|
}
|
||||||
|
outbound := after["outbounds"].([]any)[0].(map[string]any)
|
||||||
|
settings := outbound["settings"].(map[string]any)
|
||||||
|
if settings["domainStrategy"] != "AsIs" {
|
||||||
|
t.Fatalf("freedom domainStrategy=%v want AsIs", settings["domainStrategy"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRewriteFreedomFinalRulesPrivateEgressInvalidJSON(t *testing.T) {
|
func TestRewriteFreedomFinalRulesPrivateEgressInvalidJSON(t *testing.T) {
|
||||||
_, changed, err := rewriteFreedomFinalRulesPrivateEgress("{not json")
|
_, changed, err := rewriteFreedomFinalRulesPrivateEgress("{not json")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user