IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis52/65-dars18 daqiqa
Mundarija (31)

MongoDB xavfsizligi va operatsiyasi: operator injection, sanitizeFilter, rollar va mongodump

Qisqacha: MongoDB'da SQL injection yo'q, lekin operator injection bor: login so'rovi tanasiga {"email": {"$ne": null}, "parolHash": {"$ne": null}} yuborilsa, tekshiruvsiz filtr birinchi foydalanuvchini — kanonda adminni — qaytardi. Himoya: kirish turini tekshirish (satr bo'lmasa — 400) va Mongoose sanitizeFilter. Server tomonidagi JavaScript ($where, $function) MongoDB 8.0 hujjatida eskirgan deb e'lon qilingan, kanonda 8.3.13 da esa hali ishladi. Operatsiyada: --auth, eng kam huquqli rollar (readWrite, read), mongodump --gzip / mongorestore — va --drop zaxiradan keyingi ma'lumotni o'chirishini unutmang.

Bu darsda

  • Operator injection'ni hujumchi ko'zi bilan ko'rasiz va ikki xil himoyani qo'llaysiz.
  • sanitizeFilter, mongoose.trusted() va strictQuery ni tushunasiz.
  • $where va $function nima uchun xavfli ekanini bilasiz.
  • Autentifikatsiya, localhost exception va rollar bilan eng kam huquq tamoyilini qo'llaysiz.
  • mongodump/mongorestore bilan zaxira olib tiklaysiz va --drop tuzog'idan qochasiz.

Oldin bilishingiz kerak: Replica set, tranzaksiyalar va change streams, Parametrli so'rovlar va SQL injection, Rollar va huquqlar, Backup va restore.

1. Nega bu kerak?

Parametrli so'rovlar va SQL injection darsida asosiy qoida edi: foydalanuvchi kiritgani — ma'lumot, kod emas. SQL'da xavf — satrni so'rovga yopishtirish. MongoDB'da so'rov satr emas, obyekt. "Demak injection yo'q" — keng tarqalgan, lekin xato fikr.

Mongo filtri obyekt, va obyekt ichida operator bo'lishi mumkin. Foydalanuvchi satr o'rniga obyekt yuborsa — u so'rovning ma'nosini o'zgartiradi. Bu NoSQL injection yoki operator injection.

Ikkinchi xavf — operatsiya. Internetda parolsiz ochiq qolgan MongoDB serverlari ma'lumot sizib chiqishining eng mashhur manbalaridan biri bo'lgan: sukut sozlama bilan ishga tushirilgan va tashqariga ochilgan bazalar. Bitta noto'g'ri mongorestore esa xuddi o'chirish kabi ma'lumotni yo'qotadi.

O'xshatish: qorovul "ismingiz Aziz bo'lsa, kiring" deydi. Hujumchi ismini aytmaydi — u "ismim — bo'sh bo'lmagan har qanday narsa" deydi. Qorovul so'zma-so'z tekshirsa, eshik ochiladi.

Chiqishlar — kanon o'lchovi (IlmHamroh, 2026-10): MongoDB 8.3.13, mongosh 2.13.0, database-tools 100.19.1, mongoose 9.11.2, Node 24.21.0; auth sinovi — vaqtinchalik alohida konteynerda. Parollar — kanon skriptidagi lokal namunalar, haqiqiy sir emas.

2. Operator injection

2.1 Hujum

Login handler — so'rov tanasidan email va parol hash'ini olib, foydalanuvchini qidiradi (soddalashtirilgan: haqiqiy login parolni hash bilan tekshiradi — Parolni xavfsiz saqlash, 28-qismda):

js
// ❌ Tekshiruvsiz filtr
const body = JSON.parse(
  '{"email": {"$ne": null}, "parolHash": {"$ne": null}}');
await U.findOne(
  { email: body.email, parolHash: body.parolHash },
  { projection: { _id: 0, email: 1, rol: 1 } },
);

Kanon kolleksiyasida ikki foydalanuvchi: aziz@example.com (rol: "admin") va dilnoza@example.com.

text
topildi (birinchi foydalanuvchi — admin!): { email: 'aziz@example.com', rol: 'admin' }

Filtr endi "email teng emas null VA parolHash teng emas null" — har bir foydalanuvchi mos keladi, findOne birinchisini qaytardi. Birinchi yaratilgan foydalanuvchi ko'pincha admin. Hujumchi parolsiz admin bo'lib kirdi.

