IlmHamroh
JavaScript Full-stack/28-qism. Autentifikatsiya va backend xavfsizligi27/31-dars21 daqiqa
Mundarija (34)

Backend'dagi injection va mass assignment: SQL, operator obyektlari, prototype pollution, log va sarlavha

Qisqacha: Injection — tashqaridan kelgan ma'lumot kod yoki buyruq sifatida talqin qilinishi: SQL so'rovi, baza operatori, shell buyrug'i, log qatori, HTTP sarlavhasi. Mass assignment — tanadagi ortiqcha maydon (rol: "admin") obyektga jim ko'chishi. Hammasining himoyasi bitta g'oyaga borib taqaladi: ma'lumot va kodni ajratish (parametr, argument massivi, JSON) va kiruvchi shaklni ruxsat ro'yxati bilan qat'iy tekshirish. Kanon vazifalar-api ga yetti xil hujum testini yozdi — va yangi teshik topmadi. Bu darsda nega topilmaganini, ya'ni qaysi qatlam nimani ushlab qolganini ko'ramiz.

Bu darsda

  • Injection'ning umumiy formulasini va uning backend'dagi oltita ko'rinishini taniysiz.
  • Operator obyektlari ({"$ne": null}) va Express 5 query parser'ini tushuntirasiz.
  • Prototype pollution qachon xavfli va JSON.parse qachon xavfsiz ekanini ko'rasiz.
  • Mass assignment'ni ruxsat ro'yxati va qat'iy sxema bilan yopasiz.
  • vazifalar-api ga hujum ssenariylari testini qo'shasiz va commit qilasiz.

Oldin bilishingiz kerak: Parametrli so'rovlar va SQL injection, Validatsiya: nima uchun, qayerda va qanday, Prototype pollution, Regex amaliyotda va ReDoS, child_process, Logging asoslari.

Bu darsdagi hujumlarning ko'pi sizga tanish: SQL injection — 26-qismda, prototype pollution — 10-qismda, ReDoS — 12-qismda, mass assignment — 25-qismda. Bugun ularni qayta o'rgatmaymiz. Boshqa savol beramiz: tayyor API bularning hammasiga bardosh beradimi va agar bersa — aynan qaysi qatori tufayli?

1. Nega bu kerak?

1.1 Umumiy formula

Har injection bir xil sxemada ishlaydi: dastur matn yig'adi, unga foydalanuvchi qiymatini qo'shadi va natijani talqin qiluvchiga beradi. Talqin qiluvchi qiymatni ma'lumot emas, buyruq deb o'qiydi.

Talqin qiluvchi Yig'ilgan matn Hujum
Postgres SQL so'rovi SQL injection
hujjatli baza (Mongo) filtr obyekti operator injection (NoSQL)
shell buyruq qatori command injection
log tahlilchisi log qatori log injection
HTTP sarlavhalar CRLF (header) injection
JS obyekt modeli obyekt kalitlari prototype pollution

Himoya ham bitta: talqin qiluvchiga ma'lumotni alohida kanal orqali bering — SQL'da $1 parametr, shell'da argumentlar massivi, logda JSON maydon. Matn yig'ishdan qochib bo'lmasa — qiymatni ruxsat ro'yxati bilan tekshiring.

OWASP Top 10:2025 da bu toifa — A05 Injection. API Top 10:2023 da mass assignment — API3 (obyekt xususiyati darajasidagi avtorizatsiya buzilishi, BOPLA).

1.2 Kanon nima qildi

Kanon 25–27-qismlardagi vazifalar-api ga va 28-qismning yangi auth yo'llariga haqiqiy Postgres bilan yetti ssenariyli hujum testi yozdi. Natija: yangi zaiflik topilmadi. Bu ham natija — u qoidalar ishlaganini ko'rsatadi. Lekin "ishladi" deyish uchun qaysi qatlam nimani ushlaganini bilish kerak, aks holda keyingi o'zgarishda o'sha qatlamni bilmasdan olib tashlaysiz.

2. SQL injection: takror va tekshiruv

26-qismdagi qoida: qiymat — parametr ($1), nom (ustun, tartib) — ruxsat ro'yxati, LIKE belgilari — ekranlash (Parametrli so'rovlar va SQL injection). Hujumchi uchta joyni sinaydi. Jonli server, Azizning sessiyasi bilan (biz ishga tushirdik: kanon kodi, Node 24.21.0, lokal PostgreSQL 17.6; sarlavhalar va sorovId qisqartirilgan).

Qidiruvda klassik payload:

bash
curl "$A/v1/vazifalar?q=%27%20OR%201%3D1%20--&fields=id" \
  -H "Cookie: $C"
text
HTTP/1.1 200 OK

{"versiya":13,"vazifalar":[],"sahifa":{"limit":20,"keyingi":null}}

' OR 1=1 -- — oddiy qidiruv so'zi bo'lib qoldi va hech narsa topilmadi. Saralashda esa nom ishlatiladi — u parametr bo'la olmaydi:

bash
curl "$A/v1/vazifalar?sort=matn;DROP%20TABLE%20vazifalar" \
  -H "Cookie: $C"
