3 คะแนน โดย GN⁺ 2024-11-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • คุกกี้ HTTP เป็นกลไกพื้นฐานสำหรับคงสถานะของเว็บ แต่ เบราว์เซอร์·เซิร์ฟเวอร์·ไลบรารีมาตรฐาน กลับตีความอักขระที่อนุญาตและการจัดการข้อผิดพลาดไม่ตรงกัน จนทำให้เกิดปัญหาจริงได้
  • ตระกูล RFC 6265 กำหนดเงื่อนไขของค่าที่เซิร์ฟเวอร์ส่งผ่าน Set-Cookie กับค่าที่เบราว์เซอร์ยอมรับต่างกัน และค่าที่สร้างผ่าน document.cookie ก็อาจชนกับสมมติฐานของตัวแยกวิเคราะห์ฝั่งเซิร์ฟเวอร์
  • Firefox, Chromium และ Safari มีพฤติกรรมต่างกันในการจัดการช่องว่าง เครื่องหมายอัญประกาศ จุลภาค แบ็กสแลช และ Unicode โดย Safari แสดงพฤติกรรมที่เมื่อเจออักขระต้องห้ามจะไม่ทิ้งคุกกี้ทั้งก้อน แต่ เก็บไว้เฉพาะส่วนก่อนหน้า
  • Go อาจเงียบ ๆ ทิ้งคุกกี้ JSON ที่เบราว์เซอร์ยอมรับได้, Python SimpleCookie อาจหยุดโหลดหลังเจอคุกกี้ที่มันไม่เข้าใจ, และ PHP·Ruby·Rust ก็มีขอบเขตการยอมรับต่างกันไป
  • คุกกี้ Unicode เพียงตัวเดียวสามารถทำให้เว็บใหญ่ ๆ อย่าง Facebook, Netflix, Okta, WhatsApp, AWS และ Apple Support เกิดข้อผิดพลาด 400/500 หรือระบบล่มบางส่วนได้ จึงควรทำให้สเปกคุกกี้และพฤติกรรมของไลบรารีสอดคล้องกันให้ชัดเจนกว่านี้

คุกกี้ที่เบราว์เซอร์รับได้ แต่ Go อ่านไม่ได้

  • คุกกี้คือข้อมูลที่ตั้งผ่าน JavaScript อย่าง document.cookie หรือผ่าน HTTP server และจะถูกแนบไปกับคำขอ HTTP ที่อยู่ในขอบเขตจนกว่าจะหมดอายุ
  • ตัวอย่าง JavaScript เก็บสตริง JSON ตรง ๆ เป็นค่าคุกกี้ session
    • ค่าจะอยู่ในรูป {"ginger":"snap","peanutButter":"chocolate chip","snicker":"doodle"}
    • เวลาใส่ JSON ลงในคุกกี้มักมีการ serialize เป็น base64 แต่เบราว์เซอร์ตั้งค่านี้ได้โดยไม่มีปัญหาและส่งกลับในส่วนหัว Cookie
  • ปัญหาเกิดขึ้นเมื่อคุกกี้นี้ถูกส่งเข้าโค้ดที่ใช้ Go standard library
    • ตัวแยกวิเคราะห์ของ Go ไม่สามารถตีความคุกกี้นี้ได้
    • ความล้มเหลวลามต่อขึ้นไปในชั้นบนของสแตก

เกณฑ์สองชุดใน RFC ที่ไม่ตรงกัน

  • คุกกี้ถูกนิยามผ่าน RFC 2109, RFC 2965, RFC 6265 และปัจจุบันมี draft version ที่กำลังปรับปรุงอยู่
  • RFC ปฏิบัติต่อค่าคุกกี้ต่างกันในสองบริบท
    • Section 4.1.1 ตัดอักขระควบคุม ช่องว่าง เครื่องหมายอัญประกาศคู่ จุลภาค อัฒภาค และแบ็กสแลช ออกจากค่าที่เซิร์ฟเวอร์ส่งผ่าน Set-Cookie
    • Section 5.6 กลับเปิดกว้างกว่ามากสำหรับสิ่งที่เบราว์เซอร์ควรยอมรับเมื่อแยกวิเคราะห์สตริง Set-Cookie โดยยกเว้นเพียงอักขระควบคุม
  • จุดขัดแย้งหลักคือ ค่าที่เซิร์ฟเวอร์ควรส่ง กับ ค่าที่เบราว์เซอร์ควรรับ ไม่ได้จัดให้สอดคล้องกัน
    • ถ้าเบราว์เซอร์รับเฉพาะคุกกี้ที่เซิร์ฟเวอร์ตั้งเอง ผลกระทบอาจน้อย แต่ document.cookie ก็สร้างคุกกี้ได้เช่นกัน
    • มาตรฐานไม่ได้ระบุชัดว่าไลบรารีมาตรฐานที่ประมวลผลส่วนหัว Cookie ควรผ่อนปรนแบบ user agent หรือเข้มงวดแบบเซิร์ฟเวอร์

