mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-13 01:14:59 +03:00
fix(inbounds): refuse a Shadowsocks-2022 server key xray-core cannot parse
xray-core parses the inbound-level Shadowsocks-2022 key with ParseKey and refuses one of the wrong size while building the inbound, which keeps the whole core from starting, not only that inbound. Only client keys were checked, so an API caller posting a 32-byte key on aes-128 (or keeping a 16-byte key after switching to aes-256/chacha20) saved fine and took Xray down. AddInbound and UpdateInbound now reject such a key with a message that names the method and the size it needs. The check uses the core's own GetCipherMethod/ParseKey, so a raw non-base64 key the core accepts stays valid. Startup also replaces a stored wrong-size server key: that inbound never worked, so no client holds a working copy of it.
This commit is contained in:
@@ -181,6 +181,9 @@ func initModels() error {
|
||||
if err := migrateShadowsocksRemovedCiphers(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := repairShadowsocks2022ServerKeys(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateVmessRemovedSecurities(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -987,6 +990,37 @@ func migrateShadowsocksRemovedCiphers() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// repairShadowsocks2022ServerKeys replaces a stored server key xray-core cannot
|
||||
// parse; that inbound never worked, as the core refuses to start with it.
|
||||
func repairShadowsocks2022ServerKeys() error {
|
||||
var inbounds []model.Inbound
|
||||
if err := db.Where("protocol = ?", model.Shadowsocks).Find(&inbounds).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, inbound := range inbounds {
|
||||
var settings map[string]any
|
||||
if json.Unmarshal([]byte(inbound.Settings), &settings) != nil {
|
||||
continue
|
||||
}
|
||||
method, _ := settings["method"].(string)
|
||||
n := xray.Shadowsocks2022KeyBytes(method)
|
||||
if key, _ := settings["password"].(string); n == 0 || xray.ValidShadowsocks2022Key(method, key) {
|
||||
continue
|
||||
}
|
||||
settings["password"] = random.Base64Bytes(n)
|
||||
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Model(&model.Inbound{}).Where("id = ?", inbound.Id).
|
||||
Update("settings", string(newSettings)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Inbound %q: replaced a %s server key of the wrong size; its share links changed", inbound.Tag, method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateVmessRemovedSecurities rewrites the vmess "none"/"zero" security
|
||||
// values that xray-core v26.7.11 removed to "auto" (what the core now treats
|
||||
// them as), on both the clients column and each vmess inbound's settings.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
// A stored Shadowsocks-2022 server key of the wrong size keeps xray-core from
|
||||
// starting at all, so startup must replace it and leave every valid key alone.
|
||||
func TestRepairShadowsocks2022ServerKeys(t *testing.T) {
|
||||
dbDir := t.TempDir()
|
||||
t.Setenv("XUI_DB_FOLDER", dbDir)
|
||||
if err := InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
|
||||
t.Fatalf("InitDB: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = CloseDB() })
|
||||
|
||||
key16 := base64.StdEncoding.EncodeToString([]byte("0123456789abcdef"))
|
||||
key32 := base64.StdEncoding.EncodeToString([]byte("0123456789abcdef0123456789abcdef"))
|
||||
rows := []struct {
|
||||
tag, settings, wantKey string
|
||||
wantBytes int
|
||||
}{
|
||||
{"oversized", `{"method":"2022-blake3-aes-128-gcm","password":"` + key32 + `","clients":[]}`, "", 16},
|
||||
{"undersized", `{"method":"2022-blake3-chacha20-poly1305","password":"` + key16 + `","clients":[]}`, "", 32},
|
||||
{"valid", `{"method":"2022-blake3-aes-256-gcm","password":"` + key32 + `","clients":[]}`, key32, 32},
|
||||
{"raw valid", `{"method":"2022-blake3-aes-128-gcm","password":"0123456789abcd-!","clients":[]}`, "0123456789abcd-!", 0},
|
||||
{"legacy cipher", `{"method":"aes-256-gcm","password":"short","clients":[]}`, "short", 0},
|
||||
}
|
||||
for i, r := range rows {
|
||||
ib := model.Inbound{UserId: 1, Port: 31100 + i, Protocol: model.Shadowsocks, Tag: r.tag, Settings: r.settings}
|
||||
if err := GetDB().Create(&ib).Error; err != nil {
|
||||
t.Fatalf("create %s: %v", r.tag, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := repairShadowsocks2022ServerKeys(); err != nil {
|
||||
t.Fatalf("repairShadowsocks2022ServerKeys: %v", err)
|
||||
}
|
||||
|
||||
for _, r := range rows {
|
||||
t.Run(r.tag, func(t *testing.T) {
|
||||
var ib model.Inbound
|
||||
if err := GetDB().Where("tag = ?", r.tag).First(&ib).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(ib.Settings), &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.wantKey != "" {
|
||||
if got.Password != r.wantKey {
|
||||
t.Errorf("key changed to %q, want it kept as %q", got.Password, r.wantKey)
|
||||
}
|
||||
return
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(got.Password)
|
||||
if err != nil || len(decoded) != r.wantBytes {
|
||||
t.Errorf("repaired key %q: %d bytes (err %v), want %d", got.Password, len(decoded), err, r.wantBytes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user