text
HTTP/1.1 400 Bad Request

{"type":"https://vazifalar-api.example/xatolar/parametr","title":"Parametr noto'g'ri","status":400,"detail":"sort: noma'lum maydon \"matn;DROP TABLE vazifalar\" (mumkin: id, tartib, matn, bajarildi, yaratildi, yangilandi)","instance":"/v1/vazifalar?sort=matn;DROP%20TABLE%20vazifalar","kod":"parametr"}

Ruxsat ro'yxati ishladi va xabar hatto mumkin bo'lgan qiymatlarni aytdi — bu sir emas, ommaviy hujjat. Kanon testi yana uchta joyni tekshiradi: login emailidagi uchta payload (401/422), %, _, \ va UNION SELECT qidiruvda (0 natija), sort=constructor (400 — ruxsat ro'yxati Object.hasOwn bilan, prototip kalitlari o'tmaydi).

Prisma ishlatsangiz (Prisma: raw SQL va TypedSQL), xavfli joy — xom SQL: teglangan shablon $queryRaw parametrlarga aylantiradi, $queryRawUnsafe esa satrni o'zingiz yig'asiz. Qoida o'zgarmaydi.

3. Operator injection: {"$ne": null}

3.1 Hujum

Hujjatli bazalarda (MongoDB, MongoDB xavfsizligi va operatsiyasi) filtr — obyekt. Agar tanadagi qiymat to'g'ridan-to'g'ri filtrga tushsa, hujumchi qiymat o'rniga operator yuboradi. Soddalashtirilgan model:

js
// soddalashtirilgan "hujjatli baza" filtri:
// { maydon: qiymat } yoki { maydon: { $ne: qiymat } }
const HISOBLAR = [
  { email: "aziz@example.com", kod: "7341", rol: "admin" },
  { email: "dilnoza@example.com", kod: "9025", rol: "foydalanuvchi" },
];

function mos(qiymat, shart) {
  if (typeof shart === "object" && shart !== null && "$ne" in shart) {
    return qiymat !== shart.$ne;
  }
  return qiymat === shart;
}

function topish(filtr) {
  return HISOBLAR.find((h) =>
    Object.entries(filtr).every(([k, s]) => mos(h[k], s)));
}

// zaif: tanadagi qiymatlar to'g'ridan-to'g'ri filtrga
function kirishZaif(t) {
  return topish({ email: t.email, kod: t.kod })?.email ?? "401";
}

// himoya: faqat satr qabul qilinadi
function kirish(t) {
  if (typeof t.email !== "string" || typeof t.kod !== "string") {
    return "422";
  }
  return topish({ email: t.email, kod: t.kod })?.email ?? "401";
}

const hujum = JSON.parse('{"email":{"$ne":null},"kod":{"$ne":null}}');
console.log(kirishZaif(hujum));
console.log(kirish(hujum));
console.log(kirish({ email: "dilnoza@example.com", kod: "9025" }));

Konsolda:

text
aziz@example.com
422
dilnoza@example.com

"Email null emas va kod null emas" — bunday birinchi hisob — admin Aziz. Hujumchi parolsiz kirdi. Himoya bitta qator: tur tekshiruvi. Email — satr, obyekt emas.

3.2 vazifalar-api da

Bizda Postgres, lekin shu payload'lar baribir sinaladi — ular boshqa joyda 500 xato berishi mumkin. Jonli server (biz ishga tushirdik):

text
HTTP/1.1 422 Unprocessable Entity

{"type":"https://vazifalar-api.example/xatolar/validatsiya","title":"So'rov ma'lumoti noto'g'ri","status":422,"detail":"2 ta maydon noto'g'ri","instance":"/v1/auth/kirish","kod":"validatsiya","xatolar":[{"joy":"body.email","xabar":"Noto‘g‘ri kirish: kutilgan string, qabul qilingan object"},{"joy":"body.parol","xabar":"Noto‘g‘ri kirish: kutilgan string, qabul qilingan object"}]}

Zod sxemasi email va parol ni satr deb kutadi — obyekt 422 bilan qaytdi, so'rov hatto parol hash'iga yetmadi. Xabardagi ‘ — Zod o'zbekcha lokalining tutuq belgisi, kanon chiqishidagidek qoldirdik.

Query satri orqali urinish: ?bajarildi[$ne]=true. Express 4 da sukutdagi "extended" query parser buni ichma-ich obyektga aylantirardi: { bajarildi: { $ne: "true" } }. Express 5 da sukut "simple" ga o'zgargan — kalit oddiy satr bajarildi[$ne] bo'lib qoladi:

text
HTTP/1.1 400 Bad Request

{"type":"https://vazifalar-api.example/xatolar/parametr","title":"Parametr noto'g'ri","status":400,"detail":"noma'lum parametr: bajarildi[$ne]","instance":"/v1/vazifalar?bajarildi%5B%24ne%5D=true","kod":"parametr"}

Ikki qatlam: Express 5 sukuti obyekt yasamadi, 25-qismdagi qat'iy query o'quvchi noma'lum parametrni rad etdi.

4. Command injection