ความต่างของการยอมรับค่าคุกกี้ในแต่ละเบราว์เซอร์

  • Firefox

    • การตรวจสอบค่าคุกกี้ของ Firefox ยอมให้อักขระบางตัวที่ RFC 6265 ห้ามไว้
    • อักขระที่ RFC แนะนำให้ตัดออกแต่ Firefox ยอมรับ ได้แก่
      • 0x09 horizontal tab
      • 0x20 ช่องว่าง
      • 0x22 เครื่องหมายอัญประกาศคู่
      • 0x2C จุลภาค
      • 0x5C แบ็กสแลช
    • พฤติกรรมนี้มีเข้ามาแต่เดิมเพื่อให้เข้ากันได้กับ Chrome และยังคงอยู่ในทั้งสอง codebase
    • การตั้งค่า network.cookie.blockUnicode สามารถปฏิเสธค่าตั้งแต่ 0x80 ขึ้นไปได้ และมีการติดตามงานนี้ใน bug 1797231
    • ปัญหาการยอมรับ 0x7F ถูกแก้ใน Firefox 108 ตาม bug 1797235
  • Chromium

    • Chromium ปฏิเสธเฉพาะ อักขระควบคุม และอัฒภาคในค่าคุกกี้
    • จึงเข้มงวดกว่า Firefox เล็กน้อย เพราะไม่รับ 0x09 horizontal tab
    • แต่ต่างจาก RFC ตรงที่ยังรับและส่งต่อช่องว่าง เครื่องหมายอัญประกาศคู่ จุลภาค แบ็กสแลช และอักขระ Unicode ได้
  • Safari / WebKit

    • โค้ดเก็บคุกกี้ของ Safari อยู่ภายใน CFNetwork ซึ่งเป็นซอฟต์แวร์ปิด จึงตรวจสอบโดยตรงได้ยาก
    • จากการทดสอบตั้งค่าคุกกี้ผ่าน JavaScript ด้วยค่าตั้งแต่ 0x00 ถึง 0xFF พบว่า Safari ยอมรับค่าต่อไปนี้
      • 0x09 horizontal tab
      • 0x20 ช่องว่าง
      • 0x22 เครื่องหมายอัญประกาศคู่
      • 0x5C แบ็กสแลช
    • Safari ไม่ยอมรับ 0x7F delete และอักขระ 0x80-FF high ASCII / Unicode
    • RFC ระบุว่าเมื่อพบอักขระควบคุมควรทิ้งคุกกี้ทั้งก้อน แต่ Safari กลับยอมรับค่าจนถึงตำแหน่งก่อนหน้าที่เจออักขระต้องห้าม
    • ยังพบ Safari bug ที่เมื่อตั้งค่า -- , -- จะลบช่องว่างรอบจุลภาคออก

