node_foreign_client_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. package service
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  7. "gorm.io/gorm"
  8. )
  9. func readClientUUID(t *testing.T, db *gorm.DB, email string) string {
  10. t.Helper()
  11. var row model.ClientRecord
  12. if err := db.Where("email = ?", email).First(&row).Error; err != nil {
  13. t.Fatalf("read client %q: %v", email, err)
  14. }
  15. return row.UUID
  16. }
  17. // Emails are globally unique, so a node reporting one that belongs to a master
  18. // inbound would otherwise overwrite its credentials and lock the real user out.
  19. func TestNodeCannotClaimClientOfAnotherInbound(t *testing.T) {
  20. db := initTrafficTestDB(t)
  21. svc := &InboundService{}
  22. clientSvc := &ClientService{}
  23. seedNodeRow(t, db, &model.Node{Id: 1, Name: "n1", Address: "127.0.0.1", Port: 2096, ApiToken: "tok", Enable: true})
  24. const (
  25. victim = "victim@x"
  26. nodeLocal = "nodelocal@x"
  27. legitUUID = "11111111-1111-1111-1111-111111111111"
  28. attackUUID = "99999999-9999-9999-9999-999999999999"
  29. )
  30. master := &model.Inbound{
  31. UserId: 1, Tag: "master-in", Enable: true, Port: 40001, Protocol: model.VLESS,
  32. Settings: fmt.Sprintf(`{"clients":[{"email":%q,"id":%q,"enable":true}]}`, victim, legitUUID),
  33. }
  34. if err := db.Create(master).Error; err != nil {
  35. t.Fatalf("create master inbound: %v", err)
  36. }
  37. masterClients, err := svc.GetClients(master)
  38. if err != nil {
  39. t.Fatalf("parse master clients: %v", err)
  40. }
  41. if err := clientSvc.SyncInbound(db, master.Id, masterClients); err != nil {
  42. t.Fatalf("attach master client: %v", err)
  43. }
  44. if got := readClientUUID(t, db, victim); got != legitUUID {
  45. t.Fatalf("setup: master client uuid = %q, want %q", got, legitUUID)
  46. }
  47. createNodeInbound(t, db, 1, "n1-in", 41001)
  48. hostile := fmt.Sprintf(`{"clients":[{"email":%q,"id":%q,"enable":true},{"email":%q,"id":%q,"enable":true}]}`,
  49. victim, attackUUID, nodeLocal, attackUUID)
  50. syncNodeWithSettings(t, svc, 1, "n1-in", hostile,
  51. xray.ClientTraffic{Email: victim, Enable: true},
  52. xray.ClientTraffic{Email: nodeLocal, Enable: true})
  53. if got := readClientUUID(t, db, victim); got != legitUUID {
  54. t.Fatalf("node overwrote a master client's uuid: got %q, want %q", got, legitUUID)
  55. }
  56. nodeAttached, err := clientSvc.ListForInbound(db, nodeInboundID(t, db, "n1-in"))
  57. if err != nil {
  58. t.Fatalf("list node clients: %v", err)
  59. }
  60. for _, c := range nodeAttached {
  61. if c.Email == victim {
  62. t.Fatal("node inbound adopted a client that belongs to a master inbound")
  63. }
  64. }
  65. // The node's own client must still be adopted, or the guard has replaced one
  66. // bug with a worse one.
  67. if got := readClientUUID(t, db, nodeLocal); got != attackUUID {
  68. t.Fatalf("node-owned client not adopted: uuid = %q, want %q", got, attackUUID)
  69. }
  70. }
  71. func nodeInboundID(t *testing.T, db *gorm.DB, tag string) int {
  72. t.Helper()
  73. var ib model.Inbound
  74. if err := db.Where("tag = ?", tag).First(&ib).Error; err != nil {
  75. t.Fatalf("read inbound %q: %v", tag, err)
  76. }
  77. return ib.Id
  78. }