captcha หายไปจากหน้าเว็บ — ไล่แก้ 4 ชั้น กว่าจะเจอตัวจริงที่ Cloudflare CSP

ถ้าคุณดูแลเว็บให้คนอื่น คุณน่าจะคุ้นกับข้อความทำนองนี้ — “พี่ครับ ฟอร์มติดต่อในเว็บส่งไม่ได้” จบ. ไม่มีรายละเอียด ไม่มี error ไม่มีภาพ. พอผมเปิดเข้าไปดู ก็เจอปุ่มส่งขึ้นข้อความแดงๆ ว่า captcha ยืนยันไม่ผ่าน ทั้งที่ผมไล่เช็คการตั้งค่าแล้ว มันถูกหมดทุกอย่าง

เคสนี้กินเวลาผมไปหลายชั่วโมง เพราะปัญหามันไม่ได้มีชั้นเดียว — มันซ้อนกันอยู่ 4 ชั้น และตัวที่เป็นต้นเหตุจริงๆ ดันเป็นสิ่งที่ผมแทบไม่ได้เหลียวมองตั้งแต่แรก บทความนี้ผมเลยอยากเล่าให้ฟังว่ามันหลอกผมยังไง และเบาะแสเล็กๆ อะไรที่พาผมไปเจอตัวจริง เผื่อวันหนึ่งคุณเจอ captcha ไม่ขึ้น บนเว็บแล้วงงว่ามันหายไปไหน จะได้ไม่ต้องเสียเวลาเท่าผม

อาการ: ทุกอย่างดูถูก แต่กล่อง captcha ไม่ยอมโผล่

เว็บนี้ใช้ปลั๊กอินฟอร์มยอดนิยมตัวหนึ่ง คู่กับระบบ captcha ของ Cloudflare ที่ชื่อ Turnstile (ตัวที่ไม่ต้องให้คนกดเลือกรูปสะพาน-รถเมล์ให้เมื่อยมือ) ลูกค้าตั้งค่าเองมาก่อน แล้วมันใช้ไม่ได้

สิ่งแรกที่ผมทำคือไล่เช็คของพื้นฐานทีละข้อ — key ถูกไหม ประเภท captcha ตั้งตรงไหม ฟอร์มเรียกใช้จริงหรือเปล่า ทุกอย่างผ่านหมด แต่พอเปิดหน้าจริง กล่อง captcha มันไม่โผล่ขึ้นมาเลย ฟอร์มข้ามจากปุ่มอัปโหลดรูปไปปุ่มส่งเลย เหมือนไม่มีตัวตน

ชั้นที่ 1: ค่าเก่าที่ค้างอยู่ในฐานข้อมูล

พอขุดเข้าไปดูค่าในฐานข้อมูล ผมก็เจอชั้นแรก — มีค่า global ตัวหนึ่งที่ตั้ง “บังคับใช้ captcha” ไว้ แต่ดันชี้ไปที่ captcha คนละเจ้า (ตัวเก่าที่เคยใช้) ผลคือทุกครั้งที่ฟอร์มถูกสร้างขึ้นมา มันจะไปดึง captcha ตัวเก่ามาแทน แล้วลบตัวใหม่ทิ้ง

ผมแก้ค่าตรงนั้นให้ชี้มาที่ Turnstile ให้ถูก คิดในใจว่า “เออ น่าจะจบแล้วล่ะ” — แต่เปล่าเลย หน้าเว็บยังเหมือนเดิมเป๊ะ

ชั้นที่ 2: แคชที่ผมลบไม่ได้ (เพราะผมไม่ใช่เจ้าของไฟล์)

ที่หน้าเว็บไม่เปลี่ยน เพราะมันโดนปลั๊กอินแคชเก็บ HTML หน้าเก่าไว้ ผมเลยสั่งล้างแคช — แต่ดันเจอด่านแปลกๆ คือ ลบไฟล์แคชไม่ได้ ขึ้น permission denied

สาเหตุคือไฟล์แคชพวกนั้นมีเจ้าของเป็น user ของตัวเว็บ (ที่ php สร้างไฟล์ขึ้นมา) แต่ตอนผม SSH เข้าไปสั่งลบ ผมล็อกอินด้วยอีก user หนึ่งที่ไม่มีสิทธิ์แตะไฟล์ของเขา — สั่ง rm ก็ไม่ได้ สั่งผ่านคำสั่งของปลั๊กอินก็ลบไม่ขาด