Express'ning express.json() (Express'ga kirish) JSON tanani aynan shunday obyektga aylantiradi — hech qanday xato yo'q.

2.2 $regex bilan terish

js
await U.countDocuments({ email: JSON.parse('{"$regex": "^a"}') });
text
$regex bilan email terish: 1

"a bilan boshlanadigan email bormi?" — 1. Keyin ^az, ^azi … Har javobga qarab hujumchi email'ni harfma-harf tiklaydi — parolni ham, agar u shunday tekshirilsa. Bundan tashqari, qimmat regex serverni qiynaydi (ReDoS).

2.3 Query string orqali

JSON tana yagona yo'l emas. Kanon izohi: ?email[$ne]=x — qs kutubxonasi (Express'ning extended query parser'i) undan obyekt yasaydi: { email: { $ne: "x" } }. Express 5 da sukut parser — simple va u obyekt yasamaydi (req obyekti). Kanon bu qismni Mongo bilan sinamadi — 25-qism kanoniga tayanadi. Express 4 dan ko'chirilgan yoki query parser: "extended" yoqilgan loyihada bu teshik ochiq.

sequenceDiagram
  participant H as Hujumchi
  participant E as Express
  participant M as MongoDB
  H->>E: POST /login {"email":{"$ne":null}, ...}
  E->>E: express.json() — obyekt
  E->>M: findOne({ email: {$ne: null}, ... })
  M-->>E: birinchi foydalanuvchi (admin)
  E-->>H: kirish muvaffaqiyatli

Diagrammaga qarang: biror qatlam "bu satr bo'lishi kerak" deb tekshirmadi — xato shu.

3. Himoya

3.1 Tur tekshiruvi — birinchi chiziq

js
const xavfsiz = (b) =>
  typeof b.email === "string" && typeof b.parolHash === "string"
    ? U.findOne({ email: b.email, parolHash: b.parolHash })
    : Promise.resolve("400 — noto'g'ri so'rov");
console.log(await xavfsiz(body));
text
400 — noto'g'ri so'rov

Eng ishonchli himoya: kirish shaklini tekshirish. API'da bu Zod schema'si (Zod 4 serverda): z.object({ email: z.email(), parol: z.string() }) — obyekt kelsa, validatsiya xatosi va 400. vazifalar-api da hamma kirish allaqachon Zod'dan o'tadi — Mongo'ga o'tsa ham shu qatlam himoya qiladi.

3.2 Mongoose sanitizeFilter — ikkinchi chiziq

Mongoose filtrdagi $ bilan boshlanadigan kirish obyektlarini zararsizlantira oladi:

js
await F.findOne({ email: body.email, parolHash: body.parolHash })
  .lean();                                    // himoyasiz
await F.findOne({ email: body.email, parolHash: body.parolHash })
  .setOptions({ sanitizeFilter: true }).lean();
text
sanitizeFilter'siz: aziz@example.com
sanitizeFilter: true: CastError: Cast to string failed for value "{ '$ne': null }" (type Object) at path "parolHash" for model "Foydalanuvchi"

Mongoose orqali ham himoyasiz so'rov Azizni qaytardi — Mongoose o'z-o'zidan himoya qilmaydi. sanitizeFilter: true bilan — CastError: "{ '$ne': null } ni satrga aylantirib bo'lmadi". Ichkarida nima bo'ldi? Biz bazasiz tekshirdik — mongoose.sanitizeFilter funksiyasi filtrni qanday o'zgartiradi:

js
const body = JSON.parse(
  '{"email": {"$ne": null}, "parolHash": {"$ne": null}}');
console.log(JSON.stringify(mongoose.sanitizeFilter(body)));

Natija (biz ishga tushirdik: mongoose 9.11.2, Node 24.21.0, ulanishsiz):

text
{"email":{"$eq":{"$ne":null}},"parolHash":{"$eq":{"$ne":null}}}

{ $ne: null } endi operator emas — $eq ichida qiymat: "email aynan { $ne: null } obyektiga teng bo'lsin". Bunday email yo'q, hujum o'chdi. Schema'da parolHash: String bo'lgani uchun Mongoose bu obyektni satrga aylantirmoqchi bo'ldi va CastError berdi.

