2 คะแนน โดย GN⁺ 2024-05-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • checksum database ของ Go และ public module proxy สามารถรับแม้แต่รีโพซิทอรีที่ไม่มีโค้ด Go ได้ ทำให้เกิดเส้นทางสำหรับนำข้อมูลจาก Git repository ตามอำเภอใจไปเก็บบนโครงสร้างพื้นฐานของ Go และดาวน์โหลดกลับมาได้อีกครั้ง
  • คำขอ sum.golang.org/lookup/$module@$version จะไปดึงจาก เซิร์ฟเวอร์ต้นทาง เมื่อพบเวอร์ชันของโมดูลที่ยังไม่ถูกบันทึก และในกระบวนการนี้ zip ของรีโพซิทอรีนั้นจะถูกให้บริการบน proxy.golang.org ด้วย
  • พบรีโพซิทอรี Ruby ของ Homebrew และ fork ของรีโพซิทอรี Rust อยู่ใน checksum database และการทดลองลงทะเบียนทั้งโมดูล Go ใหม่และรีโพซิทอรีที่ไม่มีไฟล์ Go เลยด้วย pseudo-version ก็สำเร็จ
  • แม้ module zip จะมีข้อจำกัด สูงสุด 500 MiB ทั้งแบบบีบอัดและไม่บีบอัด แต่ก็ยังใหญ่พอสำหรับการหลบเลี่ยงข้อจำกัดการดาวน์โหลดบนเครื่องนักพัฒนาหรือ CI/CD การเก็บเพย์โหลด และการทำ C2
  • จากพาธเฉพาะของ sum.golang.org ราว 1.59 ล้านรายการ มีพาธ GitHub ราว 1.51 ล้านรายการ คิดเป็นประมาณ 95% สะท้อนทั้งการพึ่งพา GitHub ของระบบนิเวศ Go และความเป็นไปได้ในการนำ public proxy ไปใช้ในทางที่ผิด

รีโพซิทอรีที่ไม่ใช่ Go ที่พบใน Go checksum database

  • ระหว่างตรวจดู checksum database ของ Go พบว่า github.com/homebrew/homebrew-core ปรากฏในตาราง modules จำนวนมากผิดปกติ
    • github.com/homebrew/homebrew-core: 39,438 รายการ
    • github.com/Homebrew/homebrew-core: 30,896 รายการ
    • github.com/concourse/concourse: 25,372 รายการ
    • github.com/openshift/release: 24,065 รายการ
    • github.com/cilium/cilium: 22,138 รายการ
  • รีโพซิทอรี Homebrew เป็นที่รู้กันว่าใช้ Ruby และไม่พบ go.mod หรือไฟล์ซอร์ส Go ทั้งในรีโพซิทอรีและไฟล์ที่โคลนมา
  • ความแตกต่างของตัวพิมพ์เล็กพิมพ์ใหญ่สามารถอธิบายได้ด้วยกฎ case encoding ในเอกสารของ Go
    • ตัวอักษรพิมพ์ใหญ่จะถูกเข้ารหัสเป็น ! ตามด้วยตัวพิมพ์เล็กที่สอดคล้องกัน ทำให้สามารถเก็บทั้ง example.com/M และ example.com/m พร้อมกันได้แม้บนระบบไฟล์ที่ไม่แยกตัวพิมพ์เล็กพิมพ์ใหญ่
  • github.com/Edu4rdSHL/rust-headless-chrome ก็ปรากฏใน checksum database เช่นกัน ทั้งที่เป็น fork ของรีโพซิทอรี Rust ที่ไม่เกี่ยวกับ Go

วิธีที่ /lookup ดึงรีโพซิทอรีมา

  • ตาม Go Modules Reference ในเอกสารโมดูล Go คำสั่ง Go จะดึงข้อมูลเรคคอร์ดจากเอ็นด์พอยต์ /lookup ก่อนเมื่อทำการสอบถาม checksum database
  • หากเวอร์ชันของโมดูลยังไม่ได้ถูกบันทึกลงในล็อก checksum database จะพยายามดึงโมดูลนั้นจาก เซิร์ฟเวอร์ต้นทาง ก่อนตอบกลับ
  • รูปแบบของเอ็นด์พอยต์คือ $base/lookup/$module@$version
    • จะคืนหมายเลขเรคคอร์ดในล็อกสำหรับ $version ของ $module, บรรทัด go.sum และคำอธิบายต้นไม้ที่มีลายเซ็น
  • เมื่อตรวจสอบ pseudo-version ของ github.com/homebrew/homebrew-core ก็ได้รับ checksum record และแฮชของ go.mod กลับมา
  • หากรีโพซิทอรีไม่มี version tag ระบบจะใช้กฎ pseudo-version ของ Go