ความต่างในการแยกวิเคราะห์ของภาษาและไลบรารีมาตรฐาน

  • Go

    • โค้ดคุกกี้ของ Go มีพฤติกรรมค่อนข้างใกล้กับถ้อยคำ RFC สำหรับค่าที่เซิร์ฟเวอร์ส่งผ่าน Set-Cookie
    • ในการใช้งานจริงมันยอมรับช่องว่างและจุลภาคที่พบได้บ่อย แต่ไม่ยอมรับเครื่องหมายอัญประกาศคู่ อัฒภาค และแบ็กสแลช
    • หากส่วนหัว Cookie ตัวอย่างมีคุกกี้ JSON อยู่ด้วย ผลจาก request.Cookies() ของ Go จะเหลือเพียง cookie1=foo และ cookie3=bar
    • cookie2 ที่เบราว์เซอร์ยอมรับจะหายไปแบบเงียบ ๆ โดยไม่มี exception หรือ error ชัดเจน
  • PHP

    • PHP ไม่มีฟังก์ชัน native สำหรับ parse คุกกี้ ทำให้ระบุขอบเขตการยอมรับที่แน่ชัดได้ยาก แต่ผลทดสอบพบว่าพฤติกรรมต่ออักขระควบคุมไม่สม่ำเสมอ
    • ค่าพวก 0x00-0x09 และ 0x0D carriage return ใช้งานได้
    • แต่ถ้าใช้ 0x10 data link escape หรือ 0x7F delete, PHP จะส่งข้อผิดพลาด 400 Bad Request
    • คุกกี้ Unicode ก็ปรากฏในผลทดสอบด้วย
  • Python

    • http.cookies.SimpleCookie ของ Python จะหยุดโหลดคุกกี้หลังเจอคุกกี้ JSON แบบเงียบ ๆ
    • ในตัวอย่าง อินพุตจะเหลือผลลัพธ์เพียง cookie1=foo
    • หาก subdomain สามารถตั้งคุกกี้มีปัญหาให้โดเมนหลักได้ คุกกี้ตัวเดียวนี้ก็อาจทำลายการประมวลผลคุกกี้ของทั้งเว็บไซต์
    • การจัดการอักขระควบคุมก็ไม่เป็นระเบียบเช่นกัน
      • อักขระควบคุมบางตัวถูกโหลดเป็นค่าว่าง
      • หากเติม aa ไว้หน้าและหลังค่า คุกกี้ที่มีอักขระควบคุมจะไม่ถูกโหลด
  • Ruby

    • CGI::Cookie.parse ของ Ruby ดูจะผ่อนปรนมากในการ parse
    • มันรับอักขระควบคุม, tab, เครื่องหมายอัญประกาศคู่, จุลภาค, แบ็กสแลช, 0x7F และอักขระ Unicode ได้ และจะใช้ percent-encoding เมื่อนำค่าออกจาก cookie jar
    • วิธีนี้อาจใกล้เคียงสิ่งที่ดีที่สุดในโลกของคุกกี้ แต่โค้ดที่ตั้งค่าผ่าน document.cookie อาจไม่ได้คาดหวังค่าสะท้อนกลับที่ถูก percent-encode
  • Rust

    • Rust ไม่มีฟังก์ชันจัดการคุกกี้พื้นฐานในตัว จึงอ้างอิงจาก cookie crate ที่นิยมใช้
    • cookie crate ในการตั้งค่าเริ่มต้นดูจะอยู่ฝั่งที่ผ่อนปรนมากกว่า และดูเหมือนยอมรับสตริง UTF-8 ที่ส่งเข้ามาได้

ผลกระทบที่เห็นได้จริงบนเว็บไซต์

  • ปัญหานี้ถูกพบระหว่างการตรวจสอบอัปเดตไลบรารี third-party แบบ manual บนเว็บไซต์ทดสอบ
    • เป็นการเปลี่ยนแปลงที่จับด้วย automated test ได้ยาก
    • หากปล่อยขึ้น production ผู้เข้าชมหลังจากนั้นอาจได้รับคุกกี้เสียและติดอยู่กับข้อผิดพลาดที่อธิบายได้ยากจนกว่าจะ rollback การอัปเดตและลบคุกกี้
  • ปัญหานี้ไม่ได้จำกัดอยู่แค่เว็บเล็ก ๆ หรือ framework เฉพาะทาง
  • หากตั้งค่า Unicode cookie ให้โดเมนจาก browser console แบบนี้ ก็อาจทำให้หลายเว็บไซต์ใหญ่พังได้
    • document.cookie="unicodeCookie=🍪; domain=.grayduck.mn; Path=/; SameSite=Lax"
  • กรณีที่สังเกตพบมีดังนี้
    • Facebook: แสดงหน้าข้อผิดพลาดและรูปภาพก็พังด้วย
    • Instagram และ Threads: เกิดข้อผิดพลาด 500 แบบตรง ๆ
    • Netflix: ส่งคืนข้อผิดพลาด NSES-500 และแม้แต่หน้าช่วยเหลือก็พัง
    • Okta: ทุกหน้าเข้าสู่ระบบส่งคืนข้อผิดพลาด 400
    • WhatsApp: แสดง “whatsapp error”
    • Amazon: ส่วนใหญ่ยังใช้งานได้ แต่บางฟีเจอร์พังแบบสุ่ม
    • AWS: คอนโซลล็อกอินส่งคืนข้อผิดพลาด 400 และใช้งานต่อไม่ได้
    • Apple Support: โหลดรายการอุปกรณ์ไม่ได้
    • Best Buy: เมนูนำทางใช้งานได้ แต่ฟังก์ชันค้นหาใช้ไม่ได้
    • eBay: ส่วนใหญ่แก้แล้ว แต่บางส่วนยังส่ง 400 อยู่
    • Home Depot: มีแผนจะแก้
    • Intuit: เป็นเว็บไซต์เดียวที่ระบุสาเหตุของข้อผิดพลาดได้
    • Outlook: พบอีกกรณีของข้อผิดพลาด 400

