Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. Muammo: umumiy prototip
- 2.1 Prototype pollution nima?
- 2.2 Nega bu xavfli: mantiq jimgina buziladi
- 2.3 Ikki "eshik": __proto__ va constructor.prototype
- 3. Qanday paydo bo'ladi
- 3.1 JSON.parse o'zi xavfsiz
- 3.2 O'z birlashtir funksiyamiz
- 3.3 Yo'l bo'yicha qiymat qo'yish
- 4. Himoya
- 4.1 Xavfli kalitlarni rad etish
- 4.2 O'qishda ham Object.hasOwn
- 4.3 Object.create(null): eshiksiz lug'at
- 4.4 Map: kalit faqat kalit
- 4.5 Nusxalash usullari bir xil emas
- 4.6 Object.freeze(Object.prototype): kuchli, lekin ehtiyotkorlik bilan
- 4.7 Kirishni oq ro'yxat bilan olish
- 5. Hujumchi nigohi
- 6. Ko'p uchraydigan xatolar
- 6.1 Faqat "__proto__" ni rad etish
- 6.2 Tashqi obyektni for...in bilan aylanish
- 6.3 "Bormi?" ni in yoki !== undefined bilan tekshirish
- 6.4 Object.assign ni xavfsiz nusxa deb o'ylash
- 6.5 Object.freeze(Object.prototype) ni oddiy skriptda sinash
- 7. Mashqlar
- 1-mashq (oson): Ifloslanadimi?
- 2-mashq (o'rta): qiymatQoy ni tuzating
- 3-mashq (qiyin): Teglar sanog'i
- 4-mashq: Vazifalar qadami — xavfsiz import (mashqlar repo)
- 8. Real ishda
- Xulosa
- Manbalar
JavaScript prototype pollution: __proto__ xavfi va himoyasi
Qisqacha: Prototype pollution — tashqaridan kelgan kalit (
"__proto__","constructor") tufayli umumiyObject.prototypega begona xususiyat tushishi. Shundan keyin dasturdagi hamma obyektda kutilmagan xususiyat paydo bo'ladi:({}).admin—true. Himoya: xavfli kalitlarni rad etish,Object.hasOwnbilan tekshirish, tashqi kalitlar uchunObject.create(null)yokiMap, kirish ma'lumotini oq ro'yxat bilan olish.
Bu darsda
- Prototype pollution nima ekanini va nega bitta kalit butun dasturni buzishini tushuntira olasiz.
- O'zingiz yozgan "chuqur birlashtirish" va "yo'l bo'yicha qiymat qo'yish" funksiyalarida zaif joyni topa olasiz.
JSON.parse, spread,structuredClonevaObject.assign"__proto__"kalitini qanday qabul qilishini bilasiz.- Xavfsiz
xavfsizBirlashtirfunksiyasini yozasiz: xavfli kalitlar rad etiladi, faqat o'z kalitlar olinadi. Object.freeze(Object.prototype)ning foydasi va narxini ajrata olasiz.
Oldin bilishingiz kerak: Prototip va prototip zanjiri, Object.create va instanceof, O'rnatilgan prototiplar, JSON asoslari, Obyektni himoyalash.
1. Nega bu kerak?
O'rnatilgan prototiplar darsida bitta qoidani aytgan edik: Object.prototype ga tekkan narsa butun dasturga tegadi. U yerda prototipni biz o'zimiz o'zgartirgan edik. Bugun savol boshqa: buni foydalanuvchi qila oladimi?
Avval natijaga qarang. Hech qaysi obyektda admin xususiyati yo'q edi:
const mehmon = { ism: "Bobur" };
console.log(mehmon.admin); // undefined
Object.prototype.admin = true; // ❌ umumiy prototip ifloslandi
console.log(mehmon.admin); // true
const yangiMehmon = { ism: "Jasur" };
console.log(yangiMehmon.admin); // true
console.log({}.admin); // trueUchinchi qator — bitta tayinlash. Undan keyin eski obyekt ham, yangi obyekt ham, hatto bo'sh {} ham "admin" bo'lib qoldi. Ularning hech biriga tegmadik.
Hayotdan o'xshatish. Choyxonada osh katta qozonda pishadi, har likopcha shu qozondan suziladi. Kimdir qozonga bir hovuch tuz tashlasa, hamma likopcha sho'r bo'ladi — oshpaz likopchalarga tegmagan bo'lsa ham. Object.prototype — o'sha umumiy qozon. Har oddiy obyekt undan "suziladi".
Real dasturda Object.prototype.admin = true ni hech kim ataylab yozmaydi. Xavf boshqa joyda: sizning o'z foydali funksiyangiz tashqaridan kelgan JSON'ni qayta ishlayotganda xuddi shu ishni qilib qo'yishi mumkin. Bugun shu yo'lni Node'da xavfsiz tajriba bilan ko'ramiz va uni yopamiz.
Diqqat: Bu dars — himoya darsi. Biz faqat o'z kichik funksiyalarimizni o'z kompyuterimizda sinaymiz. Natija — bitta
console.log. Begona sayt yoki dasturga bunday ma'lumot yuborish — ruxsatsiz kirishga urinish va qonunga zid.
2. Muammo: umumiy prototip
2.1 Prototype pollution nima?
Prototype pollution ("prototipni ifloslash") — dastur tashqi ma'lumotni qayta ishlayotganda Object.prototype (yoki boshqa umumiy prototip) ga begona xususiyat yozib qo'yishi. Kursda atama inglizchaligicha qoladi: MDN, OWASP va xavfsizlik hisobotlarida shunday yoziladi.
Nega bu ishlaydi — Prototip zanjiri darsidan bilasiz. Obyektda xususiyat topilmasa, qidiruv prototipga, undan Object.prototype ga o'tadi. mehmon.admin o'zida yo'q edi, qidiruv yuqoriga chiqdi va u yerda true ni topdi.
2.2 Nega bu xavfli: mantiq jimgina buziladi
Ifloslangan xususiyat xato chiqarmaydi. U shunchaki "bor" bo'lib qoladi va dasturdagi tekshiruvlar noto'g'ri javob bera boshlaydi. «Bahor» kassasidagi chegirma hisobini olaylik:
function narxniHisobla(buyurtma) {
const chegirma = buyurtma.chegirma ?? 0;
return buyurtma.narx - (buyurtma.narx * chegirma) / 100;
}
const osh = { taom: "osh", narx: 35000 };
console.log(narxniHisobla(osh)); // 35000
Object.prototype.chegirma = 100; // ❌ ifloslandi
console.log(narxniHisobla(osh)); // 0
for (const kalit in osh) {
console.log(kalit);
}
console.log(osh); // { taom: 'osh', narx: 35000 }Konsolda:
35000
0
taom
narx
chegirma
{ taom: 'osh', narx: 35000 }Funksiya o'zgarmadi, osh obyekti ham o'zgarmadi. Lekin buyurtma.chegirma endi undefined emas — 100. ?? 0 ishlamadi va osh bepul bo'lib qoldi. for...in ham meros kalitni ko'rsatdi (for...of va for...in).
Eng yomoni — oxirgi qator. console.log(osh) faqat o'z xususiyatlarni ko'rsatadi, unda chegirma yo'q. Xatoni qidirayotgan dasturchi obyektga qaraydi va hech narsa topmaydi.
Shu tarzda nimalar buzilishi mumkin:
| Tekshiruv | Ifloslangandan keyin |
|---|---|
if (foydalanuvchi.admin) |
har kim admin |
sozlama.chegirma ?? 0 |
hammaga chegirma |
if (!buyurtma.tasdiqlandi) |
tekshiruv o'tkazib yuboriladi |
kalit in lug'at |
yo'q kalit "bor" deyiladi |
Serverda bunday xato yanada og'ir: Node'dagi bitta jarayon minglab foydalanuvchiga xizmat qiladi. Ifloslangan prototip ularning hammasiga ta'sir qiladi — to server qayta ishga tushguncha.
Tekshirib ko'ring:
Object.prototype.chegirma = 100dan keyin[35000, 5000].chegirmanima qaytaradi?
Javob
100. Massivning zanjiri Array.prototype → Object.prototype (O'rnatilgan prototiplar). Massivda ham, funksiyada ham qidiruv oxirida Object.prototype ga yetadi. Shuning uchun aynan shu prototip eng xavfli nishon.
2.3 Ikki "eshik": __proto__ va constructor.prototype
Foydalanuvchi Object.prototype deb yoza olmaydi — u faqat ma'lumot yuboradi: kalitlar va qiymatlar. Lekin ikki kalit nomi oddiy obyektdan to'g'ri Object.prototype ga olib boradi:
const bosh = {};
console.log(bosh["__proto__"] === Object.prototype); // true
console.log(bosh["constructor"] === Object); // true
const proto = bosh["constructor"]["prototype"];
console.log(proto === Object.prototype); // trueBirinchi yo'l Prototip zanjiri darsidan tanish: __proto__ — Object.prototype dagi eski "eshik", u obyektning prototipini qaytaradi. Ikkinchi yo'l Konstruktor funksiya darsidan: har obyekt constructor ni meros oladi, u Object funksiyasi, uning prototype xususiyati esa — o'sha Object.prototype.
Diagrammada ikkala yo'l ham bitta joyga boradi:
flowchart LR
B["{} — oddiy obyekt"] -->|"["__proto__"]"| OP["Object.prototype<br/>umumiy qozon"]
B -->|"["constructor"]"| O["Object funksiyasi"]
O -->|"["prototype"]"| OP
OP --> H["hamma obyektlar<br/>meros oladi"]Demak xavfli kalitlar uchta: "__proto__", "constructor" va "prototype". Ular satr sifatida JSON ichida, URL parametrida yoki forma maydoni nomida kelishi mumkin. Agar kod bunday kalitni tekshirmasdan ishlatib, zanjir bo'ylab yozsa — ifloslanish tayyor.
Tekshirib ko'ring:
[]["constructor"]["prototype"]qaysi prototip?
Javob
Array.prototype. Massivning constructor i — Array funksiyasi. Bu yo'l bilan massivlarning umumiy prototipini ham ifloslash mumkin. Shuning uchun himoyada "faqat __proto__" emas, uchala kalit ham rad etiladi.
3. Qanday paydo bo'ladi
3.1 JSON.parse o'zi xavfsiz
Tashqi ma'lumot ko'pincha JSON matn bo'lib keladi (JSON asoslari). Avval tinch yangilik: JSON.parse "__proto__" kalitini oddiy xususiyat qiladi:
const matn = '{"ism": "Ali", "__proto__": {"admin": true}}';
const kelgan = JSON.parse(matn);
console.log(kelgan);
console.log(Object.keys(kelgan));
console.log(kelgan.admin); // undefined
console.log({}.admin); // undefinedKonsolda:
{ ism: 'Ali', ['__proto__']: { admin: true } }
[ 'ism', '__proto__' ]
undefined
undefinedNode bunday kalitni ['__proto__'] deb, qavs ichida ko'rsatadi. Ma'nosi: "bu prototip emas, shunchaki shu nomli o'z kalit". kelgan ning prototipi o'zgarmadi, Object.prototype ham toza.
Obyekt literali esa boshqacha ishlaydi. Prototip zanjiri darsida ko'rganimizdek, literal ichidagi __proto__: prototipni o'rnatadi:
const literal = { "__proto__": { admin: true } };
console.log(literal.admin); // true
console.log(Object.keys(literal)); // []Bir xil ko'rinadigan yozuv — ikki xil natija. Lekin bu yerda xavf yo'q: literalni dasturchi yozadi, foydalanuvchi emas. Xavf keyingi qadamda: JSON'dan olingan obyekt kalitlarini boshqa obyektga ko'chirganda.
3.2 O'z birlashtir funksiyamiz
«Bahor» Telegram botida mehmon o'z sozlamalarini yuboradi: til, tema rangi, shrift. Server ularni standart sozlamalar ustiga "chuqur" qo'shadi. Chuqur birlashtirish (deep merge) — ikki obyektni ichma-ich darajalari bilan birga qo'shish: ichki obyektlar almashtirilmaydi, ular ham birlashtiriladi.
Ko'p dasturchi birinchi marta shunday yozadi. Rekursiya yordamida ichki obyektlarga tushamiz:
function birlashtir(nishon, manba) {
for (const kalit in manba) {
const qiymat = manba[kalit];
if (typeof qiymat === "object" && qiymat !== null) {
if (!(kalit in nishon)) {
nishon[kalit] = {};
}
birlashtir(nishon[kalit], qiymat);
} else {
nishon[kalit] = qiymat;
}
}
return nishon;
}
const sozlama = { til: "uz", tema: { rang: "yashil" } };
const kelgan = JSON.parse('{"tema": {"shrift": "katta"}}');
console.log(birlashtir(sozlama, kelgan));
const zararli = JSON.parse('{"__proto__": {"admin": true}}');
birlashtir({}, zararli);
console.log({}.admin); // trueKonsolda:
{ til: 'uz', tema: { rang: 'yashil', shrift: 'katta' } }
trueOddiy ma'lumot bilan funksiya a'lo ishladi: rang saqlandi, shrift qo'shildi. Ikkinchi chaqiruv esa butun dasturdagi bo'sh obyektni "admin" qildi. Funksiyada bitta ham Object.prototype so'zi yo'q-ku — qanday bo'ldi? Keyingi tugmasi bilan qadamma-qadam kuzating:
Ikki zaif joyga e'tibor bering. Birinchisi — kalit in nishon: in zanjir bo'ylab qaraydi va "__proto__" ni "bor" deydi. Ikkinchisi — nishon[kalit] ni o'qish: u ham zanjir bo'ylab ketib, Object.prototype ning o'zini qaytaradi. Funksiya o'zi bilmagan holda umumiy qozonga qoshiq tiqdi.
3.3 Yo'l bo'yicha qiymat qo'yish
Ikkinchi xavfli naqsh — yo'l bo'yicha qiymat qo'yish (deep set): "tema.rang" kabi nuqtali yo'lni qismlarga bo'lib, ichma-ich obyektga yozish. Yo'l ko'pincha forma maydoni nomidan yoki URL parametridan keladi: ?tema.rang=yashil.
function qiymatQoy(obyekt, yol, qiymat) {
const qismlar = yol.split(".");
let joriy = obyekt;
for (const qism of qismlar.slice(0, -1)) {
if (!(qism in joriy)) {
joriy[qism] = {};
}
joriy = joriy[qism];
}
joriy[qismlar.at(-1)] = qiymat;
}
const sozlama = {};
qiymatQoy(sozlama, "tema.rang", "yashil");
console.log(sozlama); // { tema: { rang: 'yashil' } }
qiymatQoy({}, "constructor.prototype.chegirma", 100);
console.log({}.chegirma); // 100Bu safar ikkinchi "eshik" ishladi. joriy qadamma-qadam {} → Object funksiyasi → Object.prototype bo'ldi va oxirgi qadam chegirma ni o'sha yerga yozdi. Endi «Bahor»dagi har buyurtma bepul.
Ikkala funksiyaning umumiy kasalligi bitta: tashqaridan kelgan kalit tekshirilmay, zanjir bo'ylab o'qish va yozish uchun ishlatildi. Shu jumlani eslab qoling — himoyaning hammasi undan kelib chiqadi.
Tekshirib ko'ring:
birlashtir({}, JSON.parse('{"constructor": {"prototype": {"x": 1}}}'))dan keyin{}.xnimaga teng?
Javob
1. "constructor" in nishon — true (meros), rekursiya Object funksiyasiga tushadi. U yerda "prototype" in Object ham true, rekursiya Object.prototype ga tushadi va x ni yozadi. birlashtir ikkala eshikka ham ochiq.
4. Himoya
Himoya bitta usul emas, bir necha qatlam. Har biri yuqoridagi "kasallik"ning bir qismini davolaydi.
4.1 Xavfli kalitlarni rad etish
Eng to'g'ridan-to'g'ri chora: uchta xavfli nomni umuman qabul qilmaslik. Kalitlarni esa for...in emas, Object.keys bilan olamiz — u faqat o'z kalitlarni beradi (Obyekt bo'ylab yurish):
const XAVFLI_KALITLAR = ["__proto__", "constructor", "prototype"];
function oddiyObyektmi(qiymat) {
return typeof qiymat === "object" && qiymat !== null
&& !Array.isArray(qiymat);
}
function xavfsizBirlashtir(nishon, manba) {
for (const kalit of Object.keys(manba)) {
if (XAVFLI_KALITLAR.includes(kalit)) {
continue; // xavfli kalit — o'tkazib yuboramiz
}
const qiymat = manba[kalit];
if (oddiyObyektmi(qiymat)) {
const ichkisiBormi = Object.hasOwn(nishon, kalit)
&& oddiyObyektmi(nishon[kalit]);
if (!ichkisiBormi) {
nishon[kalit] = {};
}
xavfsizBirlashtir(nishon[kalit], qiymat);
} else {
nishon[kalit] = qiymat;
}
}
return nishon;
}
const sozlama = { til: "uz", tema: { rang: "yashil" } };
const kelgan = JSON.parse('{"tema": {"shrift": "katta"}}');
console.log(xavfsizBirlashtir(sozlama, kelgan));
const zararli1 = JSON.parse('{"__proto__": {"admin": true}}');
const zararli2 = JSON.parse(
'{"constructor": {"prototype": {"admin": true}}}',
);
console.log(xavfsizBirlashtir({}, zararli1)); // {}
console.log(xavfsizBirlashtir({}, zararli2)); // {}
console.log({}.admin); // undefinedKonsolda:
{ til: 'uz', tema: { rang: 'yashil', shrift: 'katta' } }
{}
{}
undefinedOddiy ma'lumot avvalgidek birlashdi, ikkala zararli kalit esa tashlab yuborildi. Uch o'zgarishni sanab chiqamiz:
Object.keys— meros kalitlar sikliga tushmaydi.XAVFLI_KALITLAR.includes(kalit)— uchta nom rad etiladi,continuekeyingi kalitga o'tadi (breakvacontinue).Object.hasOwn(nishon, kalit)— ichki obyektga faqat u nishonning o'zida bo'lsa tushamiz. Aks holda yangi bo'sh{}yaratamiz.
oddiyObyektmi — predikat ("-mi" bilan tugaydi): massivlar va null ni chetlab o'tadi (typeof va turni aniqlash). Massiv kelsa, u butunligicha almashtiriladi.
Maslahat: Ikkinchi va uchinchi o'zgarish bir-birini sug'urtalaydi. Faqat
hasOwnbo'lsa —"__proto__"kalitinishon["__proto__"] = {}qatoriga yetib, shu bitta obyektning prototipini almashtirardi. Faqat rad ro'yxati bo'lsa — kelajakda yangi "eshik" topilsa, himoya teshik qoladi. Xavfsizlikda bitta qulfga ishonilmaydi.
Tekshirib ko'ring: Nega
xavfsizBirlashtirkalitlarnifor...inbilan emas,Object.keysbilan oladi?JSON.parsedan kelgan obyektda meros kalit baribir yo'q-ku.
Javob
Kelgan obyektda yo'q — lekin Object.prototype da bo'lishi mumkin. Agar boshqa biror kutubxona prototipni allaqachon ifloslagan bo'lsa, for...in o'sha begona kalitni ham beradi va funksiya uni yangi obyektga ko'chiradi. Object.keys esa faqat obyektning o'z kalitlarini beradi — ifloslik tarqalmaydi.
4.2 O'qishda ham Object.hasOwn
Yozishni himoyaladik. Lekin dastur kattami, unda o'nlab kutubxona bor. Ulardan birida zaif joy qolgan bo'lsa-chi? Muhim tekshiruvlar meros qiymatga ishonmasligi kerak:
Object.prototype.admin = true; // tasavvur qiling: kimdir ifloslabdi
const mehmon = { ism: "Bobur" };
console.log(mehmon.admin === true); // true
function adminmi(foydalanuvchi) {
return Object.hasOwn(foydalanuvchi, "admin")
&& foydalanuvchi.admin === true;
}
console.log(adminmi(mehmon)); // false
console.log(adminmi({ ism: "Jasur", admin: true })); // trueadminmi faqat obyektning o'z xususiyatiga qaraydi. Prototip ifloslangan bo'lsa ham, Bobur admin bo'lib qolmaydi. Bu odat Prototip zanjiri darsidagi filtrniTanla dagi bilan bir xil: tashqi kalitni tekshirish — faqat Object.hasOwn.
4.3 Object.create(null): eshiksiz lug'at
Object.create darsida prototipsiz "toza lug'at"ni ko'rgan edik. Unda __proto__ eshigi ham, constructor ham yo'q — bu nomlar oddiy kalitga aylanadi:
const lugat = Object.create(null);
lugat["__proto__"] = { admin: true };
lugat["constructor"] = "oshpaz";
console.log(lugat);
console.log(lugat.admin); // undefined
console.log({}.admin); // undefinedKonsolda:
[Object: null prototype] {
['__proto__']: { admin: true },
constructor: 'oshpaz'
}
undefined
undefinedIkkala kalit oddiy ma'lumot bo'lib saqlandi. Kalitlari tashqaridan keladigan joyda — teglar, so'z sanog'i, forma maydonlari — Object.create(null) tabiiy tanlov.
Bitta nozik joy bor: toza lug'at faqat o'zi toza. Uning ichiga {} bilan yangi obyekt qo'ysangiz, o'sha ichki obyektning prototipi yana Object.prototype. Ichma-ich tuzilmada har daraja Object.create(null) bilan yaratilishi kerak — buni 2-mashqda qilasiz.
4.4 Map: kalit faqat kalit
Yana bir to'plam bor — Map. U kalit-qiymat juftliklarini saqlaydi, lekin kalitlari hech qachon prototip bilan aralashmaydi. U yerda "__proto__" ham boshqa satrlar kabi oddiy kalit. Bu to'plamni Map darsida to'liq o'rganamiz va u yerda shu hujumni qaytadan sinaymiz — hozir nomini eslab qolsangiz yetarli.
4.5 Nusxalash usullari bir xil emas
Obyektni nusxalashning bir necha usulini bilasiz. Ular "__proto__" kalitiga har xil munosabatda bo'ladi:
const kelgan = JSON.parse('{"__proto__": {"admin": true}}');
const yoyilgan = { ...kelgan };
const klon = structuredClone(kelgan);
const tayinlangan = Object.assign({}, kelgan);
console.log(yoyilgan.admin); // undefined
console.log(klon.admin); // undefined
console.log(tayinlangan.admin); // true
console.log({}.admin); // undefinedSpread va structuredClone kalitni yangi o'z xususiyat sifatida yaratadi — xuddi JSON.parse dagidek. Object.assign esa tayinlaydi: nusxa["__proto__"] = qiymat. Object.* metodlari darsida aytganimizdek, tayinlash setter'larni ishga tushiradi. __proto__ esa aynan setter — u nusxaning prototipini almashtirdi.
| Usul | "__proto__" kaliti |
|---|---|
JSON.parse |
oddiy o'z kalit |
{ ...obj }, structuredClone |
oddiy o'z kalit |
Object.assign, nishon[kalit] = ... |
prototipni almashtiradi |
for...in + rekursiya |
Object.prototype gacha yetadi |
Yaxshi xabar: Object.assign sayoz, shuning uchun u faqat bitta nusxaning prototipini buzdi — oxirgi qatorda {}.admin toza. Yomon xabar: bitta nusxa ham yetarli bo'lishi mumkin, agar aynan shu obyekt adminmi ga o'xshash tekshiruvdan o'tsa.
Tekshirib ko'ring: Nega
structuredClone(kelgan)xavfsiz,Object.assign({}, kelgan)esa yo'q, axir ikkalasi ham "nusxa" yasaydi?
Javob
Ular xususiyatni yaratish usuli bilan farq qiladi. structuredClone va spread kalitni to'g'ridan-to'g'ri yaratadi — setter chaqirilmaydi. Object.assign esa oddiy tayinlash qiladi, tayinlash esa zanjirdagi __proto__ setter'ini topib, uni chaqiradi.
4.6 Object.freeze(Object.prototype): kuchli, lekin ehtiyotkorlik bilan
Obyektni himoyalash darsida bu chorani tilga olgan edik. Umumiy qozonning qopqog'ini mahkam yopamiz — unga hech narsa qo'shib bo'lmaydi:
Object.freeze(Object.prototype);
const nishon = {};
nishon["__proto__"].admin = true;TypeError: Cannot add property admin, object is not extensibleTarjimasi: "admin xususiyatini qo'shib bo'lmaydi, obyekt kengaytirilmaydi". Muzlagan obyektga yangi xususiyat qo'shilmaydi. Strict rejimda (modul faylda ham, saytdagi «Ishga tushir» da ham) bu ochiq xato beradi, oddiy <script> da esa yozish jimgina e'tiborsiz qoladi (Strict mode):
Object.freeze(Object.prototype);
const nishon = {};
nishon["__proto__"].admin = true;
console.log({}.admin); // undefinedIkkala holatda ham ifloslanish bo'lmadi. Unda nega hamma shunday qilmaydi? Chunki narxi bor.
Birinchi narx — meros nomni o'z obyektingizda ham yozib bo'lmay qoladi. Muzlatilgan prototipdagi toString "faqat o'qish uchun" bo'ladi. Shu nomni oddiy obyektga tayinlash ham taqiqlanadi:
Object.freeze(Object.prototype);
const taom = { nom: "Osh" };
taom.toString = function () {
return this.nom;
};TypeError: Cannot assign to read only property 'toString' of object '#<Object>'Tarjimasi: "#<Object> obyektining faqat o'qiladigan toString xususiyatiga tayinlab bo'lmaydi". taom o'zi muzlamagan, lekin JavaScript qoidasi shunday: meros xususiyat faqat o'qiladigan bo'lsa, tayinlash bilan uni soyalab bo'lmaydi (Xususiyat deskriptorlari). Ba'zi kutubxonalar aynan shunday yozadi va ular buziladi.
Ikkinchi narx — vaqt. Muzlatish dastur boshida, hamma polyfill'lardan keyin bajarilishi kerak (O'rnatilgan prototiplar). Keyin hech kim prototipga metod qo'sha olmaydi.
Uchinchi narx — to'liq emas. Object.prototype muzladi, lekin Array.prototype, Function.prototype ochiq qoldi. Bitta obyektning o'z prototipini almashtirish (Object.assign holati) ham to'xtamaydi.
Xulosa: Object.freeze(Object.prototype) — qo'shimcha sug'urta, asosiy himoya emas. Uni yangi loyihada boshidan yoqib, testlar bilan tekshirish mumkin. Eski, ko'p kutubxonali loyihada esa ehtiyot bo'ling. Node'da shunga yaqin yana bir bayroq bor: node --disable-proto=delete fayl.js — __proto__ eshigini butunlay olib tashlaydi (constructor.prototype yo'lini yopmaydi).
4.7 Kirishni oq ro'yxat bilan olish
Eng ishonchli qatlam — tashqi obyektni umuman "birlashtirmaslik". Buning o'rniga undan faqat kutilgan maydonlarni, kutilgan turda olish. JSON chuqur darsidagi oq ro'yxat (whitelist) g'oyasi eksportda ishlagan edi, endi uni kirishda ham ishlatamiz:
const TILLAR = ["uz", "ru", "en"];
function sozlamaniOl(xom) {
const sozlama = { til: "uz", tungiRejim: false };
if (Object.hasOwn(xom, "til") && TILLAR.includes(xom.til)) {
sozlama.til = xom.til;
}
if (Object.hasOwn(xom, "tungiRejim")) {
sozlama.tungiRejim = xom.tungiRejim === true;
}
return sozlama;
}
const kelgan = JSON.parse(
'{"til": "ru", "admin": true, "__proto__": {"x": 1}}',
);
console.log(sozlamaniOl(kelgan)); // { til: 'ru', tungiRejim: false }Funksiya kelgan obyektda nima borligiga qaramaydi — u faqat o'zi bilgan ikki maydonni so'raydi. admin, __proto__ va boshqa har qanday "ortiqcha" narsa shunchaki tashlab ketiladi. Har maydon tekshiriladi: til — faqat ro'yxatdagi uchtadan biri, tungiRejim — faqat haqiqiy true.
Bu yondashuvning nomi — sxema tekshiruvi (schema validation): "ma'lumot qanday ko'rinishda bo'lishi kerak" degan tavsifni oldindan yozib, kelgan narsani unga solishtirish. Katta loyihalarda sxemani qo'lda emas, maxsus kutubxona bilan yozishadi — masalan Zod. Uni Zod darsida o'rganamiz, hozir bilish shart emas. G'oya esa shu: tashqaridan kelgan narsaga ishonma, faqat o'zing kutganini ol.
Tekshirib ko'ring:
sozlamaniOlichidaObject.hasOwn(xom, "til")o'rnigaxom.til !== undefinedyozilsa,Object.prototype.til = "en"holatida nima bo'lardi?
Javob
Kelgan obyektda til bo'lmasa ham, xom.til zanjirdan "en" ni topardi. Tekshiruv o'tardi va sozlama jimgina "en" ga o'zgarardi. Object.hasOwn bilan esa meros qiymat e'tiborga olinmaydi va standart "uz" qoladi.
5. Hujumchi nigohi
Bu darsning o'zi xavfsizlik haqida, shuning uchun bu bo'lim qisqa — hujumchi qanday fikrlashini umumiy ko'rinishda bilsangiz yetarli.
Nimani qidiradi. Kalitlari foydalanuvchidan keladigan har qanday joyni: JSON qabul qiladigan API, forma maydonlari nomlari, URL parametrlari (?a.b.c=...), import qilinadigan fayllar. Ayniqsa "chuqur birlashtirish", "nusxalash", "yo'l bo'yicha qo'yish" qiladigan funksiyalarni — o'zingiz yozganini ham, kutubxonadagisini ham.
Nimaga erishmoqchi. Ifloslangan xususiyatning o'zi zararsiz ko'rinadi. Hujumchi esa dasturdagi qaysi tekshiruv shu nomli meros qiymatni o'qishini qidiradi: admin, rol, tasdiqlandi, sozlama bayroqlari. Topilsa — ruxsat tekshiruvi aylanib o'tiladi yoki dastur o'zini noto'g'ri tutadi. Hech bo'lmaganda server ishdan chiqishi mumkin: masalan, ifloslangan toString hamma joyda xato bera boshlaydi.
Darsdagi kod qanday qarshi turadi.
xavfsizBirlashtir— xavfli kalitlarni rad etadi va faqat o'z kalitlar bo'ylab yuradi.adminmi,sozlamaniOl— muhim qarorni faqat o'z xususiyatga (Object.hasOwn) va oq ro'yxatga qarab qiladi.Object.create(null)vaMap— tashqi kalitlar uchun eshiksiz lug'at.Object.freeze(Object.prototype)— qo'shimcha sug'urta.
Real hayotda prototype pollution ko'plab mashhur npm kutubxonalarda topilgan va tuzatilgan. Shuning uchun bog'liqliklarni yangilab borish ham himoyaning bir qismi — buni Ta'minot zanjiri xavfsizligi darsida ko'ramiz.
6. Ko'p uchraydigan xatolar
6.1 Faqat "__proto__" ni rad etish
Ko'p "tuzatishlar" bitta nom bilan cheklanadi: if (kalit === "__proto__") continue;. «Yo'l bo'yicha qiymat qo'yish» bo'limidagi constructor.prototype yo'li ochiq qoladi. Tuzatish: uchala nom — "__proto__", "constructor", "prototype" — va bundan tashqari Object.hasOwn tekshiruvi.
6.2 Tashqi obyektni for...in bilan aylanish
for...in meros kalitlarni ham beradi. Prototip allaqachon ifloslangan bo'lsa, siz ifloslikni yangi obyektlarga ham ko'chirasiz. Tuzatish: Object.keys yoki Object.entries.
6.3 "Bormi?" ni in yoki !== undefined bilan tekshirish
"admin" in mehmon va mehmon.admin !== undefined — ikkalasi ham zanjirga qaraydi. Ifloslangan prototipda ular yolg'on "bor" deydi. Tuzatish: Object.hasOwn(mehmon, "admin"). Eski kodda mehmon.hasOwnProperty("admin") ni uchratasiz — u ham o'z xususiyatga qaraydi, lekin Object.create(null) lug'atida yo'q (Object.* metodlari).
6.4 Object.assign ni xavfsiz nusxa deb o'ylash
«Nusxalash usullari bir xil emas» bo'limida ko'rdik: Object.assign({}, kelgan) nusxaning prototipini almashtira oladi. Tuzatish: tashqi ma'lumotni nusxalash kerak bo'lsa — spread yoki structuredClone. Undan ham yaxshisi — oq ro'yxat bilan faqat kerakli maydonlarni olish.
6.5 Object.freeze(Object.prototype) ni oddiy skriptda sinash
Oddiy <script> da muzlagan prototipga yozish xato bermaydi, shunchaki o'tib ketadi. Dasturchi "ishlayapti, himoya kerak emas" deb o'ylashi mumkin. Aslida himoya ishlagan, faqat jim. Tuzatish: himoyani modul faylda (strict) sinang — xato ochiq ko'rinadi.
7. Mashqlar
1-mashq (oson): Ifloslanadimi?
Har qatorda {}.admin nima bo'lishini avval o'ylang, keyin Node'da tekshiring. Har blok alohida faylda ishga tushirilsin.
// A
const a = JSON.parse('{"__proto__": {"admin": true}}');
// B
const b = { ...JSON.parse('{"__proto__": {"admin": true}}') };
// C
const c = {};
c["__proto__"]["admin"] = true;
// D
const d = Object.create(null);
d["__proto__"] = { admin: true };Yechim
const a = JSON.parse('{"__proto__": {"admin": true}}');
console.log({}.admin); // undefined
const b = { ...JSON.parse('{"__proto__": {"admin": true}}') };
console.log({}.admin); // undefined
const d = Object.create(null);
d["__proto__"] = { admin: true };
console.log({}.admin); // undefined
const c = {};
c["__proto__"]["admin"] = true;
console.log({}.admin); // trueFaqat C ifloslaydi. c["__proto__"] — Object.prototype ning o'zi, unga admin yozildi. A va B kalitni oddiy o'z xususiyat qiladi. D da __proto__ eshigi umuman yo'q. Yechimda C ni oxiriga qo'ydik — aks holda undan keyingi qatorlar ham true ko'rsatardi.
2-mashq (o'rta): qiymatQoy ni tuzating
«Yo'l bo'yicha qiymat qo'yish» bo'limidagi qiymatQoy funksiyasini xavfsiz qiling:
- yo'lda xavfli kalit bo'lsa — hech narsa yozilmasin;
- oraliq obyektga faqat u
joriyning o'zida bo'lsa tushilsin, aks holda yangi obyekt yaratilsin; - yangi oraliq obyektlar
Object.create(null)bilan yaratilsin.
Ishora: yo'l qismlarini avval tekshirish uchun some (Callback imzosi) va includes qulay.
Yechim
const XAVFLI_KALITLAR = ["__proto__", "constructor", "prototype"];
function xavfsizQiymatQoy(obyekt, yol, qiymat) {
const qismlar = yol.split(".");
if (qismlar.some((qism) => XAVFLI_KALITLAR.includes(qism))) {
return; // xavfli yo'l — hech narsa qilmaymiz
}
let joriy = obyekt;
for (const qism of qismlar.slice(0, -1)) {
if (!Object.hasOwn(joriy, qism)) {
joriy[qism] = Object.create(null);
}
joriy = joriy[qism];
}
joriy[qismlar.at(-1)] = qiymat;
}
const sozlama = {};
xavfsizQiymatQoy(sozlama, "tema.rang", "yashil");
xavfsizQiymatQoy(sozlama, "constructor.prototype.chegirma", 100);
xavfsizQiymatQoy(sozlama, "__proto__.admin", true);
console.log(sozlama.tema.rang); // yashil
console.log({}.chegirma); // undefined
console.log({}.admin); // undefinedsome birinchi xavfli qismdayoq true qaytaradi va funksiya erta chiqadi. Object.hasOwn meros constructor ga tushishga yo'l qo'ymaydi. Object.create(null) esa yangi daraja ichida ham eshik qoldirmaydi — uchta qatlam bir-birini sug'urtalaydi.
3-mashq (qiyin): Teglar sanog'i
«Bahor» botiga mehmonlar sharh yozadi va teglar qo'yadi. Teglar massiv bo'lib keladi: ["osh", "__proto__", "osh", "constructor", "manti"]. Har teg necha marta uchraganini sanaydigan teglarniSana(teglar) funksiyasini yozing. Talablar: natija to'g'ri bo'lsin (har teg alohida sanalsin), Object.prototype ifloslanmasin, reduce ishlatilsin (reduce).
Yechim
function teglarniSana(teglar) {
return teglar.reduce((sanoq, teg) => {
sanoq[teg] = (sanoq[teg] ?? 0) + 1;
return sanoq;
}, Object.create(null));
}
const teglar = ["osh", "__proto__", "osh", "constructor", "manti"];
console.log(teglarniSana(teglar));
console.log({}.osh); // undefinedKonsolda:
[Object: null prototype] {
osh: 2,
['__proto__']: 1,
constructor: 1,
manti: 1
}
undefinedAkkumulyatorning boshlang'ich qiymati — Object.create(null). Oddiy {} bilan sanoq["__proto__"] prototipni qaytarardi, sanoq["constructor"] esa funksiyani (Object.create darsidagi muammo). Bu yerda teglar "rad etilmaydi" — ular foydalanuvchining haqiqiy ma'lumoti. Shuning uchun to'g'ri yo'l — ularni xavfsiz saqlash. Map darsida xuddi shu vazifani yana bir usulda yechamiz.
4-mashq: Vazifalar qadami — xavfsiz import (mashqlar repo)
JSON chuqur darsida vazifalar eksport formatini oldi: { versiya: 1, vazifalar: [...] }. Importning o'zini — faylni o'qish va noto'g'ri JSON'ni ushlash — try / catch darsida qo'shamiz. Bugun uning xavfsiz yadrosini oldindan tayyorlaymiz, shuning uchun qadam kurs/mashqlar/10/35-himoya/xavfsiz-import.js da.
- Darsdagi
XAVFLI_KALITLAR,oddiyObyektmivaxavfsizBirlashtirni faylga ko'chiring. vazifaniOl(xom)yozing: oq ro'yxat["id", "matn", "bajarildi"](eksportdagi maydonlar) bo'yicha, faqatObject.hasOwnbilan, standart qiymatlar{ id: 0, matn: "", bajarildi: false }ustiga.paketniOqi(matn)yozing: u JSON'ni o'qib,xavfsizBirlashtir({}, ...)dan o'tkazsin.versiya1 bo'lmasa yokivazifalarmassiv bo'lmasa — bo'sh massiv qaytarsin. Aks holda har vazifanivazifaniOldan o'tkazsin.- Uch holatni tekshiring: oddiy paket,
"__proto__"kalitli paket, vazifa ichida"constructor": {"prototype": ...}bo'lgan paket. Har biridan keyin{}.admin—undefined.
Yechim
const XAVFLI_KALITLAR = ["__proto__", "constructor", "prototype"];
const VAZIFA_MAYDONLARI = ["id", "matn", "bajarildi"];
function oddiyObyektmi(qiymat) {
return typeof qiymat === "object" && qiymat !== null
&& !Array.isArray(qiymat);
}
function xavfsizBirlashtir(nishon, manba) {
for (const kalit of Object.keys(manba)) {
if (XAVFLI_KALITLAR.includes(kalit)) {
continue;
}
const qiymat = manba[kalit];
if (oddiyObyektmi(qiymat)) {
const ichkisiBormi = Object.hasOwn(nishon, kalit)
&& oddiyObyektmi(nishon[kalit]);
if (!ichkisiBormi) {
nishon[kalit] = {};
}
xavfsizBirlashtir(nishon[kalit], qiymat);
} else {
nishon[kalit] = qiymat;
}
}
return nishon;
}
function vazifaniOl(xom) {
const vazifa = { id: 0, matn: "", bajarildi: false };
for (const maydon of VAZIFA_MAYDONLARI) {
if (Object.hasOwn(xom, maydon)) {
vazifa[maydon] = xom[maydon];
}
}
return vazifa;
}
function paketniOqi(matn) {
const paket = xavfsizBirlashtir({}, JSON.parse(matn));
if (paket.versiya !== 1 || !Array.isArray(paket.vazifalar)) {
return [];
}
return paket.vazifalar.map(vazifaniOl);
}
const oddiy = '{"versiya": 1, "vazifalar": '
+ '[{"id": 1, "matn": "Non olish", "bajarildi": false}]}';
const proto = '{"versiya": 1, "__proto__": {"admin": true}, '
+ '"vazifalar": []}';
const konstruktor = '{"versiya": 1, "vazifalar": [{"id": 2, '
+ '"matn": "Choy", "constructor": {"prototype": {"admin": 1}}}]}';
console.log(paketniOqi(oddiy));
console.log({}.admin);
console.log(paketniOqi(proto));
console.log({}.admin);
console.log(paketniOqi(konstruktor));
console.log({}.admin);Konsolda:
[ { id: 1, matn: 'Non olish', bajarildi: false } ]
undefined
[]
undefined
[ { id: 2, matn: 'Choy', bajarildi: false } ]
undefinedUchinchi holatda vazifa ichidagi constructor ikki marta to'xtatildi: xavfsizBirlashtir ichki massivga tushmaydi (u butunligicha olinadi), lekin vazifaniOl oq ro'yxatda yo'q kalitni umuman o'qimaydi. bajarildi yo'q edi — standart false qo'yildi. Turlar (id son, matn satr ekanligi) tekshiruvini import darsida qo'shamiz.
Commit (mashqlar repo): 10/35: xavfsiz import yadrosini yoz.
8. Real ishda
- Kutubxonalarning zaifliklari. Chuqur birlashtirish, nusxalash va yo'l bo'yicha qiymat qo'yish funksiyalari bir necha mashhur npm kutubxonasida prototype pollution zaifligi bo'lib chiqqan va yangi versiyalarda tuzatilgan.
npm auditkabi vositalar (16-qismda) eskirgan versiyani topib, ogohlantiradi. - Server kodi. Express kabi server freymvorklarida (25-qismda o'rganamiz) so'rov tanasi (
body) JSON bo'lib keladi. Uni to'g'ridan-to'g'ri sozlamalar yoki foydalanuvchi obyektiga birlashtirish — klassik xato. Kasbiy qoida: kirish ma'lumoti avval sxema bilan tekshiriladi. - OWASP. Ilova xavfsizligi bo'yicha xalqaro jamoa OWASP prototype pollution uchun alohida qo'llanma (cheat sheet) chiqargan. Undagi tavsiyalar shu darsdagilar bilan bir xil:
Object.create(null),Map, kalitlarni tekshirish,Object.freeze. - Node bayroqlari.
--disable-proto=deleteyoki--disable-proto=throwbilan__proto__eshigini butun jarayon uchun yopish mumkin. - Intervyu. "Prototype pollution nima va qanday himoyalanasiz?", "
Object.create(null)nima uchun kerak?", "invaObject.hasOwnfarqi?" — middle darajadagi intervyularda, ayniqsa backend uchun, tez-tez so'raladi.
Xulosa
- Prototype pollution — tashqi kalit tufayli umumiy
Object.prototypega begona xususiyat tushishi; shundan keyin hamma obyekt uni "ko'radi". - Eshiklar:
"__proto__"va"constructor"→"prototype". Xavfli naqshlar: tekshiruvsiz deep merge va deep set. JSON.parse, spread vastructuredClone"__proto__"ni oddiy kalit qiladi;Object.assignvanishon[kalit] = ...esa prototipni almashtiradi.- Himoya qatlamlari: xavfli kalitlarni rad etish,
Object.keys+Object.hasOwn,Object.create(null)yokiMap, oq ro'yxat bilan olish. Object.freeze(Object.prototype)— foydali sug'urta, lekin meros nomlarni soyalashni ham taqiqlaydi va dastur boshida bajarilishi kerak.
Keyingi dars: class sintaksisi — prototip va konstruktor funksiya ustidagi qulay yozuv: class, constructor va metodlar.
Manbalar
- MDN: "Object.prototype.__proto__", "Object.create()", "Object.hasOwn()", "Object.freeze()" — developer.mozilla.org
- OWASP Cheat Sheet Series: "Prototype Pollution Prevention Cheat Sheet" — cheatsheetseries.owasp.org
- Node.js hujjatlari: "Command-line options —
--disable-proto" — nodejs.org/api/cli.html - ECMAScript 2025 Language Specification: "JSON.parse", "CreateDataProperty", "Object.prototype.__proto__" — tc39.es/ecma262
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!