การทดลองลงทะเบียนโมดูล Go ใหม่

  • ผู้เขียนสร้างโมดูล Go ใหม่ github.com/gdbinit/fluxmatter แล้วตรวจสอบการลงทะเบียนผ่านคำขอ lookup
  • การสอบถาม @latest ตอบกลับด้วยข้อผิดพลาดว่าไม่ใช่ canonical version
    • bad request: version "latest" is not canonical
  • การสอบถาม @v0.0.0 ตอบกลับด้วยข้อผิดพลาดว่าไม่รู้จัก revision ดังกล่าว
    • not found: ... invalid version: unknown revision v0.0.0
  • แต่เมื่อลองซิงก์ checksum database อีกครั้งแล้วสอบถามใหม่ พบว่าโมดูลถูกลงทะเบียนไปแล้ว
    • github.com/gdbinit/fluxmatter|v0.0.0-20240524163826-a7e64ffd69f2|2024-05-24T16:40:51.203837Z
  • proxy.golang.org/github.com/gdbinit/fluxmatter/@latest ส่งคืน pseudo-version และข้อมูลต้นทางจาก GitHub และยังสามารถดาวน์โหลดไฟล์ zip ของเวอร์ชันนั้นและตรวจสอบการบีบอัดได้
  • ในการ seed ครั้งแรก ไม่จำเป็นต้องระบุเวอร์ชันที่ถูกต้องอย่างแม่นยำ แค่มี lookup query ที่ใส่ค่าในรูปแบบเหมือนพาธโมดูลและเวอร์ชันก็เพียงพอ

รีโพซิทอรีที่ไม่มีโค้ด Go ก็ถูกนำขึ้น public proxy ได้

  • การทดลองแบบเดียวกันกับรีโพซิทอรี github.com/gdbinit/readmem ที่ไม่มีโค้ด Go เลยก็สำเร็จ
  • คำขอ lookup ตอบกลับว่าไม่รู้จัก revision v0.0.0 แต่ใน checksum database กลับถูกลงทะเบียนเป็น pseudo-version
    • github.com/gdbinit/readmem|v0.0.0-20131006075740-407cb0a56933|2024-05-24T16:45:35.88456Z
  • @latest ของ proxy.golang.org ส่งคืน pseudo-version ของรีโพซิทอรีนั้นพร้อมข้อมูลต้นทางจาก Git
  • ภายใน zip ที่ดาวน์โหลดมาไม่มีไฟล์ Go แต่มี Entitlements.plist, README, ไฟล์โปรเจกต์ Xcode, main.c เป็นต้น
  • แม้การทดลองนี้จะใช้ รีโพซิทอรี GitHub แต่หากเป็น VCS ที่ใช้งานได้ ก็อาจทำได้กับโฮสติงอื่นเช่นกัน

การพึ่งพา GitHub และข้อจำกัดด้านขนาด

  • จำนวนพาธเฉพาะใน checksum database คือ 1,591,375 รายการ และในนั้น github.com% มี 1,515,957 รายการ
  • ประมาณ 95% ของพาธเฉพาะถูกโฮสต์บน GitHub {p:95}
  • ตัวเลขนี้เป็นสถิติดิบที่ยังไม่ได้ตัด fork หรือสิ่งที่ไม่ใช่โค้ด Go จริงออก
  • module zip ของ Go อยู่ภายใต้ File path and size constraints
    • ไฟล์ module zip มีขนาดสูงสุด 500 MiB
    • ขนาดรวมของไฟล์เมื่อไม่บีบอัดก็สูงสุด 500 MiB
    • ไฟล์ go.mod มีขนาดสูงสุด 16 MiB
    • ไฟล์ LICENSE ก็มีขนาดสูงสุด 16 MiB
  • ข้อจำกัดเหล่านี้มีไว้เพื่อลดความเสี่ยงจาก การโจมตีแบบปฏิเสธการให้บริการ ต่อผู้ใช้, proxy และส่วนอื่นของระบบนิเวศโมดูล
  • แต่ขนาด 500 MiB ก็ยังถือว่าใหญ่พอเมื่อมองในแง่ความเป็นไปได้ของการนำไปใช้ในทางที่ผิด

