Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 1.1 Umumiy formula
- 1.2 Kanon nima qildi
- 2. SQL injection: takror va tekshiruv
- 3. Operator injection: {"$ne": null}
- 3.1 Hujum
- 3.2 vazifalar-api da
- 4. Command injection
- 5. Log va sarlavha injection
- 5.1 Log: soxta qator
- 5.2 Sarlavha: CRLF
- 6. Prototype pollution va JSON.parse
- 6.1 JSON.parse — xavfsiz, keyingisi — yo'q
- 6.2 vazifalar-api nega xavfsiz
- 6.3 Katta va chuqur JSON
- 7. Mass assignment
- 7.1 Muammo
- 7.2 vazifalar-api da
- 8. ReDoS — qisqa
- 9. Qaysi qatlam nimani ushladi
- 10. Hujumchi nigohi
- 11. Ko'p uchraydigan xatolar
- 12. Mashqlar
- 1-mashq (oson): qaysi qatlam?
- 2-mashq (o'rta): xavfsiz chuqur birlashtirish
- 3-mashq (qiyin): kichik qat'iy sxema
- 13. Loyiha qadami: hujum ssenariylari testi
- 13.1 test/hujum.test.ts
- 13.2 Ishga tushirish
- 13.3 Commit
- 14. Real ishda
- Xulosa
- Manbalar
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. Kanonvazifalar-apiga 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.parseqachon xavfsiz ekanini ko'rasiz. - Mass assignment'ni ruxsat ro'yxati va qat'iy sxema bilan yopasiz.
vazifalar-apiga 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:
curl "$A/v1/vazifalar?q=%27%20OR%201%3D1%20--&fields=id" \
-H "Cookie: $C"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:
curl "$A/v1/vazifalar?sort=matn;DROP%20TABLE%20vazifalar" \
-H "Cookie: $C"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:
// 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:
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):
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:
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:
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:
["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:
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:
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:
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:
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:
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:
["matn","__proto__"] undefined
true undefined
true
undefinedUch bosqich:
JSON.parse—__proto__oddiy kalit, hech narsa ifloslanmadi.Object.assign—nusxaning o'z prototipi almashdi (nusxa.isAdmin—true), global hali toza.- Sodda chuqur birlashtirish —
a["__proto__"]buObject.prototypening o'zi. Endi har obyektdaisAdmin: 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):
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.parseishlatamiz, 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
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:
admin
422: noma'lum kalit — rolSardor 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'lsa422. 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):
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):
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?
POST /v1/vazifalartanasida"egasiId": 2.GET /v1/vazifalar?sort=yangilandi;DELETE.POST /v1/auth/kirishtanasida"email": ["aziz@example.com"].- 20 KB lik tana.
- Emailda
\n{"msg":"admin kirdi"}.
Yechim
- Zod
strictObject—422, noma'lum kalit. - Saralash ruxsat ro'yxati —
400. - Zod turi —
422(massiv satr emas). express.json({ limit: "16kb" })—413.- 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
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:
{"til":"uz","bildirish":{"email":true,"sms":false}}
undefined undefinedIkki 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
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:
{"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):
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:
node --test test/hujum.test.ts✔ 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.4833Nima 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:
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.mergeva boshqalar) tarixida prototype pollution zaifliklari ko'p bo'lgan va yangi versiyalarda tuzatilgan —npm auditni muntazam ishga tushiring. - Intervyuda: "SQL injection'dan qanday himoyalanasiz va ORDER BY'da nima qilasiz?", "NoSQL injection nima?", "mass assignment va uning yechimi?", "
JSON.parseprototype 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\nni rad etadi. JSON.parsexavfsiz — xavf undan keyingiObject.assignva chuqur birlashtirishda.- Mass assignment: aniq maydonlar, noma'lumga
422. vazifalar-apida 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
- OWASP Top 10:2025 — A05 Injection: https://top10.owasp.org/2025
- OWASP API Security Top 10:2023 — API3 Broken Object Property Level Authorization: https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- OWASP Mass Assignment Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
- OWASP Injection Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html
- OWASP Prototype Pollution Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html
- Express 5 ga ko'chish (query parser sukuti "extended" → "simple"): https://expressjs.com/en/guide/migrating-5.html (2026-10-28 da o'qildi)
- Node.js 24
http.validateHeaderValue: https://nodejs.org/docs/latest-v24.x/api/http.html#httpvalidateheadervaluename-value - Prisma: xom so'rovlar va SQL injection: https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries
- Kanon (IlmHamroh, 2026-10-28):
test/hujum.test.ts(holat 28), DoS tajribasi (ReDoS o'lchovi). Darsdagi brauzer va Node misollari, jonlicurlvahujum.test.ts(Node 24.21.0, PostgreSQL 17.6) — biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!