ความยากในการแก้ระหว่างมาตรฐานกับความเข้ากันได้

  • การแก้ปัญหาในสเปกพื้นฐานที่มีอายุกว่า 30 ปีเป็นเรื่องยากมาก และปัญหานี้อาจไม่มีทางออกที่ดีนัก
  • ทั้ง Mozilla และ Google ต่างพิจารณาและทำงานกับแนวทางบล็อกคุกกี้ลักษณะนี้จากฝั่งเบราว์เซอร์
  • การบล็อกแบบฝ่ายเดียวซับซ้อนเพราะปัญหาด้าน compatibility
    • คุกกี้ที่ไม่ใช่ ASCII พบได้น้อยมาก คิดเป็นต่ำกว่า 0.01% ของคุกกี้ทั้งหมด
    • แต่ telemetry ชี้ว่าพบได้บ่อยกว่ามากในประเทศอย่าง Argentina, Mexico และ Finland
    • Mozilla จึงทำการตั้งค่า network.cookie.blockUnicode ที่เปิดใช้งานได้รวดเร็วไว้แล้ว แต่ยังไม่เปิดจริงเพราะปัญหาความเข้ากันได้กับพฤติกรรมของ Chromium
  • การแก้ฝั่งเซิร์ฟเวอร์ก็อาจทำได้ แต่กินวงกว้างไปถึงเว็บไซต์นับล้านและการจัดการข้อผิดพลาดภายในภาษาและเฟรมเวิร์กจำนวนมาก
    • เว็บอย่าง Facebook หรือ Netflix อาจพอบรรเทาได้ แต่ผู้ดูแลเว็บไซต์ทั่วไปอาจไม่มีเวลาหรือความสามารถในการแก้
  • ทางออกระดับรากคือให้ IETF HTTP Working Group จัดแนวสเปกคุกกี้ให้สอดคล้องกันภายใน และกำหนดให้เข้มงวดว่าระบบจัดการคุกกี้ควรทำงานอย่างไร
    • การอนุญาตหรือไม่อนุญาตอักขระ non-ASCII ควรเหมือนกันทั้งฝั่งเซิร์ฟเวอร์และ user agent
    • ขั้นตอนที่เบราว์เซอร์ ภาษา และเฟรมเวิร์กใช้จัดการคุกกี้ก็ควรถูกระบุอย่างชัดเจนเหมือนมาตรฐาน W3C สมัยใหม่อย่าง Content Security Policy
    • พฤติกรรมที่คุกกี้ผิดตัวเดียวทำให้การประมวลผลคุกกี้อื่นหยุดตามไปด้วย เป็นสิ่งที่ยอมรับได้ยากเพราะนำไปสู่ความขัดข้องที่คาดไม่ถึงหลายแบบ

ขั้นตอนการจัดการคุกกี้ที่เสนอ

  • เริ่มจาก field-value แล้วแยกด้วย ; และ , เพื่อสร้างรายการ raw-cookie-pair โดยไม่ถือว่าจุลภาคเป็นคำพ้องของอัฒภาค
  • raw-cookie-pair แต่ละรายการให้ประมวลผลตามลำดับนี้
    • ถ้าไม่มี = ให้ข้ามไป pair ถัดไป
    • ตัดช่องว่างหัวท้ายออก
    • ส่วนก่อน = ตัวแรกให้ถือเป็น cookie-name-octets และส่วนหลังให้ถือเป็น cookie-value-octets
    • ถ้าค่าเริ่มด้วยเครื่องหมายอัญประกาศคู่ ให้ลบอันเปิดออกหนึ่งตัว และถ้ามีอันปิดท้ายก็ให้ลบออกหนึ่งตัว
    • ถ้าชื่อหรือค่ามีรูปแบบที่เซิร์ฟเวอร์รับไม่ได้ ให้ข้าม pair นั้น
    • ทูเพิล [cookie-name-octets, cookie-value-octets] ที่เหลือให้นำไปประมวลผลตามนิยามของเซิร์ฟเวอร์
  • มีข้อเสนอเพิ่มเติมว่าเซิร์ฟเวอร์ควรปฏิเสธทูเพิลที่ชื่อคุกกี้ไม่ใช่ token และปฏิเสธค่าคุกกี้ที่มี octet นอกชุด cookie-octet