สถานการณ์การนำไปใช้ในทางที่ผิดที่เป็นไปได้

  • public Go proxy อาจถูกใช้เพื่อ หลบเลี่ยงข้อจำกัดปลายทางของการดาวน์โหลด บนเครื่องนักพัฒนาหรือเซิร์ฟเวอร์ CI/CD
    • สมมติว่าไม่มี private GOPROXY
    • มัลแวร์สามารถอัปโหลดเพย์โหลดขึ้นรีโพซิทอรี แล้วค่อยดาวน์โหลดจาก proxy เมื่อจำเป็น
    • ต่อให้ซอร์สต้นทางหายไป ก็อาจเหลือเพียงร่องรอยเล็กน้อยใน checksum database entry
  • การทำ DoS กับ proxy.golang.org อาจลงมือได้ไม่ง่าย
    • ผู้โจมตีสามารถร้องขอให้ proxy ดาวน์โหลดรีโพซิทอรี Git ตามอำเภอใจได้
    • แนวทางที่เป็นไปได้คือรวบรวม URL ของ GitHub จำนวนมาก แล้วส่งคำขอจำนวนมากไปยัง API ของ lookup
    • แม้จะไม่รู้รายละเอียดฝั่งเซิร์ฟเวอร์ แต่ก็อาจมีการจำกัดการประมวลผลพร้อมกัน เช่น work queue
    • ฝั่ง GitHub เองก็อาจมีการป้องกันแบนด์วิดท์ทำงานอยู่
    • ยังมีความเป็นไปได้ของ DoS ที่มุ่งเป้าไปยังพื้นที่เก็บข้อมูล แต่ยังเป็นเพียงการคาดการณ์
  • สามารถสร้าง C2(command and control) บน proxy.golang.org ได้ค่อนข้างง่าย
    • ใช้คำขอ @latest เพื่อหาเวอร์ชันล่าสุดของโมดูลที่ต้องการ
    • เพย์โหลดอาจเป็นไฟล์ธรรมดา หรือซ่อนไว้ใน go.mod หรือไฟล์ซอร์ส Go ก็ได้
    • หากไม่ต้องการใช้รีโพซิทอรีเดียว อาจใช้ module DGA ได้

โฟลว์การดาวน์โหลดของ C2

  • หาก implant ต้องการรับคำสั่ง ก็เพียงทำตามขั้นตอนต่อไปนี้
  • โฟลว์นี้เรียบง่ายมากจนสามารถเขียนได้ด้วยโค้ด Go ไม่ถึง 300 บรรทัด