ทางออกที่ผมใช้คือยืมมือ php เอง — เขียนสคริปต์เล็กๆ ฝากไว้ให้เว็บรัน พอมันรันในฐานะ user ตัวเดียวกับที่สร้างไฟล์ ก็ลบแคชหน้านั้นได้สบาย เสร็จแล้วเอาสคริปต์ออก ตรงนี้ผมเสียเวลาไปพอสมควรกว่าจะนึกออกว่าปัญหาคือเรื่องสิทธิ์ ไม่ใช่เรื่องคำสั่งผิด

ชั้นที่ 3: ตัวเร่งความเร็วเว็บที่เร่งจนพัง

พอแคชหายจริง หน้าเว็บส่ง HTML ที่ถูกต้องออกมาแล้ว — โค้ดของ captcha มาครบ สคริปต์โหลดครบ แต่กล่องก็ยัง… ไม่โผล่อยู่ดี

ตัวการชั้นนี้คือปลั๊กอินเร่งความเร็ว (cache + optimize) ที่เปิดฟังก์ชัน “เลื่อนการโหลด JavaScript” กับ “รวมไฟล์ JS” ไว้ ปกติมันดีนะ ทำให้เว็บโหลดเร็วขึ้น — แต่สคริปต์ของ Cloudflare ต้องรันตามลำดับที่ถูกต้องถึงจะ “วาด” กล่อง captcha ออกมา พอโดนเลื่อนการโหลด จังหวะมันเพี้ยน กล่องเลยไม่ถูกสร้าง

ผมพิสูจน์ด้วยการลองปิดตัวเร่งความเร็วชั่วคราว แล้วเทียบโค้ดที่ออกมา — เห็นชัดเลยว่าตอนเปิด สคริปต์โดนยัด attribute “เลื่อนโหลด” เพิ่มเข้ามา ส่วนตอนปิดมันสะอาด ผมเลยปิดการเลื่อนโหลดกับการรวมไฟล์ JS แล้วล้างแคชอีกรอบ

เบาะแสที่พลิกเกม: “แผนที่ก็หายไปด้วย”

ตรงนี้แหละครับที่เป็นจุดเปลี่ยน ระหว่างที่ลูกค้าช่วยทดสอบ เขาพิมพ์มาประโยคหนึ่งที่ผมเกือบมองข้าม — “แผนที่ Google ในหน้าติดต่อก็หายไปด้วยนะ”

หยุดคิดแป๊บ. captcha กับแผนที่ Google ไม่ได้เกี่ยวอะไรกันเลย — คนละระบบ คนละเจ้า แต่ทำไมมันพังพร้อมกัน? สิ่งเดียวที่ทั้งสองอย่างมีเหมือนกันคือ ทั้งคู่เป็น iframe ที่ฝังมาจากเว็บภายนอก (captcha มาจากเซิร์ฟเวอร์ Cloudflare, แผนที่มาจาก Google)

ถ้าของสองอย่างที่ไม่เกี่ยวกันเลยพังพร้อมกัน แปลว่ามันต้องมีอะไรสักอย่างที่บล็อก “iframe จากเว็บนอก” ทั้งหมด ไม่ใช่ปัญหาที่ตัว captcha หรือแผนที่เอง

ภาพภูเขาน้ำแข็งเปรียบเทียบบั๊กที่เห็นบนผิวน้ำกับต้นเหตุจริงที่ซ่อนลึกใต้น้ำ
อาการที่เห็น (captcha ไม่ขึ้น) เป็นแค่ยอดภูเขาน้ำแข็ง — ต้นเหตุจริงซ่อนอยู่ลึกกว่านั้นมาก

ชั้นที่ 4 (ตัวจริง): นโยบายความปลอดภัยที่บล็อก iframe ทุกตัว

ผมเปิดเครื่องมือ developer ในเบราว์เซอร์ดู console แล้วก็เจอ — บรรทัดสีแดงที่ตามหามาทั้งวัน:

Framing 'https://challenges.cloudflare.com/' violates the following
Content Security Policy directive: "frame-src 'self' blob:".
The request has been blocked.

(และอีกบรรทัด บล็อก maps.google.com ด้วยเหตุผลเดียวกัน)

ตัวการคือ Content Security Policy (CSP) — เป็น header ความปลอดภัยที่บอกเบราว์เซอร์ว่า “อนุญาตให้โหลดอะไรจากที่ไหนได้บ้าง” ในเคสนี้มันตั้งกฎ frame-src 'self' blob: ไว้ ซึ่งแปลว่า “ฝัง iframe ได้เฉพาะจากเว็บตัวเองเท่านั้น” ผลคือมันบล็อกทั้ง captcha ของ Cloudflare และแผนที่ของ Google รวด — เพราะทั้งคู่มาจากโดเมนนอก