Global yoqish (hujjat bo'yicha): mongoose.set("sanitizeFilter", true).

3.3 mongoose.trusted() — ataylab operator

sanitizeFilter yoqilganda o'zingiz yozgan operatorlar ham o'raladi. Ishonchli operatorni mongoose.trusted() bilan belgilaysiz:

js
await F.findOne({ email: mongoose.trusted({ $regex: "^d" }) })
  .setOptions({ sanitizeFilter: true }).lean();
text
mongoose.trusted() — aniq ruxsat: dilnoza@example.com

Bazasiz tekshiruvimiz ham shuni ko'rsatdi — trusted obyekt o'zgarmay qoladi:

text
{"email":{"$regex":"^d"}}

Qoida: trusted() ichiga hech qachon foydalanuvchi kiritganini qo'ymang — faqat kodingizdagi doimiy operatorlarni.

3.4 strictQuery

text
strictQuery (Mongoose 7+ sukut false): sxemada yo'q maydon filtrdan 0 ta topdi (filtr tashlanmadi)

F.countDocuments({ nomalum: "x" }) — sxemada nomalum yo'q, lekin filtr tashlanmadi va bazaga ketdi: 0 natija. Mongoose 6 da strictQuery sukut true edi — noma'lum maydonli filtr jim o'chirilib, {} qolardi, ya'ni hamma hujjat. Mongoose 7 dan sukut false (hujjat bo'yicha) — xavfsizroq. Eski kodni yangilaganda bu farqni biling.

4. Server tomonidagi JavaScript: $where va $function

js
await U.find({ $where: "this.email.length > 16" }).toArray();
await U.find({ $expr: { $function: {
  body: "function(e){ return e.length > 16 }",
  args: ["$email"], lang: "js",
} } }).toArray();
text
$where: 1 ta
$expr + $function: 1 ta

$where va $function — server ichida JavaScript kodini bajaradi. MongoDB 8.0 hujjati ularni eskirgan (deprecated) deb e'lon qilgan: "MongoDB 8.0 dan server tomonidagi JavaScript funksiyalari eskirgan, ishlatilsa logga ogohlantirish yoziladi". Hujjatning hozirgi 9.0-bob sahifalarida bu ogohlantirish yo'q, faqat "boshqa operator bilan yozib bo'lmasagina ishlating" tavsiyasi qolgan (2026-10 holatiga). Kanon ko'rsatdi: 8.3.13 da ikkalasi hali ishlaydi.

Xavf ikki xil:

  1. Kod injection. $where ga foydalanuvchi satri qo'shilsa (`this.ism == '${ism}'`), bu — SQL injection'ning to'liq o'xshashi: hujumchi o'z kodini serverda bajaradi.
  2. Tezlik. JS har hujjat uchun bajariladi, indeks ishlamaydi — butun kolleksiya skani.

Qoida: $where va $function ishlatmang. Deyarli har doim oddiy operatorlar yoki $expr bilan yozish mumkin ({ $expr: { $gt: [{ $strLenCP: "$email" }, 16] } }). Server JS'ni sozlama bilan butunlay o'chirib qo'yish mumkin (security.javascriptEnabled: false — hujjat bo'yicha; sukut bo'yicha yoqiq). Ilova bu operatorlarni ishlatmasa — o'chirib qo'ying.

5. Hujumchi nigohi

Hujum Qanday Himoya
Operator injection (JSON) {"email": {"$ne": null}} Zod/tur tekshiruvi → 400; sanitizeFilter
Operator injection (query) ?email[$ne]=x Express 5 simple parser; tur tekshiruvi
Ma'lumotni terish {"$regex": "^a"} → ^az … Tur tekshiruvi; regex — faqat ekranlangan
Server JS $where ga satr yopishtirish $where/$function yo'q; JS o'chiq
Mass assignment {"rol": "admin"} profil yangilashda Ruxsat etilgan maydonlar ro'yxati (Mongoose schema)
Ochiq port parolsiz 0.0.0.0:27017 --auth, 127.0.0.1/firewall, TLS
Ortiqcha huquq ilova root bilan ulanadi Eng kam huquq: readWrite faqat o'z bazasiga

Uchta qatlam bir-birini to'ldiradi: kirish shakli (Zod), so'rov qatlami (sanitizeFilter), baza qatlami (rollar). Bittasi teshilsa, qolganlari ushlaydi.

6. Autentifikatsiya va rollar

6.1 --auth va localhost exception

Kanon bazasi --auth siz edi (faqat lokal sinov). Auth'ni sinash uchun kanon vaqtinchalik alohida konteynerni --auth bilan ko'tardi, MONGO_INITDB_* o'zgaruvchilarisiz (ya'ni hech qanday foydalanuvchi yo'q):

text
$ [auth] db.getSiblingDB("vazifalar").v.find().toArray()  (mongodb://127.0.0.1:27017/)
MongoServerError: not authorized on vazifalar to execute command { find: "v", filter: {}, lsid: { id: UUID("82f3868c-5ed4-43bc-bfae-c914ff6e5796") }, $db: "vazifalar" }

Foydalanuvchisiz — not authorized ("vazifalar bazasida buyruq bajarishga ruxsat yo'q"). lsid — session id (har safar boshqacha).

Lekin hali hech qanday foydalanuvchi yo'q — birinchisini kim yaratadi? Localhost exception: foydalanuvchilar yo'q paytda, server bilan bir mashinadan (konteyner ichidan) ulangan mijoz birinchi foydalanuvchini yarata oladi:

text
$ [auth] db.getSiblingDB("admin").createUser({user: "admin", pwd: "namuna-admin-parol", roles: ["root"]}).ok  (mongodb://127.0.0.1:27017/)
1
$ [auth] db.getSiblingDB("admin").createUser({user: "boshqa", pwd: "x", roles: []})  (mongodb://127.0.0.1:27017/)
MongoServerError: Command createUser requires authentication

Birinchi createUser — muvaffaqiyatli. Ikkinchisi — endi foydalanuvchi bor, istisno yopildi: "createUser buyrug'i autentifikatsiyani talab qiladi". Shuning uchun yangi serverda birinchi admin'ni darhol yarating — aks holda o'sha mashinadagi har qanday jarayon buni sizdan oldin qilishi mumkin.

6.2 Eng kam huquq: readWrite va read

Admin bilan ikki foydalanuvchi: ilova (api — readWrite) va hisobot (hisobot — faqat read), ikkalasi faqat vazifalar bazasida (Rollar va huquqlar darsidagi Postgres rollari bilan bir xil g'oya):

text
$ [auth] db.getSiblingDB("vazifalar").createUser({user: "api", pwd: "namuna-api-parol", roles: [{role: "readWrite", db: "vazifalar"}]}).ok  (mongodb://admin:namuna-admin-parol@127.0.0.1:27017/?authSource=admin)
1
$ [auth] db.getSiblingDB("vazifalar").createUser({user: "hisobot", pwd: "namuna-hisobot-parol", roles: [{role: "read", db: "vazifalar"}]}).ok  (mongodb://admin:namuna-admin-parol@127.0.0.1:27017/?authSource=admin)
1

authSource=admin — admin foydalanuvchisi admin bazasida yaratilgan, autentifikatsiya o'sha yerda.

Endi har biri nima qila oladi:

text
$ [auth] db.v.insertOne({matn: "api yozdi"}).acknowledged  (mongodb://api:namuna-api-parol@127.0.0.1:27017/vazifalar)
true
$ [auth] db.v.insertOne({matn: "hisobot yozmoqchi"})  (mongodb://hisobot:namuna-hisobot-parol@127.0.0.1:27017/vazifalar)
MongoServerError: not authorized on vazifalar to execute command { insert: "v", documents: [ { matn: "hisobot yozmoqchi", _id: ObjectId('6add9e3f1a1fac541a4002c0') } ], ordered: true, lsid: { id: UUID("05f7f102-2011-4230-b33f-bed148e9ed9c") }, $db: "vazifalar" }
$ [auth] db.v.countDocuments()  (mongodb://hisobot:namuna-hisobot-parol@127.0.0.1:27017/vazifalar)
1
$ [auth] db.getSiblingDB("admin").system.users.find().toArray()  (mongodb://api:namuna-api-parol@127.0.0.1:27017/vazifalar)
MongoServerError: not authorized on admin to execute command { find: "system.users", filter: {}, lsid: { id: UUID("98dd8456-9c49-416c-a062-0d704fb79a39") }, $db: "admin" }
$ [auth] db.v.countDocuments()  (mongodb://api:notogri@127.0.0.1:27017/vazifalar)
MongoServerError: Authentication failed.
Kim Amal Natija
api (readWrite) yozish ruxsat
hisobot (read) yozish not authorized
hisobot o'qish ruxsat (1 hujjat)
api admin.system.users ni o'qish not authorized
api, noto'g'ri parol har qanday Authentication failed.

Ilova SQL injection yoki operator injection orqali buzilsa ham, api foydalanuvchisi boshqa bazalarga, foydalanuvchilar ro'yxatiga va server sozlamalariga tega olmaydi. root bilan ulangan ilova esa buzilsa — hamma narsa hujumchida.

Parollarni bu yerdagidek ulanish satriga yozish — faqat lokal namuna. Haqiqiy loyihada parol .env da yoki sirlar omborida (Sirlarni boshqarish), kodda va git'da emas. Ulanish satridagi parolda maxsus belgilar (@, :, /) bo'lsa — URL-kodlash (encodeURIComponent).

Tekshirib ko'ring: Hisobot servisi faqat statistika o'qiydi. Unga readWrite bersangiz, qanday xavf bor?

Javob

Hisobot servisi buzilsa yoki unda xato bo'lsa (masalan, noto'g'ri deleteMany), u asosiy ma'lumotni o'zgartira yoki o'chira oladi. read roli bilan eng yomon holat — ma'lumot o'qilishi; o'zgartirish imkonsiz. Kanon ko'rsatdi: read foydalanuvchining yozishi not authorized bilan to'xtadi.

6.3 Tarmoq va TLS

Hujjat bo'yicha (kanon sinamadi): bindIp — server qaysi manzillarda tinglaydi (kanonda port faqat 127.0.0.1 ga ochilgan); firewall bilan faqat ilova serverlariga ruxsat; tarmoq orqali ulanishda TLS (tls=true). Atlas'da bular sukut bo'yicha yoqiq va IP ruxsat ro'yxati majburiy.

7. Zaxira: mongodump va mongorestore

7.1 Zaxira olish

mongodump — bazani BSON fayllarga eksport qiladi (Postgres'dagi pg_dump — Backup va restore). Kanon vazifalar bazasini konteyner ichida siqib oldi (qatorlar bo'lingan):

bash
docker exec kanon27-mongo sh -c 'rm -rf /tmp/zaxira &&
  mongodump --quiet \
    --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
    --db=vazifalar --gzip --out=/tmp/zaxira'
text
-rw-r--r-- 1 root root  541 Oct 25 06:14 foydalanuvchilar.bson.gz
-rw-r--r-- 1 root root  158 Oct 25 06:14 foydalanuvchilar.metadata.json.gz
-rw-r--r-- 1 root root   67 Oct 25 06:14 prelude.json.gz
-rw-r--r-- 1 root root 3194 Oct 25 06:14 vazifalar.bson.gz
-rw-r--r-- 1 root root  186 Oct 25 06:14 vazifalar.metadata.json.gz
28K	/tmp/zaxira

(ls -la chiqishining fayl qatorlari; sana-vaqt — sizda boshqacha.) Har kolleksiya uchun ikki fayl: .bson.gz — hujjatlar, .metadata.json.gz — indekslar va sozlamalar. Butun zaxira — 28 KB (--gzip bilan).

7.2 Boshqa nomga tiklash

bash
docker exec kanon27-mongo sh -c 'mongorestore \
  --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --gzip --nsFrom="vazifalar.*" --nsTo="tiklash.*" /tmp/zaxira'
text
2026-10-25T06:14:06.485+0000	no indexes to restore for collection `tiklash.foydalanuvchilar`
2026-10-25T06:14:06.623+0000	56 document(s) restored successfully. 0 document(s) failed to restore.

--nsFrom/--nsTo — tiklashda nomni almashtirish: vazifalar.* → tiklash.*. Asl bazaga tegmasdan zaxirani tekshirishning to'g'ri yo'li. 56 hujjat = 8 foydalanuvchi + 48 vazifa. "tiklash.foydalanuvchilar uchun tiklanadigan indeks yo'q" — u kolleksiyada _id dan boshqa indeks yo'q edi.

text
$ mongosh --eval 'db.getSiblingDB("tiklash").vazifalar.countDocuments() + " / indekslar: " + db.getSiblingDB("tiklash").vazifalar.getIndexes().length'
48 / indekslar: 2

48 hujjat va 2 indeks (_id va aggregation darsida yaratilgan { egasi, bajarildi }) — indekslar ham tiklandi.

7.3 --drop xavfi

Kanon uch qadam qildi. Avval tiklangan bazaga yangi hujjat qo'shdi ("zaxiradan keyin" yozilgan ma'lumot) — 49 ta bo'ldi:

text
$ mongosh --eval 'db.getSiblingDB("tiklash").vazifalar.insertOne({matn: "zaxiradan keyin"}); db.getSiblingDB("tiklash").vazifalar.countDocuments()'
49

Keyin xuddi shu zaxirani --drop bilan qayta tikladi (--quiet — chiqishsiz):

bash
docker exec kanon27-mongo sh -c 'mongorestore --quiet \
  --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --gzip --drop --nsFrom="vazifalar.*" --nsTo="tiklash.*" /tmp/zaxira'

Oxirida "zaxiradan keyin" hujjatini qidirdi:

text
$ mongosh --eval 'db.getSiblingDB("tiklash").vazifalar.countDocuments({matn: "zaxiradan keyin"})'
0

--drop — tiklashdan oldin kolleksiyani o'chiradi. Zaxiradan keyin yozilgan hamma narsa (bu yerda bitta hujjat, prodakshnda — bir kunlik buyurtmalar) yo'qoldi. Bu Postgres'dagi pg_dump --clean restore xavfining o'zi — loyiha qoidalarimizda ham "to'liq restore qilmang, faqat kerakli qatorlarni ko'chiring" deyilgan.

To'g'ri tartib: (1) boshqa nomga tiklash (--nsTo), (2) kerakli hujjatlarni tekshirish, (3) faqat ularni asl bazaga ko'chirish.

Kanon zaxirani faqat shu bazada sinadi. Hujjat bo'yicha: mongodump katta bazalarda sekin va bir lahzalik izchil emas (replica set'da --oplog bilan izchilroq); katta tizimlarda disk snapshot'lari yoki Atlas'ning uzluksiz zaxirasi ishlatiladi. Zaxira tiklab ko'rilmaguncha zaxira emas — buni Backup va restore darsida ham aytgandik.

8. Atlas Search va Vector Search (tanishuv)

Kanon sinamadi — faqat Atlas'da bor (hujjat bo'yicha, 2026-10-26 holatiga):

  • Atlas Search — Lucene asosidagi to'liq matn qidiruvi. Imkoniyatlari: xato yozuvga chidamlilik (fuzzy), avtomatik to'ldirish, til analizatorlari, relevantlik bo'yicha saralash. MongoDB indekslari darsidagi text indeksning cheklovlarini (o'zbek tili, apostrof, sukut OR) hal qiladi. $search aggregation bosqichi bilan.
  • Atlas Vector Search — embedding vektorlari bo'yicha "ma'no bo'yicha" qidiruv: AI yordamchilar, RAG, o'xshash mahsulotlar. $vectorSearch bosqichi. Postgres'da buning o'xshashi — pgvector kengaytmasi (Extension'lar).

Ikkalasi ham ma'lumotni alohida qidiruv tizimiga ko'chirmasdan, Mongo ichida ishlatish imkonini beradi. Narxi — Atlas'ga bog'lanish.

9. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api Postgres'da: kirish Zod bilan tekshiriladi, SQL parametrli, rollar alohida (Rollar va huquqlar), zaxira — pg_dump. Kanon Mongo'da:

  1. Xavfsizlik: $ne injection → admin; $regex terish; tur tekshiruvi → 400; sanitizeFilter → CastError; trusted(); strictQuery false; $where/$function 8.3 da ishlaydi.
  2. Operatsiya: mongodump --gzip (28 KB), --nsFrom/--nsTo bilan 56 hujjat, --drop zaxiradan keyingi hujjatni o'chirdi; --auth: localhost exception, readWrite/read, Authentication failed.

Biz qo'shganimiz: mongoose.sanitizeFilter va trusted ning ichki natijasi (bazasiz).

10. Ko'p uchraydigan xatolar

Xato Oqibat To'g'risi
req.body maydonlari to'g'ridan filtrga Operator injection, admin'ga kirish Zod; sanitizeFilter
"Mongo'da injection yo'q" Himoya yozilmaydi Har kirish — tur tekshiruvi
$where ga satr yopishtirish Serverda begona kod $where ishlatmang
Ilova root bilan ulanadi Buzilsa — butun server readWrite faqat o'z bazasiga
Parolsiz va tashqariga ochiq port Ma'lumot sizib chiqadi --auth, 127.0.0.1, firewall, TLS
Asl bazaga mongorestore --drop Zaxiradan keyingi ma'lumot yo'qoladi --nsTo bilan boshqa nomga
Zaxirani hech qachon tiklab ko'rmaslik Kerak paytda ishlamaydi Muntazam tiklash sinovi
Parol ulanish satrida, git'da Sir sizib chiqadi .env / sirlar ombori

11. Mashqlar

Mashqlar kurs/mashqlar/27/52-xavfsizlik/ da. 2- va 3-mashq yechimlari Node 24 da tekshirilgan (biz ishga tushirdik, bazasiz).

1-mashq (oson): to'ldiring

  • {"email": {"$ne": null}} hujumining nomi — operator
  • Mongoose'da $ li kirishni $eq ga o'raydigan sozlama —
  • Faqat o'qish huquqi beradigan rol —
  • Birinchi foydalanuvchini parolsiz yaratish imkoniyati — localhost
  • Tiklashdan oldin kolleksiyani o'chiradigan mongorestore bayrog'i — --
Yechim
  • injection — operator injection (NoSQL injection).
  • sanitizeFilter — trusted() bilan istisno.
  • read — kanonda yozish not authorized.
  • exception — faqat foydalanuvchi yo'q paytda va o'sha mashinadan.
  • drop — zaxiradan keyingi ma'lumot yo'qoladi.

2-mashq (o'rta): sanitizeFilter modeli

tozala(filtr) funksiyasini yozing — Mongoose sanitizeFilter ning modeli: filtrning har maydonida qiymat obyekt bo'lsa va uning biror kaliti $ bilan boshlansa — qiymatni { $eq: qiymat } ga o'rasin. ishonchli(obj) bilan belgilangan obyektlar o'ralmasin (belgi — Symbol). Kanon hujum tanasi va $regex misoli bilan sinang — natija bazasiz Mongoose chiqishi bilan bir xil bo'lsin.

Ishora: belgi uchun const ISHONCHLI = Symbol("ishonchli") va obyektga [ISHONCHLI]: true xususiyati; JSON.stringify Symbol kalitlarni chiqarmaydi.

Yechim
js
const ISHONCHLI = Symbol("ishonchli");
const ishonchli = (obj) => ({ ...obj, [ISHONCHLI]: true });

function tozala(filtr) {
  const natija = {};
  for (const [k, v] of Object.entries(filtr)) {
    const operatorli = v !== null && typeof v === "object"
      && Object.keys(v).some((x) => x.startsWith("$"));
    natija[k] = operatorli && !v[ISHONCHLI] ? { $eq: v } : v;
  }
  return natija;
}

const body = JSON.parse(
  '{"email": {"$ne": null}, "parolHash": {"$ne": null}}');
console.log(JSON.stringify(tozala(body)));
console.log(JSON.stringify(
  tozala({ email: ishonchli({ $regex: "^d" }) })));
console.log(JSON.stringify(
  tozala({ email: "aziz@example.com", parolHash: "h1" })));

Konsolda:

text
{"email":{"$eq":{"$ne":null}},"parolHash":{"$eq":{"$ne":null}}}
{"email":{"$regex":"^d"}}
{"email":"aziz@example.com","parolHash":"h1"}

Birinchi ikki qator — «Mongoose sanitizeFilter» bo'limidagi haqiqiy Mongoose natijasi bilan aynan bir xil. Uchinchisi: oddiy satrlar o'zgarmaydi — himoya halol so'rovga xalaqit bermaydi. Haqiqiy Mongoose bundan ko'proq ishni qiladi (ichma-ich $and/$or, massivlar).

3-mashq (qiyin): hujum va himoya

Xotiradagi "kolleksiya" va kichik filtr mos kelishini yozing: mos(hujjat, filtr) — tenglik va $ne operatori. Keyin ikki login funksiyasi: loginZaif(body) — body maydonlarini to'g'ridan filtrga qo'yadi; loginXavfsiz(body) — email va parolHash satr bo'lmasa "400", aks holda qidiradi. Kanon hujumini ($ne: null) va to'g'ri loginni ikkala funksiyada sinang.

Ishora: find — birinchi mos hujjat (Mongo'dagi findOne kabi).

Yechim
js
const foydalanuvchilar = [
  { email: "aziz@example.com", parolHash: "h1", rol: "admin" },
  { email: "dilnoza@example.com", parolHash: "h2", rol: "oddiy" },
];

function mos(h, filtr) {
  return Object.entries(filtr).every(([k, shart]) => {
    if (shart !== null && typeof shart === "object") {
      if ("$ne" in shart) return h[k] !== shart.$ne;
      return false;
    }
    return h[k] === shart;
  });
}
const findOne = (filtr) =>
  foydalanuvchilar.find((h) => mos(h, filtr)) ?? null;

function loginZaif(body) {
  const u = findOne({ email: body.email, parolHash: body.parolHash });
  return u ? `kirdi: ${u.email} (${u.rol})` : "401";
}
function loginXavfsiz(body) {
  if (typeof body.email !== "string"
    || typeof body.parolHash !== "string") return "400";
  return loginZaif(body);
}

const hujum = JSON.parse(
  '{"email": {"$ne": null}, "parolHash": {"$ne": null}}');
const halol = { email: "dilnoza@example.com", parolHash: "h2" };
console.log("zaif, hujum:", loginZaif(hujum));
console.log("xavfsiz, hujum:", loginXavfsiz(hujum));
console.log("xavfsiz, halol:", loginXavfsiz(halol));
console.log("xavfsiz, xato parol:",
  loginXavfsiz({ ...halol, parolHash: "x" }));

Konsolda:

text
zaif, hujum: kirdi: aziz@example.com (admin)
xavfsiz, hujum: 400
xavfsiz, halol: kirdi: dilnoza@example.com (oddiy)
xavfsiz, xato parol: 401

Birinchi qator — kanondagi hujum natijasi: parolsiz admin. Tur tekshiruvi hujumni 400 bilan to'xtatdi, halol login esa ishlayveradi. Haqiqiy loyihada typeof o'rniga Zod schema'si va parolni hash bilan solishtirish (28-qismda) — g'oya bir xil.

12. Real ishda

  • Operator injection — Node + Mongo loyihalarining xavfsizlik auditlarida eng ko'p topiladigan muammolardan biri; Zod/Joi validatsiya va sanitizeFilter — standart javob.
  • Rollar — har servis o'z foydalanuvchisi bilan: API — readWrite, hisobot — read, zaxira — backup roli; admin faqat qo'lda.
  • Zaxira — Atlas'da avtomatik va uzluksiz; o'z serverida — mongodump cron bilan + muntazam tiklash sinovi.
  • Intervyu savollari: "MongoDB'da injection bormi?", "sanitizeFilter nima qiladi?", "eng kam huquq tamoyili Mongo'da qanday?", "mongorestore --drop xavfi?".

Xulosa

  • Mongo'da SQL injection yo'q, lekin operator injection bor: {"$ne": null} kanonda admin'ga parolsiz kirdi; $regex bilan ma'lumot terish mumkin.
  • Himoya qatlamlari: tur tekshiruvi/Zod (400), Mongoose sanitizeFilter ($eq ga o'raydi, trusted() — istisno), strictQuery sukut false.
  • $where/$function — 8.0 hujjatida eskirgan deb e'lon qilingan, 8.3 da ishlaydi; ishlatmang.
  • --auth, localhost exception (faqat birinchi foydalanuvchi), eng kam huquq: readWrite/read faqat o'z bazasiga.
  • mongodump --gzip → mongorestore --nsFrom/--nsTo (boshqa nomga); --drop zaxiradan keyingi ma'lumotni o'chiradi.
  • Atlas Search va Vector Search — Atlas'dagi qidiruv imkoniyatlari (sinalmagan).

Keyingi dars: Redis nima va ulanish — xotiradagi kalit-qiymat ombori, Docker'da Redis 8 va Node'dan node-redis bilan ulanish.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
MongoDB xavfsizligi va operatsiyasi: operator injection, sanitizeFilter, rollar va mongodump — IlmHamroh