เครื่องมือตรวจสอบ iframe
เครื่องมือตรวจสอบ iframe ออนไลน์ฟรี: กรอก URL ของเว็บเพจใดก็ได้เพื่อตรวจสอบ Header X-Frame-Options และ Content-Security-Policy frame-ancestors ว่าหน้านั้นสามารถฝังใน iframe ได้หรือไม่ พร้อมยืนยันผลด้วยการดูตัวอย่างแบบ Live โดยประวัติจะถูกเก็บไว้เฉพาะเซสชันเบราว์เซอร์ปัจจุบัน
ประวัติ (เซสชันนี้)
บันทึกเฉพาะแท็บนี้เท่านั้น (sessionStorage) และจะถูกล้างเมื่อปิดแท็บ ข้อมูลไม่ถูกส่งไปยังเซิร์ฟเวอร์
หลักการทำงานของเครื่องมือตรวจสอบ
- กรอก URL เป้าหมายแล้วคลิกตรวจสอบ เซิร์ฟเวอร์จะดึงหน้าเว็บในรูปแบบคำขอ iframe จริง (พร้อมหัวข้อมาตรฐาน Sec-Fetch-Dest: iframe) และอ่านค่า Header ของ Response
- ผลสรุปคำนวณจาก X-Frame-Options (DENY / SAMEORIGIN ป้องกันการฝัง) และ Content-Security-Policy frame-ancestors (เฉพาะค่า * หรือ wildcards ของโพรโทคอลเท่านั้นที่ยอมรับการฝังจากโฮสต์ภายนอกอื่น ๆ)
- URL เด่นจะถูกโหลดลงใน iframe ตัวอย่างแบบ Live เพื่อให้คุณยืนยันสิ่งที่เบราว์เซอร์ จริง ๆ ทำ รวมถึง JavaScript frame-busting ที่ Header ไม่สามารถเปิดเผยได้
อะไรเป็นตัวกำหนดว่าหน้าเว็บสามารถฝังได้หรือไม่?
การที่เบราว์เซอร์จะยอมให้แสดงหน้าเว็บหนึ่งอยู่ใน `<iframe>` ของอีกไซต์นั้น ขึ้นอยู่กับการตรวจสอบ Response Headers ที่ส่งมาจากหน้าเว็บนั้นโดยตรง เครื่องมือตรวจสอบนี้จะทำการวิเคราะห์ฝั่งเซิร์ฟเวอร์ในรูปแบบเดียวกันและให้ผลการประเมินทันที
`X-Frame-Options` เป็น Header มาตรฐานดั้งเดิม โดยค่า DENY จะห้ามฝังหน้าเว็บในทุกกรณี, SAMEORIGIN เปิดโอกาสเฉพาะหน้าที่มาจากต้นทางเดียวกันเท่านั้น ส่วน ALLOW-FROM นั้นถูกลบออกจากการรองรับในเบราว์เซอร์ยุคใหม่แล้ว หากไม่พบ Header นี้ กฎรุ่นเก่าจะไม่ได้ทำหน้าที่บล็อกการฝังหน้าเว็บ
`Content-Security-Policy: frame-ancestors` คือตัวเลือกปัจจุบันที่ทันสมัยกว่า โดยจะมีการระบุ Origin ที่อนุญาตให้ฝังหน้าเว็บ เช่น `frame-ancestors 'self' https://example.com` หากกำหนดค่าเป็น `*` (หรือระบุแค่โปรโตคอลเช่น `https:`) จะอนุญาตให้ไซต์ HTTPS อื่นๆ นำมาฝังได้ทั้งหมด ในขณะที่การจำกัดเงื่อนไขที่แคบลงจะช่วยบล็อกการฝังจากภายนอก
แต่ Header อาจไม่ใช่ปัจจัยเดียวที่ใช้ตัดสินได้: สคริปต์ในหน้าเว็บอาจรันโค้ดป้องกัน Frame-Busting (เช่น ตรวจสอบ `top !== self` เพื่อบังคับนำทางกลับมายังระดับบนสุด) นอกจากนี้หน้าล็อกอินหรือจุดเชื่อมต่อที่จำกัดตามภูมิศาสตร์ อาจทำงานต่างออกไปเมื่อรับคำขอจาก Data Center นี่คือสาเหตุที่แต่ละผลลัพธ์จะมาพร้อมกับตัวอย่างการแสดงจริง (Live Preview) เสมอ
คู่มือลัด: การตั้งค่า Response Header
หากเป็นผู้ดูแลหรือเจ้าของเว็บไซต์ สามารถควบคุมการฝัง iframe ได้โดยตรงผ่าน Response Headers
ปิดกั้นการฝังหน้าเว็บจากทุกแหล่ง
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
เบราว์เซอร์จะปฏิเสธการแสดงผลหน้าเว็บใน iframe ทุกประเภท นี่เป็นมาตรการป้องกันการคลิกแจ็กกิ้ง (Clickjacking) ที่เข้มงวดที่สุด
อนุญาตให้ฝังเฉพาะจากโดเมนต้นทางเดียวกัน (Same-Origin)
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
อนุญาตเฉพาะหน้าที่มีโปรโตคอล (Scheme), โดเมน (Host) และพอร์ต (Port) ตรงกันเท่านั้น โดยจะปิดกั้นไซต์ของผู้ให้บริการบุคคลที่สามทั้งหมด
อนุญาตให้ฝังเฉพาะโดเมนที่กำหนดเท่านั้น
Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com
ระบุโดเมนที่น่าเชื่อถือลงในค่า frame-ancestors; โดเมนอื่นที่ไม่ได้ระบุจะได้รับการปิดกั้น
อนุญาตให้ฝังได้จากทุกหน้าเว็บ
ไม่ตั้งค่า X-Frame-Options
Content-Security-Policy: frame-ancestors *
ไซต์ใดก็สามารถนำหน้าเว็บนี้ไปฝังใน iframe ได้ ควรพิจารณา权衡ความปลอดภัยอย่างรอบคอบ
ข้อควรทราบ: Directive ใน CSP อย่าง `frame-ancestors` มาแทนที่มาตรฐาน `X-Frame-Options` ในยุคปัจจุบัน เมื่อทั้งสอง Header ปรากฏพร้อมกัน ค่า CSP จะมีความสำคัญสูงกว่า จึงแนะนำให้ใช้ `frame-ancestors` เป็นหลัก
เซิร์ฟเวอร์ตรวจจับได้อย่างไรว่าคำขอมาจาก iframe?
นับตั้งแต่ประมาณปี 2020 เป็นต้นมา เบราว์เซอร์ฐาน Chromium และ Firefox จะแนบชุด Request Headers สำหรับ Fetch Metadata อัตโนมัติไปกับทุกการโหลดหน้าเว็บและคำขอ Resourceเสริม Header ที่มีประโยชน์ที่สุดสำหรับการตรวจสอบการฝังคือ `Sec-Fetch-Dest` ซึ่งบอกเซิร์ฟเวอร์ได้ว่าทรัพยากรนี้ถูกร้องขอในบริบทประเภทใด โดยไม่ต้องพึ่งพาการตั้งค่าใดๆ จากฝั่งหน้าเว็บที่ถูกนำไปฝัง
- `Sec-Fetch-Dest: document` — เป็นการนำทางระดับบนสุด (Top-level) ไม่ใช่การโหลดภายในกรอบใดๆ
- `Sec-Fetch-Dest: iframe` — คำขอนี้ถูกโหลดภายในองค์ประกอบ `<iframe>`
- `Sec-Fetch-Dest: frame` — โหลดภายใน `<frame>` (Frameset) รุ่นเก่า
- `Sec-Fetch-Dest: embed / object` — โหลดภายในองค์ประกอบ `<embed>` หรือ `<object>`
เครื่องมือตรวจสอบนี้จะส่งแพ็กเก็ตทดสอบพร้อมเฮดเดอร์ Sec-Fetch-Dest: iframe เพื่อจำลองพฤติกรรมของเบราว์เซอร์จริงที่ทำการฝังหน้าเว็บอย่างครบถ้วน ส่งผลให้ผลลัพธ์สะท้อนการตอบสนองที่แท้จริงของเซิร์ฟเวอร์เป้าหมายต่อคำขอ iframe จริงๆ ไม่ใช่เพียงแค่การเรียกข้อมูลทั่วไป (generic fetch)
Sec-Fetch-Dest เป็นสัญญาณที่มีประโยชน์แต่ไม่ได้ถือเป็นชั้นเชิงป้องกันความปลอดภัย เนื่องจากมีเพียงเบราว์เซอร์สมัยใหม่ที่ส่งค่านี้ และมีไคลเอนต์อื่นที่ไม่ใช่เบราว์เซอร์ เช่น curl, บอต, Webview บางรูปแบบ หรือเบราว์เซอร์รุ่นเก่า ที่อาจไม่ส่งมาเลยหรือส่งค่าใดก็ตามเข้ามาได้ เซิร์ฟเวอร์อาจนำค่านี้ไปใช้ในการบันทึก Log หรือกำหนดอัตราการเรียกใช้ (Rate-limit) ได้ แต่ไม่ควรยึดถือเพียงลำพังในการตัดสินใจว่าการเล่นใน Frame ปลอดภัยหรือไม่ การควบคุมดังกล่าวควรขึ้นอยู่กับเฮดเดอร์ X-Frame-Options และ CSP frame-ancestors เป็นหลัก โดยเบราว์เซอร์จะบังคับใช้กฎเหล่านี้แยกต่างหากจากรหัสเฮดเดอร์ของคำขอ
ทางฝั่งไคลเอนต์ หน้าเว็บยังสามารถตรวจสอบตัวเองว่ากำลังรันอยู่ในเฟรมหรือไม่ แม้ไม่มีเฮดเดอร์ใดๆ รองรับ ตัวอย่างเช่น การเปรียบเทียบค่า window.top !== window.self (หรืออ่านค่า window.frameElement ในกรณี Same-Origin) จากนั้นจึงจัดการตอบสนองที่เหมาะสม เช่น แสดงคำเตือน หรือบังคับให้เปลี่ยนเส้นทางไปยังหน้าระดับบนสุด (Top-level redirect) ซึ่งเป็นเทคนิค Frame-busting ที่ได้กล่าวไว้ก่อนหน้านี้ และวิธีการนี้จะทำงานได้เสมอ ไม่ว่าสถานะเฮดเดอร์ของคำขอจะเป็นอย่างไร
ตัวอย่างการตั้งค่าเซิร์ฟเวอร์: ป้องกันการฝัง iframe
ตัวอย่างโค้ดด้านล่างอธิบายวิธีตั้งค่าเซิร์ฟเวอร์หรือเฟรมเวิร์กเพื่อปิดกั้นการฝัง iframe อย่างสมบูรณ์ (X-Frame-Options: DENY + CSP frame-ancestors 'none') ซึ่งเป็นแนวทางที่เข้มงวดที่สุดในคำแนะนำด่วนด้านบน
server {
location / {
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;
}
}
<IfModule mod_headers.c>
Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "frame-ancestors 'none'"
</IfModule>
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
next();
});
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Content-Security-Policy', value: "frame-ancestors 'none'" },
],
},
];
},
};
class SetFrameOptions
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('Content-Security-Policy', "frame-ancestors 'none'");
return $response;
}
}
export default {
async fetch(request) {
const response = await fetch(request);
const headers = new Headers(response.headers);
headers.set('X-Frame-Options', 'DENY');
headers.set('Content-Security-Policy', "frame-ancestors 'none'");
return new Response(response.body, { status: response.status, headers });
},
};
ต้องการอนุญาตเฉพาะเว็บไซต์ของตนเอง หรือรายชื่อเว็บไซต์สั้นๆ ให้ฝังหน้าเว็บได้ แทนการปิดกั้นทั้งหมดหรือไม่? สามารถเปลี่ยน DENY เป็น SAMEORIGIN และเปลี่ยน 'none' เป็น 'self' หรือระบุโดเมนที่ต้องการเจาะจงได้เลย โดยวิธีการตั้งค่าในส่วนอื่นๆ ยังคงเหมือนเดิม
จุดที่ต้องระวังของแพลตฟอร์มต่าง ๆ
นอกจากหลักการมาตรฐานของเฮดเดอร์ทั่วไปแล้ว แพลตฟอร์มยอดนิยมหลายแห่งยังมีความเฉพาะตัวของตัวเอง ซึ่งผู้พัฒนาควรทำความเข้าใจไว้ก่อนจะได้อาจทึกทักเองว่าเนื้อหาถูก "Blocked" หรือ "Embeddable" เพียงจากการตรวจสอบเฮดเดอร์เท่านั้น
| แพลตฟอร์ม / สถานการณ์ | พฤติกรรมการทำงาน | เหตุผลประกอบ |
|---|---|---|
| หน้าวิดีโอ YouTube (youtube.com/watch) | ถูกบล็อก | ควรใช้ URL วิดีโอแบบฝังทางการของ YouTube (youtube.com/embed/{id}) แทน เนื่องจากหน้าดูวิดีโอปกติจะมีนโยบายจำกัดการเล่นใน Frame อยู่แล้ว |
| หน้ารายละเอียดสถานที่บน Google Maps | ถูกบล็อก | จะมีก็เฉพาะ URL ของ Maps Embed API (google.com/maps/embed) เท่านั้นที่เปิดรองรับการฝังใน Frame โดยตรง |
| หน้าเข้าสู่ระบบผ่าน OAuth ของ Google และ Microsoft | ถูกบล็อก | มีการบล็อกไว้ตั้งแต่ปี 2015 เพื่อป้องกันการหลอกลวงขโมยข้อมูลรับรอง (Credential Phishing) ภายใน iframe โดยกระบวนการเข้าสู่ระบบจำเป็นต้องดำเนินการในหน้าต่างหลักหรือป๊อปอัปเท่านั้น |
| หน้าชำระเงิน (เช่น Stripe Checkout, PayPal และ Gateway ธนาคารส่วนใหญ่) | ถูกบล็อก | การเล่นใน Frame ของฟอร์มชำระเงินถือเป็นช่องทางโจมตีแบบ Clickjacking ที่พบบ่อยที่สุด ผู้ให้บริการตัดเงินจึงปฏิเสธการฝังแบบเด็ดขาด |
| หน้า Repository หรือไฟล์บน GitHub | ถูกบล็อก | กำหนดนโยบาย frame-ancestors 'none' ครอบคลุมทั้งเว็บไซต์ แนะนำให้ใช้ REST/GraphQL API หรือการแสดงภาพหน้าจอ (Screenshot) แทนการใช้ iframe |
| หน้า "Notion" หรือ "Google Docs" แบบ Publish to web | ฝังได้ | ออกแบบมาให้รองรับการฝังโดยตรง และผลิตภัณฑ์ยังให้โค้ดสำหรับฝัง (Embed code) มาให้ใช้งานได้เลย |
| บทความใน Wikipedia | ฝังได้ | โดยค่าเริ่มต้นไม่มีการจำกัดด้วย X-Frame-Options หรือ CSP อย่างไรก็ดี ควรตรวจสอบเรื่องลิขสิทธิ์และเงื่อนไขการอ้างอิงอีกครั้งก่อนนำเนื้อหาไปฝังจำนวนมาก |
สถานการณ์การใช้งานทั่วไป
- ช่วยตัดสินใจล่วงหน้าว่าวิดเจ็ต (Widget), แดชบอร์ด หรือเอกสารของบุคคลที่สามสามารถนำมากับงานในผลิตภัณฑ์ของคุณได้หรือไม่ ก่อนจะเริ่มลงมือเขียนโค้ดเชื่อมต่อ (Integration)
- ตรวจสอบให้แน่ใจว่าเว็บไซต์ของคุณส่งนโยบาย X-Frame-Options หรือ CSP frame-ancestors ตรงตามที่ตั้งใจไว้จริง ๆ
- แก้ไขปัญหาด้านการป้องกันการคลิกจี้ (Clickjacking): ตรวจสอบสาเหตุที่ทำให้หน้าเว็บแสดงผลเป็น `about:blank` หรือแสดงข้อความปฏิเสธการทำงานเมื่ออยู่ภายใน iframe
- ตรวจสอบรายชื่อ URL ที่ผ่านการคัดกรองเบื้องต้นแบบแบตช์ ระหว่างการรวบรวมเนื้อหาหรือการผสานระบบไปยังพอร์ทัล โดยสามารถใช้ประวัติเซสชันย้อนกลับมาดูผลลัพธ์เดิมได้
- เตรียมความพร้อมสำหรับการทบทวนความปลอดภัยหรือการทดสอบเจาะระบบ (Penetration Testing) โดยการบันทึกนโยบายการควบคุมการแสดงผลในกรอบ (Framing Policy) ของ Third-party Dependencies