ที่ตลกร้ายคือ CSP ตัวนี้ถูกตั้งไว้ที่ฝั่ง Cloudflare เอง (ไม่ใช่ในตัว WordPress) — มันเลยบล็อก captcha ของ Cloudflare เองด้วย ผมยืนยันด้วยการเทียบ header ระหว่างเรียกผ่าน Cloudflare กับเรียกตรงเข้าเซิร์ฟเวอร์ — เรียกตรงไม่มี CSP ตัวนี้เลย แปลว่ามันถูกฉีดที่ชั้น Cloudflare ชัดเจน

วิธีแก้คือเข้าไปแก้กฎ CSP ตัวนั้น ให้ frame-src อนุญาตโดเมนที่จำเป็นเพิ่ม — ของ Cloudflare และของ Google แค่นั้นเอง ส่วนที่เหลือคงไว้เหมือนเดิม

// เดิม — บล็อกทุก iframe จากนอก
frame-src 'self' blob:

// แก้เป็น — เปิดเฉพาะเท่าที่จำเป็น
frame-src 'self' blob: https://challenges.cloudflare.com https://*.google.com

เซฟ รอ header อัปเดต แล้วรีเฟรชแบบล้างแคชเบราว์เซอร์ — คราวนี้กล่อง captcha ขึ้นเครื่องหมายถูกเขียว “Success!” และแผนที่กลับมาแสดงพร้อมกัน

กล่อง Cloudflare Turnstile แสดงสถานะ Success ผ่านการยืนยันเรียบร้อย
หลังแก้ CSP — กล่อง captcha กลับมาขึ้นสถานะ Success ตามปกติ

ก่อน-หลัง และบทเรียนที่ผมได้

ปัญหาทั้งหมดมันซ้อนกัน 4 ชั้น และทุกชั้นต้องแก้จริง — แต่ตัวที่ “บล็อก” จริงๆ คือชั้นสุดท้ายที่อยู่ไกลตัวที่สุด สรุปสั้นๆ ให้เห็นภาพ

ชั้นอาการที่เห็นต้นเหตุจริง
1ใส่ค่าใหม่แล้วโดนเขียนทับค่า global เก่าค้างในฐานข้อมูล
2แก้แล้วหน้าไม่เปลี่ยนแคชหน้าเก่า + ลบไม่ได้เพราะสิทธิ์ไฟล์
3โค้ดถูกแต่กล่องไม่วาดตัวเร่งความเร็วเลื่อนการโหลด JS
4captcha + แผนที่หายพร้อมกันCSP frame-src บล็อก iframe นอก

บทเรียนที่ผมจะเก็บไว้ใช้ตลอดไปคือ — ถ้า captcha กับแผนที่ (หรือ iframe ฝังอื่นๆ) พังพร้อมกัน ให้สงสัย CSP เป็นอันดับแรก อย่าเพิ่งไปวนแก้ที่ key หรือปลั๊กอินเหมือนผม เปิด console ในเบราว์เซอร์ดูก่อนเลย ถ้าเห็นคำว่า “violates Content Security Policy” ก็ตัดจบได้เร็วขึ้นเป็นชั่วโมง

อีกเรื่องคือ — อย่ามองข้ามคำบ่นของผู้ใช้ ประโยค “แผนที่ก็หายด้วยนะ” ที่ฟังดูเหมือนไม่เกี่ยว กลับเป็นเบาะแสที่พาผมไปเจอต้นเหตุจริง บางทีคนที่ใช้งานจริงเขาเห็นภาพรวมที่เรามองไม่เห็น เพราะเรามัวแต่จ้องอยู่ที่จุดเดียว

ถ้าใครสนใจเรื่องการดูแลเว็บหลายๆ เว็บพร้อมกัน หรือการใช้เครื่องมือช่วย debug แบบนี้ ผมเคยเขียนไว้สองเรื่อง — จัดการเว็บ WordPress 90 กว่าเว็บ จากที่เดียว กับ ควบคุม Server ด้วย AI ผ่าน Coolify MCP ลองอ่านดูได้ครับ

หวังว่าเคสนี้จะเป็นประโยชน์กับคนที่กำลังนั่งงงว่าทำไม captcha ในเว็บถึงหายไปเฉยๆ นะครับ — บางทีปัญหาที่เห็นบนผิวน้ำ มันก็แค่ยอดของภูเขาน้ำแข็งจริงๆ

เครื่องมือหลักที่ใช้ในเคสนี้

  • SSH + WP-CLI สำหรับขุดค่าในฐานข้อมูลและล้างแคช
  • Developer Tools (Console) ในเบราว์เซอร์ — ตัวที่ชี้เป้า CSP ให้เจอ
  • Cloudflare API สำหรับแก้กฎ Response Header (CSP)
Scroll to Top