JSON Toolkit

__proto__ injection คืออะไร

สรุปสำคัญ: JSON.parse มอง __proto__ เป็นคีย์ธรรมดา; ความเสียหายเกิดใน recursive merge ที่เขียนไปยัง Object.prototype ทำให้ทุก object สืบทอดพร็อพเพอร์ตี้ของผู้โจมตี

ขั้นสูงอ่านประมาณ 5 นาทีเผยแพร่ 2026-10-30อัปเดต 2026-10-30
prototype pollution ทำงานอย่างไรและจะบล็อกได้อย่างไร

สรุปสั้น ๆ: บล็อก __proto__, constructor และ prototype ก่อนการ merge ใด ๆ, merge ข้อมูลที่ไม่น่าเชื่อถือลงใน Object.create(null) และเพิ่ม assertion เรื่อง prototype ใน CI; การแพตช์ไลบรารีอย่างเดียวไม่พอ

พูดสั้น ๆ: คีย์ที่ไม่น่าเชื่อถือชื่อ __proto__ เจอ recursive merge และ merge นั้นเขียนไปยัง prototype

การจำลอง

function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = target[key] || {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
console.log({}.isAdmin);   // true

ไม่มี object ทางธุรกิจใดถูกแตะต้อง Object.prototype ต่างหาก และตอนนี้ทุก object พก isAdmin ไปด้วย

ทำไม JSON.parse ไม่ใช่ต้นเหตุ

const data = JSON.parse('{"__proto__": {"a": 1}}');
Object.getOwnPropertyNames(data);                  // [ '__proto__' ]
Object.getPrototypeOf(data) === Object.prototype;  // true, untouched

__proto__ มาถึงเป็น own property ธรรมดา อันตรายอยู่ที่โค้ดซึ่งภายหลัง merge, deep copy หรือ set ตาม path ทั้งหมด — _.merge, รูปแบบ Object.assign บางแบบ และตัวช่วย set(obj, path, value) ที่เขียนเองนับไม่ถ้วน

ผลกระทบ

  • ข้ามการอนุญาต: if (user.isAdmin) กลายเป็นจริงโดยไม่มีเงื่อนไข
  • ข้อมูลเสียหาย: ฟิลด์ที่ไม่คาดคิดถูก serialize ลงใน record ที่เก็บไว้
  • Denial of service: object ขนาดใหญ่ที่ติดกับ prototype ทำให้หน่วยความจำโตถาวร
  • ยกระดับสิทธิ์: เมื่อรวมกับ template engine หรือตัวรันคำสั่ง เคยนำไปสู่ remote code execution

การป้องกันสี่ชั้น

กรองคีย์

const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

บล็อกแค่ __proto__ ไม่พอ: constructor.prototype ไปถึงที่เดียวกันได้

Merge ลงใน object ที่ไม่มี prototype

const safeTarget = Object.create(null);

เมื่อไม่มี prototype chain __proto__ ก็เป็นแค่คีย์สตริง

ใช้ Object.hasOwn แทน in เพราะ in จะสอบถาม prototype chain

อัปเดต dependency ให้เป็นเวอร์ชันล่าสุด lodash, deepmerge และ json-patch ต่างเคยออกแพตช์สำหรับบั๊กประเภทนี้ ให้ติดตาม npm audit

ตรวจสอบว่ายังปลอดภัย

ป้อน payload ที่มี __proto__ และ constructor ผ่าน merge ของคุณ แล้ว assert:

console.log({}.polluted);                 // undefined
console.log(Object.prototype.polluted);   // undefined

assertion หนึ่งตัวใน CI ต้นทุนเกือบศูนย์ จับ regression จากการ refactor ภายหลังได้

คำถามที่พบบ่อย

การ merge ควรกรองคีย์ใดออก?

บล็อก __proto__, constructor และ prototype พร้อมกัน กรองแค่ __proto__ ไม่พอ เพราะ constructor.prototype ไปถึง prototype เดียวกันได้

ทำไม Object.create(null) หยุดการเขียนได้?

object ที่ไม่มี prototype มี prototype chain ว่าง __proto__ จึงยังเป็นคีย์สตริงธรรมดา มันใช้เป็นที่เก็บข้อมูลได้ แต่ไม่ทดแทนการกรองคีย์หรือการอัปเดต dependency

จะทดสอบ prototype pollution ใน CI ได้อย่างไร?

ส่ง payload ที่มี __proto__ และ constructor ผ่าน merge ของคุณ แล้ว assert ว่า {}.polluted และ Object.prototype.polluted เป็น undefined assertion หนึ่งตัวต้นทุนแทบไม่มี และจับ regression จากการ refactor ภายหลังได้

อ่านเพิ่มเติม