Foydalanuvchi qiymati shell buyrug'iga tushsa (exec(\convert ${fayl} …`)), ;, &&, |` yangi buyruq boshlaydi (child_process: tashqi dasturlarni ishga tushirish). Himoya — shell'siz ishga tushirish va argumentlarni massiv bilan berish:

js
import { execFileSync } from "node:child_process";

// hujumchi kiritgan "fayl nomi"
const nom = "hisobot.txt; echo BUZILDI";
const kod = "console.log(JSON.stringify(process.argv.slice(1)))";
const chiqish = execFileSync(process.execPath, ["-e", kod, nom], {
  encoding: "utf8",
});
console.log(chiqish.trim());

Konsolda:

text
["hisobot.txt; echo BUZILDI"]

Bola jarayon (node -e) o'z argumentlarini chiqardi. Butun satr — bitta argument, echo ishga tushmadi. execFile shell chaqirmaydi, shuning uchun ; oddiy belgi. vazifalar-api tashqi dastur ishga tushirmaydi — bu xavf bizda yo'q, lekin 29-qismdagi fayl va rasm ishlov berishda paydo bo'ladi.

5. Log va sarlavha injection

5.1 Log: soxta qator

Matnli logda yangi qator belgisi (\n) bitta yozuvni ikkiga bo'ladi. Hujumchi emailga "o'z log qatorini" qo'shadi va tahlilchi uni haqiqiy deb o'qiydi:

js
const email = 'a@b.uz\n{"daraja":"info","msg":"admin kirdi"}';

const matnLog = `daraja=warn msg="kirish xato" email=${email}`;
console.log(matnLog.split("\n").length, "qator");
console.log(matnLog);

const jsonLog = JSON.stringify({ daraja: "warn", email });
console.log(jsonLog.split("\n").length, "qator");
console.log(jsonLog);

Konsolda:

text
2 qator
daraja=warn msg="kirish xato" email=a@b.uz
{"daraja":"info","msg":"admin kirdi"}
1 qator
{"daraja":"warn","email":"a@b.uz\n{\"daraja\":\"info\",\"msg\":\"admin kirdi\"}"}