บทสรุปและคำถามที่ยังค้างอยู่

  • checksum database และ proxy ของ Go สามารถดึงและเก็บโมดูลที่ยังไม่ถูกบันทึกจากเซิร์ฟเวอร์ต้นทางได้ตามกระบวนการที่มีการจัดทำเอกสารไว้
  • สถานะปัจจุบันอาจยังไม่ดูเป็นปัญหาร้ายแรงของโครงสร้างพื้นฐาน Go แต่ก็ถูกนำไปใช้ในทางที่ผิดได้ง่ายและยังมีช่องให้ปรับปรุง
  • อาจมีเหตุผลที่มีการบันทึกไว้ในเอกสารหรือไม่เปิดเผยว่าเหตุใดจึงอนุญาตให้รีโพซิทอรีที่ไม่ใช่โค้ด Go ขึ้นไปอยู่บน proxy และ checksum database ได้
  • หากต้องการตรวจสอบว่ามีผู้ใดนำสิ่งนี้ไปใช้ในทางที่ผิดแล้วหรือไม่ จำเป็นต้องสำรวจรีโพซิทอรีเฉพาะราว 1.6 ล้านรายการ และ entry ราว 22 ล้านรายการ ตามฐานข้อมูลโลคัลล่าสุด
  • ยังเป็นคำถามเปิดอยู่ว่าทำไมบางโปรเจกต์ที่ไม่ใช่ Go แต่ถูกต้องตามปกติจึงอยู่ใน database

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

 
GN⁺ 2024-05-26
ความเห็นจาก Hacker News
  • บริการออนไลน์ ที่ให้ผู้ใช้อัปโหลดข้อมูลและทำให้ข้อมูลนั้นมองเห็นได้แบบสาธารณะ สุดท้ายก็มักจะถูกนำไปใช้เป็น command-and-control, ละเมิดลิขสิทธิ์, และโฮสต์ CSAM
    ยิ่งเป็นบริการที่บล็อกได้ยากเพราะมีการใช้งานสำคัญนอกเหนือจากการโฮสต์ไฟล์ก็ยิ่งเป็นแบบนั้น โดยเรื่องนี้เคยเกิดขึ้นแล้วกับ Twitter[1], Telegram[2], และโครงสร้างพื้นฐานคีย์ PGP[3] ส่วนเป้าชัด ๆ อย่าง GitHub ก็ไม่ต้องพูดถึง
    [1] https://pentestlab.blog/2017/09/26/command-and-control-twitt...
    [2] https://www.blazeinfosec.com/post/leveraging-telegram-as-a-c...
    [3] https://torrentfreak.com/openpgp-keyservers-now-store-irremo...

    • Gmail, Google Groups, Google Drive และ Gchat ก็เคยเป็นแบบเดียวกัน และข้อมูลที่เก็บไว้ก็ไม่จำเป็นต้องเป็นสาธารณะด้วยซ้ำ
      ในกรณีของ Gmail มีการแจก credentials ให้ไปล็อกอิน แล้วให้อ่านไฟล์แนบที่อัปไว้ผ่าน IMAP
      ผมเคยอยู่ทีม Google SAD-SRE (Spam, Abuse, Delivery)
    • PyPI ก็สามารถใส่ ไฟล์ที่ไม่ใช่ Python ลงในแพ็กเกจได้ เลยดูเหมือนจะใช้ในลักษณะนี้ได้ง่าย
      จะเข้ารหัสไฟล์เป็น Base64 แล้วใส่ไว้ในสตริงของโค้ด Python ก็ยังได้
    • ไม่แน่ใจว่ามีมาก่อนหรือยัง แต่เป้าที่ดู less obvious กว่าน่าจะเป็น HuggingFace
  • ผมเป็น Googler และนี่เป็นความเห็นส่วนตัว อีกทั้งก็ไม่ได้เชี่ยวชาญด้านนี้
    หวังว่าทีม Go จะร่วมมือกับฝั่ง GCP และ Drive เพราะการโฮสต์ไฟล์อันตรายเป็นปัญหาที่ Google รับมืออยู่ตลอด
    มันไม่ได้ต่างจาก endpoint อื่น ๆ ที่ Google อนุญาตให้คนใส่ข้อมูลตามอำเภอใจอยู่แล้วมากนัก

    • ผมเป็นอดีต Googler และไม่ได้รู้จักทีม Go Dev Tools มากนัก แต่ในบรรดาบริษัทใหญ่ที่ผมเคยทำงานหรือได้ยินจากเพื่อนสนิทมา Google แทบจะทำ การประสานงานภายใน แบบนี้ได้ดีที่สุดแล้ว
      Google เก่งมากเรื่องมีทีมกลางมาดูแลโครงสร้างพื้นฐานและแชร์ให้ทั้งองค์กรใช้ ยกเว้นแอปแชตก็อีกเรื่อง แต่ถ้าเดาล้วน ๆ ทีม Go ก็น่าจะใช้ blob storage ภายใน และน่าจะมีทีมโครงสร้างพื้นฐานภายในที่จัดการการรับมือการใช้งานในทางที่ผิดและสแกนไฟล์แบบอัตโนมัติอยู่แล้ว
  • ใน PyPI ก็มี โปรเจกต์ที่ไม่ใช่ Python อยู่ไม่น้อย
    Python จำเป็นต้องรองรับการแจกจ่าย wheel ซึ่งเป็นไบนารีที่คอมไพล์แล้ว เพราะผู้ใช้อาจคอมไพล์โค้ดไลบรารีเองไม่ได้
    โค้ดแบบนั้นมักเขียนด้วย C แต่ก็ทำด้วย Golang[1] ได้เหมือนกัน และแม้จะหาตัวอย่างไม่เจอ ผมคิดว่าเคยเห็นมันถูกใช้แจกจ่ายแอปพลิเคชัน ไม่ใช่แค่ไลบรารีด้วย
    การเขียนแอปด้วย C แล้วอัปขึ้น PyPI จากนั้นให้ผู้ใช้ติดตั้งด้วย pip install ก็ถือว่าเจ๋งดี
    [1] https://github.com/popatam/gopy_build_wheel_example

    • ต่อให้ใส่ข้อกำหนดว่าต้องใช้ Python ก็เถอะ ถ้าคนจะทำแบบมุ่งร้ายจริง ๆ ก็คงแค่ใส่ โค้ดสตับ Python ขั้นต่ำมาให้ก็พอหรือเปล่า
      บน Linux ก็เหมือนกับว่า ls เขียนด้วย Python ไปแล้ว ดังนั้นผมว่าคงไม่ควรเล่นเกมแบบนี้
    • ก่อนที่ pip จะเริ่มบังคับว่าต้องอยู่ในสภาพแวดล้อมอย่าง venv/virtualenv/pipenv/pyenv ถึงจะดาวน์โหลดแพ็กเกจได้ การใช้งานลักษณะนี้น่าจะมีประโยชน์กว่านี้มาก
    • มีทั้ง pip install cmake และไบนารีปิดซอร์สก็ทำได้แบบ pip install nvidia-cudnn-cu12
    • ช่วงหลังผมใช้ PyPI กับ เครื่องมือที่ไม่ใช่ Python อย่าง FFmpeg และ Eigen เยอะมาก
      นี่เป็นหนึ่งในเหตุผลที่ทำให้ผมเลิกใช้ Homebrew ได้หมดจด
  • อาจจะเป็นความคิดไร้เดียงสา แต่ผมไม่เข้าใจว่ามันต่างจากการอัปไฟล์ขึ้น GitHub repository อย่างไร
    หรือความต่างมีแค่ว่า GitHub ต้องสร้างบัญชี? บน GitHub ก็เก็บข้อมูลอะไรก็ได้เหมือนกัน และก็ไม่ได้มีลิมิต 500MB ด้วย

    • GitHub มี การจำกัดคำขอ สำหรับคำขอแบบไม่ระบุตัวตนค่อนข้างเข้มงวด
  • ระบบโมดูลของ CUE กำลังออกมาอย่างเป็นทางการในที่สุด และ MVS ก็คล้ายกับของ Go แต่สร้างอยู่บน โครงสร้างพื้นฐาน OCI
    ถ้าสนใจระบบจัดการ dependencies นี่คือลิงก์ที่น่าอ่าน
    proposal: https://github.com/cue-lang/proposal/tree/main/designs/modul...
    custom registry: https://cuelang.org/docs/tutorial/working-with-a-custom-modu...
    road map: https://github.com/orgs/cue-lang/projects/10/views/8
    ตั้งแต่ 0.9.0-alpha-5 เป็นต้นไป โมดูลถูกเปิดใช้เป็นค่าเริ่มต้น: https://github.com/cue-lang/cue/releases/tag/v0.9.0-alpha.5
    ใน Go Sum โปรเจกต์ Trillian รองรับ transparency log อยู่เบื้องหลัง: https://github.com/google/trillian
    CUE มีแผนจะอาศัยตัวเลือกของ OCI อย่าง attestation ด้วย

    • ไม่เข้าใจว่าเรื่องนี้เกี่ยวอะไรกับบทความที่ลิงก์ไว้
  • ลองเล่นกับไอเดียที่อาศัย golang proxy และ sumdb หรือพูดอีกอย่างคือใช้ในทางที่ผิด เพื่อสร้าง transparent log ฟรีสำหรับ checksum ของ URL ใดก็ได้ตามใจ
    https://getsum.pub/

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

    • ดูเหมือนจะไม่ได้พลาดอะไร
      ข่าวใหม่ตรงนี้น่าจะมีแค่ว่า public proxy ที่ไม่มีมาตรการความปลอดภัย รับสิ่งที่ให้มันพร็อกซีแล้วนำมาเผยแพร่ต่อแบบสาธารณะโดยไม่มีมาตรการความปลอดภัยเช่นกัน
      ในบทความบอกว่าเครือข่ายที่มีการเฝ้าระวังบางแห่งอาจเชื่อถือ URL ของ golang proxy มากกว่า URL เว็บทั่วไป เลยอาจใช้หลบตัวกรองตามชื่อเสียงได้ แต่ก็มีวิธีแบบนี้อยู่แล้วหลายแบบ และวิธีนี้ก็ดูไม่ได้พิเศษอะไร
  • นอกเรื่องหน่อย แต่เห็นโดเมนแล้วมุกคำพ้องมันชวนรู้สึกไม่ค่อยดี เลยลองเข้าไปดู put.as และก็เป็นอย่างที่คาดไว้โดยรวม

    • https://put.as/ ค่อนข้าง NSFW
    • ผมพลาดเปิดมันที่ทำงาน
    • มันเป็นคำพหูพจน์ในภาษาสเปนด้วย แต่ว่า…
  • เป็นประเด็นที่รู้จักกันอยู่แล้ว: https://github.com/golang/go/issues/31866

    • การแก้นั้นคงช่วยกันความผิดพลาดโดยไม่ตั้งใจได้ แต่ถ้าคนตั้งใจจะทำอยู่แล้ว แค่เพิ่มไฟล์ .mod กับ .go ไว้ที่รูทก็พอไม่ใช่หรือ?
    • ไม่แปลกใจเลยที่ Marwan อยู่ใน issue นี้
      เขากับ Aaron เป็นคนสร้าง Athens และเท่าที่ผมรู้ Marwan เป็นคนเขียน implementation ของ Go download protocol ตัวแรกที่กลายมาเป็นพื้นฐานของ Athens
      สิ่งที่น่าสนใจใน issue นี้คือ Athens ใช้คำสั่ง go mod download -json ที่ถูกพูดถึงก่อนหน้านี้ว่าเป็นการตรวจสอบล่วงหน้าสำหรับการยืนยันโมดูลอยู่แล้ว
      โดยทั่วไป ถ้ารีโพผ่านเป็นโมดูลที่คำสั่ง Go module เข้าใจได้ Athens ก็จะให้บริการมัน
      ถ้าจะพูดแบบตรงกว่าเดิม ก็ต้องสามารถสร้างเวอร์ชันโมดูล, pseudo-version และ +incompatible ได้ และทั้งโมดูลนั้นกับ dependency ของมันต้องสร้าง checksum ที่ถูกต้องได้
      checksum ของโมดูลตอนนี้เกี่ยวข้องกับการรวม .mod, ทุกไฟล์ และแต่ละ dependency แบบเรียกซ้ำ
      เพราะงั้นอย่างที่ผู้เขียนบทความบอก แค่มีโปรแกรม Go พื้นฐานก็มีพื้นที่สำหรับไฟล์ตามอำเภอใจได้มากอยู่แล้วตามการออกแบบ
  • W3C ได้วางรากฐานไว้ให้ทุกอย่างบนเว็บสามารถถูกแคชแบบเข้มข้นได้ แต่การมี generic proxy cache น้อยขนาดนี้กลับดูแปลก
    เป็นไปได้ไหมว่าผู้เผยแพร่ส่ง Cache-Control: max-age สั้น ๆ หรือ Vary: Cookie ทั้งที่ไม่จำเป็นอยู่ตลอด?
    หรือ ISP ต้องจ่ายค่าทรานซิตมากกว่าค่า peering มากเกินไป?

    • โดยทั่วไปไม่มีวิธีรับประกันได้ว่าแคชไม่ได้ดัดแปลงเนื้อหา
      ตัวอย่างเช่น ในไซต์ที่ไม่ใช้ HTTPS พร็อกซีของ ISP อาจแทรกโฆษณาได้
      การดาวน์โหลดซอฟต์แวร์มักมีลายเซ็นและ checksum แต่เนื้อหาทั่วไปมักไม่มี