1 ความคิดเห็น

 
GN⁺ 2024-11-22
ความคิดเห็นจาก Hacker News
  • คุกกี้เต็มไปด้วยกับดักแปลก ๆ และพฤติกรรมที่ชวนหงุดหงิด แต่ 99.95% ก็ทำงานได้ดี กับดักคุกกี้ที่ผมชอบที่สุดคือ cookie shadowing คือถ้าตั้งค่าคุกกี้ชื่อเดียวกันแต่ต่างกันแค่แอตทริบิวต์สำคัญอย่างโดเมนหรือพาธ ก็จะมีคุกกี้ที่แทบเหมือนกันหลายตัวเกิดขึ้นพร้อมกัน และทั้งแบ็กเอนด์หรือ JS ก็ไม่มีทางแยกได้ว่าอันไหนเป็นอันไหน
    ลองไปที่ https://example.com/somepath แล้วพิมพ์สิ่งต่อไปนี้ในคอนโซลของเบราว์เซอร์ได้เลย
    document.cookie = "foo=a";
    document.cookie = "foo=b; domain=.example.com";
    document.cookie = "foo=c; path=/somepath";
    document.cookie
    ในกรณีของผม ผลลัพธ์คือ 'foo=c; foo=a; foo=b'

    • ไม่รู้ว่าใครในบริษัทออกแบบไว้แบบนี้ แต่เขาเอา สเตจจิงกับสภาพแวดล้อมพัฒนา ไปไว้บนโดเมนเดียวกัน และทั้งบริษัทขนาดมหึมาก็กำลังทำตามแพตเทิร์นนี้อยู่
      เป็นความผิดพลาดครั้งใหญ่จริง ๆ
    • ผมคิดว่าพฤติกรรมแปลก ๆ จำนวนมากที่เกิดจากการใช้ หลายบัญชี บนเว็บไซต์เดียวกันในเบราว์เซอร์เดียวกัน อาจอธิบายได้ด้วยเรื่องนี้
    • ถ้าอยู่ที่ /somepath การได้รับค่า C ซึ่งเป็นค่าที่เจาะจงที่สุดในสามค่าก็ดูสมเหตุสมผลอยู่มาก ทุกค่าถูกส่งกลับมาตามลำดับ จึงรู้ได้ทั้งค่าตามพาธและค่าระดับโกลบอล ฟังดูเหมือนเป็นทางประนีประนอมที่ดีที่สุด
      แต่ตัว setter ของ document.cookie ที่เหมือนเวทมนตร์นี่ไม่ค่อยถูกใจเท่าไร ถึงอย่างนั้นมันก็เป็นของที่อายุเกือบ 30 ปีแล้ว คงช่วยไม่ได้
    • อนึ่ง ในเชิงเทคนิคแล้ว จุดนำหน้าโดเมนไม่อนุญาตและจะถูกละทิ้ง: https://www.rfc-editor.org/rfc/rfc6265#section-4.1.2.3
      ประเด็นนี้กลับมาอีกครั้งเมื่อไม่นานมานี้หลังจาก jshttp/cookie เพิ่มความเข้มงวดในการตรวจสอบ: https://github.com/jshttp/cookie/pull/167
      หลัง PR นั้น การตรวจสอบก็ถูกผ่อนลงเล็กน้อยอีกครั้ง คล้ายกับโค้ดของเบราว์เซอร์ที่บทความกล่าวถึง
      การเปลี่ยนแปลงเดิมเริ่มจากโค้ดของเรา ตอนที่เราพบบั๊กซึ่งสร้าง เฮดเดอร์คุกกี้ ด้วยการต่อสตริงโดยไม่เข้ารหัส บางครั้งค่ามีช่องว่างจนทำให้คำขอเสีย เราจึงตั้งใจจะแนะนำให้นักพัฒนาใช้ serialize() ของ jshttp/cookie เพื่อเลี่ยงปัญหานี้ แต่ก็พบว่าการตรวจสอบของฟังก์ชันนั้นยังไม่พอที่จะจับบั๊กที่เราเจอ
      พอเสนอแพตช์ไป คนอื่นก็พบว่าการตรวจสอบหลวมเกินไป จนสามารถแทรก JS ลงใน ฟิลด์ชื่อ ของคุกกี้ แล้วให้อีกที่หนึ่งตีความมันเหมือนเป็น ค่า ได้ กลายเป็นเส้นทาง code injection ที่ค่อนข้างแปลก
    • ใช่เลย มีปัจจัยเสี่ยงเยอะจริง ๆ ที่ https://www.usenix.org/conference/usenixsecurity15/technical-sessions/presentation/zheng อธิบายปัญหานี้และเรื่องน่าปวดหัวที่เกี่ยวข้องไว้อย่างละเอียด
  • บทความพูดถึงแนวทางของ Rust แต่ต่างจากภาษาอื่น ๆ คือไลบรารีมาตรฐานของ Rust ไม่มี ฟังก์ชันจัดการคุกกี้ รวมอยู่ด้วย ที่จริงแล้วเรากำลังดูพฤติกรรมของ crate cookie จากภายนอก และมันก็มีตัวเลือกสำหรับ percent encoding แบบเดียวกับ Ruby ด้วย: https://docs.rs/cookie/0.18.1/cookie/

    • เป็นวิธีที่จองชื่อดี ๆ ได้ตั้งแต่เนิ่น ๆ แล้วกลายเป็น มาตรฐานโดยพฤตินัย
  • ในโปรโตคอล HTTP ดูเหมือนมีโปรโตคอลต่าง ๆ ฝังอยู่จริง ๆ สักหมื่นตัว เบราว์เซอร์กับเว็บเซิร์ฟเวอร์เพิ่มฟีเจอร์สารพัดเข้าไป แต่ละอย่างมีทั้งสเปกและสเปกโดยพฤตินัย แล้วทั้งหมดก็ถูกส่งผ่านใต้ร่ม HTTP อเนกประสงค์อันเดียวกัน
    ไคลเอนต์ไม่สามารถระบุได้ว่าเข้ากันได้กับเวอร์ชันไหนของบรรดาสิ่งนอกสเปกนับหมื่นนี้ และเซิร์ฟเวอร์ก็เช่นกัน เหตุผลที่อัปเกรดสเปกไม่ได้คือไคลเอนต์ที่เหลือไม่เข้าใจ และมันก็ไม่มีความเข้ากันได้ย้อนหลัง
    เลยเหลือแต่ความโกลาหลแบบสุ่ม ๆ ที่ไม่มีใครตกลงกันได้และแก้ไขไม่ได้ ไม่มีการเลิกใช้ตามแผนด้วย จึงต้องลากการตัดสินใจแย่ ๆ ในอดีตต่อไป

    • อุปกรณ์มิดเดิลแวร์ ห่วย ๆ ที่บล็อกโปรโตคอลที่มันไม่เข้าใจก็เป็นสาเหตุด้วย แนวคิดแบบ “บล็อกให้ล้มเหลวไว้ก่อนโดยค่าเริ่มต้นจะปลอดภัยกว่า” ทำให้จากนี้ไปตลอดกาล ทราฟฟิกของแอปพลิเคชันใหม่ทั้งหมดต้อง tunnel บน HTTP ถึงจะทำงานบนอินเทอร์เน็ตจริงได้
    • พูดตรง ๆ ตอนนี้ผมทำใจกับโลกแบบนี้ได้แล้ว และก็ไม่แน่ใจว่าผมชอบโลกที่มีการเลิกใช้ตามแผนมากกว่านี้ด้วยซ้ำ
    • ถ้าไม่อยากให้บริษัทผูกขาดกำหนดสเปกที่สะอาดเรียบร้อยแล้วบังคับเลิกใช้งานตามใจ ก็ต้องยอมรับ สภาพไร้รัฐบาล เป็นราคาที่ต้องจ่าย
  • ประมาณ 10 ปีก่อน ผมเคยทำโปรเจกต์ที่ใช้เซสชันบนคุกกี้ และต้องลำบากมากกับการดีบักว่าทำไมการยืนยันตัวตนใช้ได้ใน Safari แต่ใช้ไม่ได้ใน Chrome จำไม่ได้แน่ชัดว่าเป็นฝั่งไหน แต่เบราว์เซอร์หนึ่งจะไม่ตั้งค่าคุกกี้เลยถ้ารูปแบบไม่ถูกต้อง
    ไม่ได้ทำอะไรแปลกเป็นพิเศษ และเท่าที่จำได้ น่าจะเป็นความต่างระหว่าง - กับ _

    • ผมจำได้ว่าเหมือนจะมีความต่างเรื่องการแยกตัวพิมพ์ใหญ่เล็กระหว่าง Safari กับ Chrome อาจเป็น เฮดเดอร์ Set-Cookie ก็ได้
      เมื่อก่อนเคยใช้ camelCase ในคีย์คุกกี้ไม่ได้เพราะปัญหานี้
      ค้นหาแล้วก็ยังหาอิชชูที่ตรงเป๊ะไม่ค่อยเจอ
  • ตั้งแต่ไม่นานหลังคุกกี้ถูกนำมาใช้ ดูเหมือนว่าการใช้งานที่สมเหตุสมผลคือใส่แค่ opaque token เพื่อให้เซิร์ฟเวอร์รู้ว่าครั้งถัดไปยังเป็นไคลเอนต์เดิม แล้วเก็บอย่างอื่นทั้งหมดไว้ฝั่งเซิร์ฟเวอร์
    ผมไม่เข้าใจว่าทำไมการที่ไคลเอนต์โดยหลักการแล้วสามารถประมวลผลค่าที่เซิร์ฟเวอร์ไม่มีวันส่งมาให้ จึงเป็นปัญหา ก็แค่ไม่ส่งค่านั้นไป และไม่ต้องกังวลปริศนาแบบ “ถ้าส่งมันไปแล้วจะเกิดอะไรขึ้น?”

    • คุกกี้เป็น เทคโนโลยีเก่า เป็นหนึ่งในสิ่งแรก ๆ ที่ถูกนำมาใช้ในยุค 90 ตอนเว็บยังอายุน้อย และมีไอเดียแย่ ๆ ถูกทำซ้ำมาหลายรอบ
      ถึงอย่างนั้นก็ยังต้องใช้มันสำหรับการยืนยันตัวตน เพราะเป็นที่เดียวสำหรับเก็บ opaque token
  • การพาร์สเฮดเดอร์คุกกี้ นั้นเละเทะมาก “มาตรฐาน” ไม่ได้สะท้อนพฤติกรรมที่มีอยู่จริงในภาคสนาม รูปแบบที่แบ็กเอนด์เซิร์ฟเวอร์·ไลบรารี·เฟรมเวิร์กแต่ละตัวยอมรับก็แตกต่างกัน และเบราว์เซอร์ก็ทำอีกแบบหนึ่ง
    ถ้าคุณควบคุมทั้งฟรอนต์เอนด์และแบ็กเอนด์ได้ทั้งหมดก็อาจไม่ใช่ปัญหาใหญ่ แต่ทันทีที่ต้องเชื่อมสิ่งที่ต่างกันเข้าด้วยกัน สถานการณ์จะกลายเป็นเรื่องงี่เง่าอย่างรวดเร็ว

  • คุกกี้ดูเหมือนความยุ่งเหยิงขนาดใหญ่และซับซ้อน และในขณะเดียวกันก็แทบเปลี่ยนแปลงไม่ได้เพราะต้องรักษาความเข้ากันได้ย้อนหลัง ในกรณีแบบนี้ ผมคิดว่าการสร้างกลไกใหม่ที่แยกออกมาต่างหากโดยสิ้นเชิงน่าจะถูกต้องกว่า
    เช่น สามารถกำหนดสเปกกลไกใหม่อย่าง NewCookie แล้วออกแบบใหม่ตั้งแต่ต้นให้ทำงานสอดคล้องกันได้ ใส่มาตรการความปลอดภัยสมัยใหม่ไว้ในตัว รวมถึงสเปกที่เข้มงวดขึ้นและการรองรับ Unicode ที่ถูกต้องด้วย

    • ที่พูดถึง NewCookie นั้นน่าสนใจ เพราะจริง ๆ แล้วเคยมี เฮดเดอร์ Set-Cookie2 ที่ถูกเลิกใช้ไปแล้ว: https://stackoverflow.com/q/9462180/3474615
    • NewCookie โดยคร่าว ๆ แล้วเทียบได้กับ Local Storage ของเบราว์เซอร์
      อย่างน้อยก็ในบาง use case และแน่นอนว่าไม่ได้ผสานกับเฮดเดอร์โดยตรง
    • ปัญหาหลักน่าจะอยู่ที่คุกกี้ผูกติดกับ การติดตาม ลึกเกินไป ถ้าตอนนี้พยายามสร้างคุกกี้ที่ดีกว่า ก็มีโอกาสสูงที่จะถูกนักสนับสนุนความเป็นส่วนตัวขวางไว้ เพราะพวกเขาไม่อยากให้แนวคิดแบบนั้นมีอยู่เลย
      เพราะคุกกี้มีอยู่แล้ว เราจึงถูกผูกไว้กับคุกกี้
    • ที่ที่ปลอดภัยที่สุดสำหรับเก็บสถานะฝั่งไคลเอนต์คือ DOM และ URL แม้จะไม่ครอบคลุมทุก use case แต่ก็ครอบคลุมกรณีอย่างพื้นที่ที่ผู้ใช้คลิกลิงก์อนุมัติล่วงหน้าในอีเมลได้
      ผมใช้เวลาหนึ่งเดือนเต็มไล่ตามปัญหาที่ iOS Safari กินคุกกี้ของโดเมนที่ลูกค้าควบคุมเองแบบสุ่ม ๆ ไม่เคยเห็นสถานะเซสชันหายไปแบบนี้ในโดเมนอย่าง Google, Twitter, Facebook
    • ชื่อควรดีกว่า NewCookie ข้อเสนออย่าง SuperCookie, UltraCookie, BetterCookie ก็เป็นไปได้
      ถ้าพูดจริงจังกว่านั้น ควรเลี่ยง คำว่า cookie และตั้งชื่อเป็นอย่างอื่นไปเลย คำว่า cookie มีภาระพ่วงติดมามากเกินไป
  • ผู้เขียนเริ่มจากการใส่ผลลัพธ์ของ JSON.stringify ลงในคุกกี้ แต่กลับน่าแปลกที่ไม่ใช่เพราะมีคนใส่ เซมิโคลอน ไว้ใน JSON ที่ถูกแปลงเป็นสตริง
    ปัญหาส่วนใหญ่รอบคุกกี้ดูเหมือนจะเกิดตอนพยายามใส่อินพุตผู้ใช้แบบอิสระลงในคุกกี้ ไม่ควรทำแบบนั้น ถ้าใช้แค่ สตริง ASCII ตัวอักษรและตัวเลขความยาวคงที่ เหมือนที่ใช้กับโทเคนยืนยันตัวตน ก็จะไม่เป็นไร

  • เห็นด้วยว่ามันเป็นทุ่งกับระเบิดพอสมควร
    วิธีเลี่ยงในฐานะนักพัฒนาคือเข้ารหัสค่าด้วย Base64 แบบปลอดภัยสำหรับ URL แล้วจะได้ค่าไบต์ดิบ และสามารถใช้ representation ภายในตามต้องการได้ อย่างไรก็ตาม ตามที่บทความบอก ก็ไม่ได้ควบคุมได้ 100% อยู่ดี เพราะมันเป็น user agent และก็ควรเป็นเช่นนั้น
    อยากให้ user agent มากขึ้นเลือก ปฏิบัติตามมาตรฐาน แทน “ไบต์บนสายกับการสวดภาวนา” คำตอบ 400 ในภาพหน้าจอเป็นคำตอบที่ถูกต้องตามสเปก ถ้าเฮดเดอร์เป็น UTF-8 ตั้งแต่แรก หรือเริ่มจาก ASCII แล้วภายหลังค่อยอนุญาต UTF-8 ก็คงจะดีกว่า อย่างไรก็ตาม อย่างแรกทำได้ยากในเชิงเหตุและผล ส่วนอย่างหลังก็ยังอาจก่อปัญหาได้ เพราะทำให้ค่าที่เดิมผิดกฎหมายกลายเป็นถูกกฎหมาย

    • เมื่อพูดว่า Base64 แบบปลอดภัยสำหรับ URL ต้องระบุให้ชัดว่าหมายถึงอะไรกันแน่ การเข้ารหัส base64url ไม่เข้ากันกับการเอา URL encoding มาบวกกับ base64 ในประมาณ 3% ของกรณี ซึ่งตอนพัฒนามักพลาดได้ง่าย แต่ใน production จะต้องระเบิดแน่นอน
    • ในค่าคุกกี้สามารถใส่อักขระ =, /, + ได้ ดังนั้นใช้ การเข้ารหัส Base64 มาตรฐานก็ได้ :)
  • บทความล้อเลียน กฎของ Postel แต่ถ้าฝั่งที่ตั้งคุกกี้ส่งอย่างระมัดระวังตั้งแต่แรก บทความแบบนี้ก็คงไม่จำเป็น

    • สมควรถูกล้อแล้ว กฎของ Postel เป็นไอเดียที่แย่มากและสร้างทุ่งกับระเบิดไว้ทั่วทุกที่
      บางครั้งกับระเบิดนั้นไม่ได้เป็นแค่บั๊กธรรมดา แต่กลายเป็น ช่องโหว่ความปลอดภัย ขนาดใหญ่ด้วย
      ถ้าไคลเอนต์ส่งข้อมูลที่ไม่ตรงตามสเปก นั่นคือบั๊กและควรแก้ไข เซิร์ฟเวอร์ไม่ควรต้องเดาเจตนาแล้วรับไว้จนกลายเป็นเรื่องปกติเด็ดขาด
    • ปัญหาของกฎของ Postel อยู่ตรงที่ผู้ส่งไม่เคยระมัดระวังจริง ๆ รายละเอียดพฤติกรรมใดที่ผู้รับส่วนใหญ่ยอมรับ สุดท้ายผู้ส่งก็จะนำไปใช้