איך XSS קורה דרך JSON
שורה תחתונה: JSON לא מבצע שום דבר בעצמו. XSS קורה ברגע שמתייחסים אליו כ-HTML — חיבור מחרוזות בצד השרת, innerHTML בצד הלקוח, או הטמעת JSON ישירות בתג script.

שורה תחתונה: JSON לעולם לא מבצע שום דבר בעצמו — סגרו קודם את שלושת נתיבי הפלט: עשו escaping בצד השרת לפי הקשר, השתמשו ב-textContent במקום innerHTML, והחליפו < ב-\u003c כשמטמיעים JSON.
{"name": "<img src=x onerror=alert(1)>"} הוא נתון תקין. האם הוא מסוכן תלוי לגמרי במקום שבו שמים אותו.
כניסה ראשונה: בניית מחרוזות בצד השרת
res.send(`<div>Hello, ${data.name}</div>`);
הדפדפן מפרס את התגיות בתוך data.name. ה-JSON כאן הוא רק המכל; האשמה היא escaping להקשר הלא נכון.
תיקון: השתמשו ב-escaping ברירת המחדל של מנוע התבניות (<%= %> ב-EJS, {{ }} ב-Vue וב-Django). הימנעו מ-<%- %>, v-html ו-|safe לכל דבר שנשלט על ידי המשתמש.
כניסה שנייה: innerHTML
el.innerHTML = user.bio; // parses markup
el.textContent = user.bio; // inserts text
{user.bio} של React ו-{{ user.bio }} של Vue יוצרים צומתי טקסט ובטוחים כברירת מחדל. dangerouslySetInnerHTML ו-v-html הם ויתור מפורש.
אם באמת צריך טקסט עשיר, עשו סניטיזציה עם allowlist (DOMPurify ובדומה) במקום לסמוך על הבקאנד.
כניסה שלישית: הטמעה בתג script
<script>
window.__DATA__ = {"name": "x</script><script>alert(1)</script>"};
</script>
</script> בתוך הנתונים סוגר את הבלוק מוקדם וכל מה שאחריו רץ כסקריפט חדש. זה המקרה המוחמץ ביותר.
JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
עדיין JSON תקין, זהה אחרי פירוס, ולא יכול יותר לסגור את התג. Next.js ו-Nuxt עושים אותו דבר עבור __NEXT_DATA__ ו-__NUXT__.
שני שכנים שכדאי להכיר
JSON hijacking — דפדפנים ישנים ביצעו JSON שנטען דרך <script src>; הגדרה מחדש של Array הדליפה את התוכן. דפדפנים מודרניים תיקנו זאת, ו-Content-Type: application/json יחד עם X-Content-Type-Options: nosniff סוגר את השאר.
שאריות JSONP — שם callback שנשלט על ידי המשתמש הוא ביצוע קוד שרירותי לפי תכנון. אל תשתמשו בו במערכות חדשות.
צ'ק ליסט
- כל מקום שבו שדה JSON מגיע ל-HTML עובר escaping לפי ההקשר שלו (HTML, attribute, JS, URL)
- אין
innerHTML,v-htmlאוdangerouslySetInnerHTMLשצורכים מחרוזות API - JSON מוטמע עושה escaping ל-
<,>ו-& - טקסט עשיר עובר סניטיזציה לפי allowlist, לא סינון blacklist
- תגובות נושאות
Content-Type: application/jsonו-nosniff
שאלות ותשובות
למה JSON תקין עדיין יכול להפעיל XSS?
ה-JSON הוא רק טקסט ולעולם לא מתבצע; היותו תקין רק אומר שהוא נפרס. הסכנה מופיעה כשהוא נכתב להקשר HTML או script: הדפדפן מפרס את התגיות, או ש-</script> סוגר את הבלוק מוקדם והשאר רץ כקוד. האשמה היא ה-escaping החסר לפי הקשר הפלט, לא פורמט הנתונים.
האם להשתמש ב-textContent או ב-innerHTML כדי להציג שדות API?
עדיפו textContent: הוא מכניס את הערך כטקסט פשוט, כך שתגיות מוצגות מילולית. innerHTML מפרס סימון, כך ששדה API שמכיל <img onerror=...> מתבצע. {user.bio} של React ו-{{ user.bio }} של Vue יוצרים צומתי טקסט כברירת מחדל; dangerouslySetInnerHTML ו-v-html הם ויתור מפורש.
איך לעשות escaping ל-JSON שמוטמע בתג <script>?
כן. </script> בתוך הנתונים סוגר את הבלוק מוקדם וכל מה שאחריו רץ כסקריפט חדש. אחרי הסריאליזציה, החליפו <, > ו-& ב-\u003c, \u003e ו-\u0026: התוצאה עדיין JSON תקין, נפרסת באופן זהה, ולא יכולה יותר לסגור את התג.