mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-24 14:55:02 +03:00
fix(wireguard): check every line of generated client configs
The WireGuard and AmneziaWG .conf builders (subscription service, clients page, inbound modal and subscription page) checked only a few of the values they write. Each builder now assembles the file line by line and renders nothing when a line holds a control character, so every value is covered whatever stored it. A decoded vpn:// payload is refused when it carries a wg-quick hook directive, which no panel builder writes, and the subscription page hides a config block that rendered nothing. The inbound form now validates a WireGuard/AmneziaWG client's allowedIPs on create and on update, including the stored clients a protocol switch keeps, as the client endpoints already did.
This commit is contained in:
@@ -1409,6 +1409,9 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
if client.PublicKey == "" {
|
||||
return inbound, false, common.NewError("wireguard client requires a key")
|
||||
}
|
||||
if _, err := normalizeWireguardAllowedIPs(client.AllowedIPs); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
case "mtproto":
|
||||
if client.Secret == "" {
|
||||
return inbound, false, common.NewError("mtproto client requires a secret")
|
||||
@@ -1923,6 +1926,10 @@ func (s *InboundService) validateUpdatedInboundClients(inbound *model.Inbound) e
|
||||
if client.Password == "" {
|
||||
return common.NewError("empty client ID")
|
||||
}
|
||||
case model.WireGuard, model.AmneziaWG:
|
||||
if _, err := normalizeWireguardAllowedIPs(client.AllowedIPs); err != nil {
|
||||
return err
|
||||
}
|
||||
case model.TUIC:
|
||||
if client.ID == "" {
|
||||
return common.NewError("empty client ID")
|
||||
|
||||
@@ -265,6 +265,30 @@ func TestNormalizeAmneziaWGSettings_RejectsInjectedClientAllowedIPs(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
// A protocol switch keeps the stored clients, re-injected after the AmneziaWG
|
||||
// normalize ran, so a WireGuard row's bad allowedIPs skipped every check.
|
||||
func TestUpdateInbound_ProtocolSwitchRejectsStoredClientAllowedIPs(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
stored := mkInbound(t, 51830, model.WireGuard, `{"secretKey":"`+awgTestPrivateKey+`","clients":[{"email":"sw@x","enable":true,`+
|
||||
`"privateKey":"`+awgTestPrivateKey+`","publicKey":"`+awgTestPublicKey+`","allowedIPs":["10.8.1.2/32\n[Interface]\nPostUp = touch /tmp/pwned"]}]}`)
|
||||
|
||||
update := *stored
|
||||
update.Protocol = model.AmneziaWG
|
||||
update.Settings = `{"server":{"privateKey":"x","publicKey":"y","subnetIp":"10.8.1.0","subnetCidr":24},"clients":[]}`
|
||||
_, _, err := (&InboundService{}).UpdateInbound(&update)
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid allowedIPs entry") {
|
||||
t.Fatalf("UpdateInbound err = %v, want an invalid allowedIPs entry error", err)
|
||||
}
|
||||
|
||||
var after model.Inbound
|
||||
if err := database.GetDB().First(&after, stored.Id).Error; err != nil {
|
||||
t.Fatalf("reload: %v", err)
|
||||
}
|
||||
if after.Protocol != model.WireGuard {
|
||||
t.Fatalf("rejected switch was persisted: protocol = %q", after.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeAmneziaWGSettings_CanonicalizesClientAllowedIPs(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
svc := &InboundService{}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
@@ -149,6 +150,29 @@ func TestAddInbound_AcceptsWireguardClientWithKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The client endpoints already refuse a non-prefix allowedIPs entry; the inbound
|
||||
// form stored it verbatim, and a line break in it reaches every rendered .conf.
|
||||
func TestAddInbound_RejectsWireguardClientAllowedIPsThatIsNotAPrefix(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
svc := &InboundService{}
|
||||
|
||||
settings := `{"secretKey":"` + wgTestSecretKey() + `","mtu":1420,"clients":[{"email":"wgbad@x","enable":true,"privateKey":"keep-priv","publicKey":"keep-pub",` +
|
||||
`"allowedIPs":["10.0.0.51/32\n[Interface]\nPostUp = touch /tmp/pwned"]}]}`
|
||||
in := &model.Inbound{Tag: "in-45201-wg", Enable: true, Listen: "0.0.0.0", Port: 45201, Protocol: model.WireGuard, Settings: settings}
|
||||
_, _, err := svc.AddInbound(in)
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid allowedIPs entry") {
|
||||
t.Fatalf("AddInbound err = %v, want an invalid allowedIPs entry error", err)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := database.GetDB().Model(&model.Inbound{}).Where("tag = ?", "in-45201-wg").Count(&count).Error; err != nil {
|
||||
t.Fatalf("count: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("rejected WireGuard inbound was persisted anyway, row count = %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
// end-to-end: same guard on the update path, on a row that was valid before
|
||||
// the edit — the rejected StreamSettings must not overwrite the stored row.
|
||||
func TestUpdateInbound_RejectsFinalMaskRealityCombo(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user