Matnli logda ikkinchi qator — "admin kirdi" — soxta, lekin haqiqiysidan farqi yo'q. JSON'da \n va qo'shtirnoq ekranlanadi — yozuv bitta va hujumchi matni faqat email maydoni ichida. vazifalar-api 25-qismdan beri pino bilan JSON log yozadi (Logging asoslari). Kanon testi buni tekshiradi: hujumdan keyin har log qatori JSON.parse dan o'tadi va hech biri {"daraja":"info","msg":"admin kirdi" bilan boshlanmaydi.

5.2 Sarlavha: CRLF

HTTP sarlavhalari \r\n bilan ajratiladi. Foydalanuvchi qiymati sarlavhaga tushsa (masalan, Location ga qaytish manzili), hujumchi \r\n orqali o'z sarlavhasini — hatto Set-Cookie ni — qo'shadi. Bu CRLF injection (yoki response splitting). Node bunga yo'l qo'ymaydi:

js
import { validateHeaderValue } from "node:http";

validateHeaderValue("Location", "/v1/vazifalar/7");
console.log("toza qiymat — o'tdi");
validateHeaderValue("Location", "/v1\r\nSet-Cookie: admin=1");

Konsolda:

text
toza qiymat — o'tdi
TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["Location"]

"Sarlavha mazmunida yaroqsiz belgi". res.setHeader ichida aynan shu tekshiruv ishlaydi — kanon testi ServerResponse bilan buni tasdiqlaydi. Bundan tashqari X-Request-Id 25-qismdan beri regex bilan tekshiriladi: yaroqsiz qiymat (abc def) o'rniga yangi UUID. Kanon sinovida yana bir narsa ko'rindi: Node'ning HTTP mijozi ham (supertest ichida) sarlavhaga \n yubora olmaydi — shuning uchun log hujumi tana orqali sinaldi.

6. Prototype pollution va JSON.parse

6.1 JSON.parse — xavfsiz, keyingisi — yo'q

Prototype pollution darsida mexanizmni ko'rgansiz. Server uchun muhim nozik joy: JSON.parse "__proto__" ni oddiy kalit qiladi. Xavf undan keyingi ishlovda:

js
const tana = '{"matn":"Proto sinov","__proto__":{"isAdmin":true}}';
const obj = JSON.parse(tana);
console.log(JSON.stringify(Object.keys(obj)), ({}).isAdmin);

// 1) Object.assign "__proto__" ni setter orqali yozadi
const nusxa = Object.assign({}, obj);
console.log(nusxa.isAdmin, ({}).isAdmin);

// 2) sodda chuqur birlashtirish — global prototipga yetib boradi
function birlashtir(a, b) {
  for (const k of Object.keys(b)) {
    if (typeof b[k] === "object" && b[k] !== null) {
      a[k] ??= {};
      birlashtir(a[k], b[k]);
    } else {
      a[k] = b[k];
    }
  }
  return a;
}
birlashtir({}, obj);
console.log(({}).isAdmin);
delete Object.prototype.isAdmin; // tozalaymiz
console.log(({}).isAdmin);

Konsolda:

text
["matn","__proto__"] undefined
true undefined
true
undefined

Uch bosqich:

  1. JSON.parse — __proto__ oddiy kalit, hech narsa ifloslanmadi.
  2. Object.assign — nusxa ning o'z prototipi almashdi (nusxa.isAdmin — true), global hali toza.
  3. Sodda chuqur birlashtirish — a["__proto__"] bu Object.prototype ning o'zi. Endi har obyektda isAdmin: true. Serverda bu — if (foydalanuvchi.isAdmin) tekshiruvini butun jarayon uchun buzish.

6.2 vazifalar-api nega xavfsiz

Ikki sabab: kodda chuqur birlashtirish yo'q, va har tana Zod strictObject dan o'tadi — __proto__ noma'lum kalit (biz ishga tushirdik):

text
HTTP/1.1 422 Unprocessable Entity

{"type":"https://vazifalar-api.example/xatolar/validatsiya","title":"So'rov ma'lumoti noto'g'ri","status":422,"detail":"Noma’lum kalit: \"__proto__\"","instance":"/v1/vazifalar","kod":"validatsiya","xatolar":[{"joy":"body","xabar":"Noma’lum kalit: \"__proto__\""}]}

Sozlamalar kabi "qisman yangilash" kerak bo'lsa — birlashtirishni o'zingiz yozasiz. Shunda xavfli kalitlarni o'tkazib yuborish shart (2-mashq) yoki Object.create(null) dan foydalaning.

Tekshirib ko'ring: Hamkasbingiz: "Biz JSON.parse ishlatamiz, u __proto__ ni oddiy kalit qiladi — demak prototype pollution bizga tegishli emas". Qachon u haq, qachon yo'q?

Javob

Faqat ma'lumot JSON.parse dan keyin hech qayerda kalitlari bo'yicha boshqa obyektga ko'chirilmasa, u haq. Object.assign, sodda chuqur birlashtirish, obj[kalit] = qiymat sikli yoki eski kutubxonaning merge funksiyasi "__proto__" kalitini setter orqali yozadi — xavf aynan o'sha yerda. Ishonchli yo'l — tanani qat'iy sxemadan o'tkazish: noma'lum kalit, shu jumladan __proto__, darhol rad etiladi.

6.3 Katta va chuqur JSON

JSON.parse kodni bajarmaydi (eval dan farqi shu), lekin u ham resurs sarflaydi. Ikki chegara: tana hajmi — 16 KB (express.json({ limit }), 25-qism) va ichma-ichlik. Kanon testi: 17 000 belgili tana — 413; 16 KB ichiga sig'adigan 2 500 darajali {"a":{"a":…}} — 422, server ishlashda davom etadi.

Halol eslatma: kanon test nomida "5 000 darajali" deb yozilgan, kodda esa repeat(2_500) — 2 500 daraja. Test o'tadi, nomi noto'g'ri. Siz yozganingizda sonni nomga konstantadan qo'ying.

7. Mass assignment

7.1 Muammo

js
const bazada = { id: 7, ism: "Sardor", rol: "foydalanuvchi" };
const tana = JSON.parse('{"ism":"Sardor A.","rol":"admin"}');

// zaif: hamma maydon ko'chadi
const zaif = { ...bazada, ...tana };
console.log(zaif.rol);

// ruxsat ro'yxati: faqat ism
const RUXSAT = ["ism"];
const ortiqcha = Object.keys(tana).filter((k) => !RUXSAT.includes(k));
if (ortiqcha.length > 0) {
  console.log(`422: noma'lum kalit — ${ortiqcha.join(", ")}`);
} else {
  console.log({ ...bazada, ...tana }.rol);
}

Konsolda:

text
admin
422: noma'lum kalit — rol

Sardor ismini o'zgartirmoqchi edi va o'zini admin qildi. ORM'larda bu xuddi shunday: prisma.foydalanuvchi.update({ data: req.body }) — tanadagi hamma maydon bazaga ketadi.

Ikki yechim:

  • Jim tashlash (pick) — faqat ruxsat etilganlarni olish, qolganini e'tiborsiz qoldirish.
  • Rad etish (strictObject) — noma'lum maydon bo'lsa 422. Kanon shuni tanlagan.

Rad etish yaxshiroq: frontend xatosi (maydon nomida xato) darhol ko'rinadi, hujumchi esa urinishi jurnalga tushadi. Jim tashlashda ikkalasi ham ko'rinmaydi.

7.2 vazifalar-api da

Jonli server, Azizning sessiyasi bilan vazifaga begona egasi va versiya (biz ishga tushirdik):

text
HTTP/1.1 422 Unprocessable Entity

{"type":"https://vazifalar-api.example/xatolar/validatsiya","title":"So'rov ma'lumoti noto'g'ri","status":422,"detail":"Noma’lum kalitlar: \"egasiId\", \"versiya\"","instance":"/v1/vazifalar","kod":"validatsiya","xatolar":[{"joy":"body","xabar":"Noma’lum kalitlar: \"egasiId\", \"versiya\""}]}

egasiId — bu yerda eng qimmatli o'lja: u bo'lsa, Aziz vazifani Dilnozaning nomiga yozardi. Lekin egasi tanadan emas, sessiyadan olinadi (RBAC va resurs egaligi), sxema esa uni umuman tanimaydi. Auth yo'llarida ham xuddi shu: ro'yxatda rol: "admin" yoki emailTasdiqlangan: true — 422. Bu yerda tuzatish kerak bo'lmadi: 25-qismdan beri vazifa sxemasi strictObject, auth sxemalari ham shu naqshda yozilgan.

8. ReDoS — qisqa

Foydalanuvchi matnini zaif regex bilan tekshirish — ham "injection" (matn regex mexanizmini boshqaradi), ham DoS (Regex amaliyotda va ReDoS). Kanon o'z regex'larini 16 KB kirish bilan o'lchadi (kanon tajribasi):

text
  kanon TEG regex (vazifa.ts), "#" + 16 000 harf + "'" ko'p: 0.94
  kanon TEG regex, 8 000 ta "#a" ketma-ket: 0.08
  telefon regex ^\+[1-9]\d{7,14}$, 16 000 raqam: 0.09
  KLASSIK ZAIF /^(a+)+$/ — "a"×20 + "!": 57.72 (har +1 belgi ~2×)
  KLASSIK ZAIF /^(a+)+$/ — "a"×24 + "!": 120.55 (har +1 belgi ~2×)
  KLASSIK ZAIF /^(a+)+$/ — "a"×26 + "!": 483.19 (har +1 belgi ~2×)

Millisekundlarda. Kanon regex'lari 16 000 belgida ham 1 ms dan kam — chiziqli. Klassik zaif /^(a+)+$/ esa 26 ta a da yarim soniya event loop'ni to'xtatadi. Raqamlarni diqqat bilan o'qing: 24 → 26 belgi — 4×, ya'ni har belgi ~2×. 20 → 24 esa atigi ~2× — bu o'lchov shovqini (birinchi chaqiruvlar), kanon izohidagi "2×" ni 24–26 oralig'i tasdiqlaydi. Himoya: uzunlikni regex'dan oldin cheklash (tana 16 KB, matn 500 belgi) va ichma-ich takrorlovchilardan qochish. Batafsil — DoS va resurs tugashidan himoya.

9. Qaysi qatlam nimani ushladi

Kanon hujum testining xulosasi — bitta jadvalda:

Hujum Payload Ushlagan qatlam Javob
SQL — qidiruv ' OR 1=1 --, UNION SELECT $1 parametr, LIKE ekranlash 200, 0 natija
SQL — saralash matn;DROP…, constructor ruxsat ro'yxati (Object.hasOwn) 400
SQL — teg x' OR '1'='1 teg regex'i 400
operator — tana {"$ne": null}, massiv Zod turi 422
operator — query bajarildi[$ne] Express 5 "simple" + qat'iy query 400
mass assignment egasiId, versiya, rol strictObject 422
prototype pollution __proto__, constructor.prototype JSON.parse + strictObject 422
log \r\n{"msg":…} emailda pino JSON 1 yozuv
sarlavha \r\n Node validateHeaderValue, X-Request-Id regex xato / yangi ID
katta tana 17 000 belgi express.json({ limit }) 413
chuqur JSON 2 500 daraja Zod 422
flowchart LR
  T[So'rov] --> E[express.json: 16 KB, JSON.parse]
  E --> Q[query: Express 5 simple]
  Q --> Z[Zod strictObject va turlar]
  Z --> R[ombor: parametr va ruxsat ro'yxati]
  R --> P[(Postgres)]
  Z -.-> L[pino: JSON log]

Diagramma — so'rov yo'li. Har hujum birinchi mos qatlamda to'xtadi, ko'pi — Zod'da. Bu chuqur himoya: Zod bo'lmasa ham SQL parametrlar ishlaydi, lekin 500 o'rniga aniq 422 beradigan joy — shu sxema.

10. Hujumchi nigohi

Hujumchi har kirish nuqtasiga bir xil savol beradi: "bu qiymat qayerga yetib boradi va u yerda kim uni o'qiydi?". Tana maydonlari — SQL, log, ehtimol shell. Query — SQL nomlari. Sarlavhalar — log va javob sarlavhalari. U bitta payload'ni hamma joyga yuboradi (', ", \r\n, {"$ne":null}, __proto__) va qaysi javob 500 yoki g'alati bo'lishini kutadi. Kanon testining asosiy talablaridan biri shu: hech qayerda 500 yo'q. 500 — hujumchi uchun "bu yerda parser qiynaldi, chuqurroq qazing" degan signal.

11. Ko'p uchraydigan xatolar

Xato Nega xavfli To'g'risi
SQL'ni shablon satr bilan yig'ish SQL injection $1 parametr
saralash nomini parametr qilishga urinish parametr faqat qiymat uchun ruxsat ro'yxati
tanani filtrga to'g'ridan-to'g'ri berish {"$ne": null} tur tekshiruvi
exec bilan foydalanuvchi qiymati command injection execFile + massiv
matnli log soxta qatorlar JSON log
sodda chuqur birlashtirish prototype pollution xavfli kalitlarni o'tkazish, strictObject
update({ data: req.body }) mass assignment aniq maydonlar
jim pick hujum ham, frontend xatosi ham ko'rinmaydi 422 bilan rad
regex'ni uzunliksiz ReDoS avval uzunlik

12. Mashqlar

Mashqlar kurs/mashqlar/28/27-injection/ da.

1-mashq (oson): qaysi qatlam?

Har hujumga vazifalar-api ning qaysi qatlami birinchi bo'lib javob beradi?

  1. POST /v1/vazifalar tanasida "egasiId": 2.
  2. GET /v1/vazifalar?sort=yangilandi;DELETE.
  3. POST /v1/auth/kirish tanasida "email": ["aziz@example.com"].
  4. 20 KB lik tana.
  5. Emailda \n{"msg":"admin kirdi"}.
Yechim
  1. Zod strictObject — 422, noma'lum kalit.
  2. Saralash ruxsat ro'yxati — 400.
  3. Zod turi — 422 (massiv satr emas).
  4. express.json({ limit: "16kb" }) — 413.
  5. Tana o'tishi ham mumkin, lekin log — pino JSON: yozuv bitta, hujumchi matni ekranlangan.

2-mashq (o'rta): xavfsiz chuqur birlashtirish

xavfsizBirlashtir(a, b) ni yozing: ichma-ich obyektlarni birlashtiradi, lekin __proto__, constructor, prototype kalitlarini o'tkazib yuboradi. Massivni obyekt deb birlashtirmang. a da kalit o'ziniki bo'lmasa (Object.hasOwn), yangi obyektdan boshlang. Sozlamalar obyekti va xavfli tanada sinang, keyin global prototip toza ekanini ko'rsating.

Yechim
js
const TAQIQ = new Set(["__proto__", "constructor", "prototype"]);

function xavfsizBirlashtir(a, b) {
  for (const k of Object.keys(b)) {
    if (TAQIQ.has(k)) continue;
    const q = b[k];
    if (typeof q === "object" && q !== null && !Array.isArray(q)) {
      const ichki = Object.hasOwn(a, k) ? a[k] : {};
      a[k] = xavfsizBirlashtir(ichki, q);
    } else {
      a[k] = q;
    }
  }
  return a;
}

const sozlama = { til: "uz", bildirish: { email: true } };
const tana = JSON.parse(
  '{"bildirish":{"sms":false},"__proto__":{"isAdmin":true},' +
    '"constructor":{"prototype":{"ifloslangan":1}}}',
);
const natija = xavfsizBirlashtir(sozlama, tana);
console.log(JSON.stringify(natija));
console.log(({}).isAdmin, ({}).ifloslangan);

Konsolda:

text
{"til":"uz","bildirish":{"email":true,"sms":false}}
undefined undefined

Ikki himoya birga ishlaydi. TAQIQ xavfli kalitlarni kesadi. Object.hasOwn esa a[k] prototipdan kelgan qiymat bo'lsa (masalan, a.toString), unga kirmaslikni ta'minlaydi. Bittasi yetarli tuyulishi mumkin, lekin chuqur himoya shu: biri xato qilsa, ikkinchisi ushlaydi.

3-mashq (qiyin): kichik qat'iy sxema

Zod'siz qatiyTana(sxema, tana) ni yozing. Sxema — { matn: "string", bajarildi: "boolean?" } (? — ixtiyoriy). Qoidalar: tana oddiy obyekt (massiv emas); sxemada yo'q kalit — xato; majburiy maydon yo'q — xato; tur mos emas — xato. Hamma xatolar ; bilan bitta satrda, { xato } qaytsin. Hammasi to'g'ri bo'lsa — { qiymat }. Kalit tekshiruvi — Object.hasOwn bilan (in emas). Beshta tanada sinang.

Yechim
js
const SXEMA = { matn: "string", bajarildi: "boolean?" };

function qatiyTana(sxema, tana) {
  const obyekt = typeof tana === "object" && tana !== null;
  if (!obyekt || Array.isArray(tana)) {
    return { xato: "tana obyekt bo'lishi kerak" };
  }
  const xatolar = [];
  for (const k of Object.keys(tana)) {
    if (!Object.hasOwn(sxema, k)) {
      xatolar.push(`noma'lum kalit: ${k}`);
    }
  }
  for (const [k, tur] of Object.entries(sxema)) {
    const ixtiyoriy = tur.endsWith("?");
    const kerak = ixtiyoriy ? tur.slice(0, -1) : tur;
    if (!Object.hasOwn(tana, k)) {
      if (!ixtiyoriy) xatolar.push(`${k}: majburiy`);
    } else if (typeof tana[k] !== kerak) {
      const keldi = typeof tana[k];
      xatolar.push(`${k}: ${kerak} kutilgan, ${keldi} keldi`);
    }
  }
  if (xatolar.length > 0) return { xato: xatolar.join("; ") };
  return { qiymat: { ...tana } };
}

const SINOV = [
  '{"matn":"Non olish"}',
  '{"matn":"Non","egasiId":2,"rol":"admin"}',
  '{"matn":{"$ne":null}}',
  '{"matn":"x","__proto__":{"isAdmin":true}}',
  '[1,2]',
];
for (const s of SINOV) {
  console.log(JSON.stringify(qatiyTana(SXEMA, JSON.parse(s))));
}

Konsolda:

text
{"qiymat":{"matn":"Non olish"}}
{"xato":"noma'lum kalit: egasiId; noma'lum kalit: rol"}
{"xato":"matn: string kutilgan, object keldi"}
{"xato":"noma'lum kalit: __proto__"}
{"xato":"tana obyekt bo'lishi kerak"}

Bitta funksiya to'rt hujumni ushladi: mass assignment (2-qator), operator obyekti (3-qator), prototype pollution (4-qator) va noto'g'ri shakl (5-qator). Nega in emas? "toString" in tana — true (prototipdan), Object.hasOwn(tana, "toString") — false. in bilan sxema tekshiruvi prototip kalitlarini "bor" deb o'ylab qolardi. Bu — strictObject ning soddalashtirilgan nusxasi; haqiqiy loyihada Zod qoladi.

13. Loyiha qadami: hujum ssenariylari testi

13.1 test/hujum.test.ts

Kod o'zgarmaydi — faqat test. Yetti test haqiqiy Postgres bilan ishlaydi: har biridan oldin baza tozalanadi, Aziz va Dilnoza hisob ochadi, Dilnoza bitta "maxfiy" vazifa yaratadi. Hujumlar Aziz nomidan — begona vazifa hech qayerda chiqmasligi kerak.

Ikkitasi (telefon uchun bo'lingan; bo'lingan nusxa tip, lint va testlardan o'tdi — biz tekshirdik):

ts
  test("mass assignment: vazifaga egasiId/id/versiya/yaratildi — " +
    "422 (strictObject — 25-qismdan beri oq ro'yxat), maydonlar " +
    "ro'yxati javobda; auth yo'llarida rol — 422", async () => {
    const r = await request(app).post("/v1/vazifalar")
      .set("Cookie", aziz)
      .send({
        matn: "Meniki", egasiId: 2, egasi_id: 2, id: 999, versiya: 77,
        yaratildi: "2000-01-01T00:00:00.000Z",
      })
      .expect(422);
    assert.match(JSON.stringify(r.body), /egasiId/);
    for (const [y, b] of [
      ["patch", { bajarildi: true, egasiId: 2 }],
      ["put", { matn: "x", bajarildi: false, versiya: 9 }],
    ] as const) {
      const v = (await request(app).post("/v1/vazifalar")
        .set("Cookie", aziz).send({ matn: `Asl ${y}` }).expect(201))
        .body as { id: number };
      await request(app)[y](`/v1/vazifalar/${String(v.id)}`)
        .set("Cookie", aziz).send(b).expect(422);
    }
    const { rows } = await pool.query<{ n: number }>(
      "SELECT count(*)::int AS n FROM vazifalar WHERE egasi_id = 2");
    assert.equal(rows[0]?.n, 1); // Dilnozada faqat o'zining vazifasi
    await request(app).post("/v1/auth/royxat").send({
      email: "x@example.com", toliqIsm: "X", parol: PAROL,
      rol: "admin",
    }).expect(422);
    await request(app).post("/v1/auth/royxat").send({
      email: "y@example.com", toliqIsm: "Y", parol: PAROL,
      emailTasdiqlangan: true,
    }).expect(422);
    await request(app).post("/v1/auth/kirish").send({
      email: "aziz@example.com", parol: PAROL, rol: "admin",
    }).expect(422);
  });

  test("prototype pollution: __proto__ / constructor.prototype " +
    "tanada — global Object.prototype ifloslanmaydi, javob oddiy",
  async () => {
    const tana = '{"matn":"Proto sinov",' +
      '"__proto__":{"admin":true,"isAdmin":true},' +
      '"constructor":{"prototype":{"ifloslangan":true}}}';
    // JSON.parse "__proto__" ni oddiy (own) kalit qiladi —
    // strictObject uni noma'lum maydon deb rad etadi;
    // Object.assign/lodash.merge bo'lganda xavfli bo'lardi
    await request(app).post("/v1/vazifalar").set("Cookie", aziz)
      .set("Content-Type", "application/json").send(tana).expect(422);
    const bosh = {} as Record<string, unknown>;
    assert.equal(bosh["isAdmin"], undefined);
    assert.equal(bosh["ifloslangan"], undefined);
    await request(app).post("/v1/auth/kirish")
      .set("Content-Type", "application/json")
      .send('{"__proto__":{"email":"aziz@example.com"},"parol":"x"}')
      .expect(422);
  });

Birinchi testning eng muhim qatori — bazaga to'g'ridan-to'g'ri so'rov: "Dilnozada faqat bitta vazifa". Javob kodi 422 bo'lishi yetarli emas — hujum hech qanday iz qoldirmaganini baza tasdiqlashi kerak. Kanonda bosh o'rniga ({} as Record<string, unknown>) to'g'ridan-to'g'ri yozilgan — o'qish uchun nom berdik.

Qolgan beshta test: SQL injection (login, qidiruv, saralash, teg), operator obyektlari, log injection (har log qatori JSON.parse dan o'tadi), sarlavha injection (setHeader → ERR_INVALID_CHAR) va katta/chuqur JSON. Ularning har biri yuqoridagi jadvalning bir qatori.

13.2 Ishga tushirish

Faqat shu faylni ishga tushiramiz (biz ishga tushirdik: kanon nusxasi va bo'lingan testlar, Node 24.21.0, lokal PostgreSQL 17.6). Chiqishda har test nomi bitta hujum turini aytadi — ✔ belgisiga va oxirgi ℹ pass / ℹ fail qatorlariga qarang:

bash
node --test test/hujum.test.ts
text
✔ SQL injection: login email, qidiruv, saralash, teg — parametrlar/oq ro'yxat; begona ma'lumot chiqmaydi (707.6463ms)
✔ operator obyektlari (NoSQL uslubidagi) va noto'g'ri turlar — 422/400, hech qayerda 500 yo'q (503.9642ms)
✔ mass assignment: vazifaga egasiId/id/versiya/yaratildi — 422 (strictObject — 25-qismdan beri oq ro'yxat), maydonlar ro'yxati javobda; auth yo'llarida rol — 422 (578.8936ms)
✔ prototype pollution: __proto__ / constructor.prototype tanada — global Object.prototype ifloslanmaydi, javob oddiy (419.8458ms)
✔ log injection: CRLF va soxta JSON qator emailda — pino har yozuvni bitta JSON qator qilib ekranlaydi (578.5533ms)
✔ sarlavha (CRLF) injection: Node javob sarlavhasiga \r\n qo'ymaydi; X-Request-Id yaroqsiz — yangisi (581.8077ms)
✔ katta va chuqur JSON: 16 KB dan katta — 413; 5 000 darajali chuqur obyekt (16 KB ichida) — 422, server yiqilmaydi (533.6167ms)
ℹ tests 7
ℹ suites 0
ℹ pass 7
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6874.4833

Nima bo'ldi? Yetti testning hammasi o'tdi — yuqoridagi jadvaldagi har qatlam o'z hujumini ushladi. Ikki kichik izoh:

  • Oxirgi qatordagi "5 000 darajali" — «Katta va chuqur JSON» bo'limidagi nomuvofiqlik: kodda 2 500.
  • Kanon test nomlarida eski atama 'oq ro'yxat' qolgan — bu kursdagi 'ruxsat ro'yxati (allowlist)' bilan bir narsa.

13.3 Commit

Kanon to'liq tekshiruvi keyingi dars (SSRF) bilan birga o'tkazilgan: testlar 227 (226 + 1 o'tkazilgan), shundan hujum testlari 7 ta; tip va lint xatosiz. Siz bu testni alohida commit qiling — u kodni o'zgartirmaydi:

bash
M="test: injection va mass assignment hujum ssenariylari"
M+=" (SQL, operator obyektlari, prototype pollution, log/CRLF,"
M+=" chuqur JSON)"
git add test/hujum.test.ts
git commit -m "$M"

14. Real ishda

  • Pentest hisobotlarida injection hamon eng og'ir toifalardan. Ko'pincha u yangi kodda emas, "tez yozilgan" admin skriptida, eksport funksiyasida yoki hisobot so'rovida topiladi — u yerda ham parametr va ruxsat ro'yxati.
  • Ommaviy birlashtirish kutubxonalari (lodash.merge va boshqalar) tarixida prototype pollution zaifliklari ko'p bo'lgan va yangi versiyalarda tuzatilgan — npm audit ni muntazam ishga tushiring.
  • Intervyuda: "SQL injection'dan qanday himoyalanasiz va ORDER BY'da nima qilasiz?", "NoSQL injection nima?", "mass assignment va uning yechimi?", "JSON.parse prototype pollution beradimi?".

Xulosa

  • Injection — ma'lumot buyruq sifatida talqin qilinishi; himoya — alohida kanal (parametr, argument massivi, JSON) va ruxsat ro'yxati.
  • SQL: qiymat — $1, nom — ruxsat ro'yxati; $queryRawUnsafe — o'zingiz javobgarsiz.
  • Operator injection: tur tekshiruvi; Express 5 sukutda query'dan obyekt yasamaydi.
  • Command: execFile + argumentlar massivi; log: JSON; sarlavha: Node \r\n ni rad etadi.
  • JSON.parse xavfsiz — xavf undan keyingi Object.assign va chuqur birlashtirishda.
  • Mass assignment: aniq maydonlar, noma'lumga 422.
  • vazifalar-api da yangi zaiflik topilmadi — 25–27-qismdagi qoidalar ishladi; endi buni yetti test qotiradi.

Keyingi dars: SSRF va tashqi URL'lar bilan ishlash — server o'zi so'rov yuboradigan joy: ichki manzillar, bulut metadata, DNS rebinding va kanonning xavfsizPost funksiyasi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Backend'dagi injection va mass assignment: SQL, operator obyektlari, prototype pollution, log va sarlavha — IlmHamroh