כותרת Penligent

אבטחו את ממשק המשתמש שלכם: דף עזר מעודכן בנושא XSS מבוסס DOM

מבוא

דף עזר ל-XSS מבוסס DOM הוא המקור המהימן שלך כאשר ברצונך לאתר, למנוע ולהפוך לאוטומטית את ההגנה מפני הזרקת סקריפטים בצד הלקוח. בעיקרו של דבר: לזהות היכן קלט הנשלט על ידי המשתמש (א מקור) זורם ל-API מסוכן (a כיור), החלף אותו בדפוסים בטוחים (השתמש ב- תוכן הטקסט, createElement, או חומרי חיטוי), ושילבו בדיקות בתהליך הבנייה, זמן הריצה ו-pentest שלכם. מכיוון ש-DOM XSS מתרחש כולו בדפדפן ועוקף מסננים מסורתיים רבים בצד השרת, ה-front-end שלכם הופך לקו ההגנה האחרון — והמקום שבו לרוב הצוותים עדיין חסרה אוטומציה.

מהו XSS מבוסס DOM ומדוע הוא חשוב

לפי PortSwigger, תסריט בין-אתרי מבוסס DOM מתרחש כאשר JavaScript לוקח נתונים ממקור שבשליטת התוקף, כגון כתובת URL, ומעביר אותם למקור הנתונים התומך בביצוע קוד דינמי, כגון eval() או innerHTML." portswigger.net המדריך למניעת XSS מבוסס DOM של OWASP מדגיש כי ההבדל העיקרי בין XSS מאוחסן/משקף הוא הזרקה בצד הלקוח בזמן ריצה. cheatsheetseries.owasp.org+1

ביישומים מודרניים — אפליקציות דף יחיד (SPA), שימוש נרחב בווידג'טים של צד שלישי, בניית DOM דינמית — הסיכון גדל: מטענים עשויים שלא להגיע לרישומי השרת שלך, WAF מסורתיים עלולים לפספס אותם, ומפתחים לעתים קרובות אינם שוקלים מספיק את האפשרות מזהי קטעים, זרימת postMessage, או שם החלון, כל המקורות הנפוצים. הכרה בשינוי זה היא הצעד הראשון לקראת מדידת בשלות האבטחה שלכם.

מיפוי האיום: מקורות → בולעים

קידוד מאובטח מתחיל במפה מנטלית של מקורות (שם נכנסת קלט התוקף) ו כיורים (שם מתבצע הביצוע). בלוג אבטחה אחד מסכם: "המקור הוא כל מקום בדף אינטרנט שבו ניתן להוסיף קלט של המשתמש... הכיור הוא המקום אליו מועברים הנתונים שהוכנסו במקור... ואם הם לא עוברים טיהור, הדבר עלול להוביל לפגיעות XSS מבוססת DOM." בינוני

להלן טבלה קומפקטית שכדאי לשמור בתחנת העבודה שלך וברשימות הבדיקה:

מקור (נקודת כניסה)כיור (API מסוכן)
location.hash, location.search, URLSearchParamsinnerHTML, insertAdjacentHTML, outerHTML
postMessage, שם החלוןeval(), פונקציה חדשה(), setTimeout(מחרוזת)
document.referrer, localStorage, sessionStoragesetAttribute('on...'), element.src = ...
נתוני ווידג'ט של צד שלישי שלא נבדקוכל הוספה של DOM או ביצוע קוד מרומז

כפי שמדגיש ארגון OWASP, אין טכניקה אחת שיכולה למנוע XSS: יש לשלב בין סינכרון נכון, קידוד, טיהור ו-API בטוחים. cheatsheetseries.owasp.org

דפוסי תקיפה בעולם האמיתי עם קוד ותיקונים

דוגמה א' – הזרקת בורר jQuery באמצעות קטע URL

קטע פגיע:

js

$(window).on('hashchange', () => {

const target = $(location.hash); // בשליטת המשתמש

target[0].scrollIntoView();

});

תוקף עשוי ליצור https://site/page.html#<img src="x" onerror="alert(1)"> — jQuery מתייחס להאש כאל HTML/selector וה- onerror מפעילים.

תיקון:

js

const raw = location.hash.slice(1);

const safeId = /^[A-Za-z0-9_-]+$/.test(raw) ? raw : null;

if (safeId) {

const target = document.getElementById(safeId);

if (target) target.scrollIntoView();

}

מפתח: לאמת, להתייחס להאש כאל מזהה ולא כאל HTML.

דוגמה ב' – postMessage → שרשרת eval

קטע פגיע:

js

window.addEventListener('message', e => {

eval(e.data); // מסוכן

});

גרסה מתוקנת:

window.addEventListener('message', event => {

if (event.origin !== '') return;

נסה {

const msg = JSON.parse(event.data);

handleMessage(msg);

} catch {

console.warn('פורמט הודעה לא חוקי');

}

});

הימנע eval, בדוק את המקור, השתמש בניתוח בטוח.

דוגמה ג' – XSS בעורך/תצוגה מקדימה בהקשר של סימון

פגיע:

js

preview.innerHTML = marked(userInput);

מאובטח:

js

ייבא DOMPurify מ-'dompurify';

const dirty = marked(userInput);

const clean = DOMPurify.sanitize(dirty);

preview.innerHTML = נקי;

כאשר מאפשרים HTML שנוצר על ידי המשתמש, נדרשת טיהור.

יישומים פריסים: הפיכתם למבצעיים

עוזר DOM בטוח – safe-dom.js

js

ייבא DOMPurify מ-'dompurify';

פונקציית ייצוא setSafeHTML(el, dirty) {

const clean = DOMPurify.sanitize(dirty, {

ALLOWED_TAGS: ['b','i','a','p','ul','li','code','pre','img'],

ALLOWED_ATTR: ['href','src','alt','title','rel'],

FORBID_ATTR: ['onerror','onclick','style']

});

el.innerHTML = נקי;

}

פונקציית ייצוא setText(el, text) {

el.textContent = מחרוזת(טקסט ?? '');

}

פונקציית ייצוא safeSetAttribute(el, name, val) {

if (/^on/i.test(name)) throw new Error('תכונת מטפל אירועים אינה מותרת');

el.setAttribute(name, String(val));

}

השתמש בספרייה זו כדי לרכז פעולות DOM בטוחות ולהפחית טעויות אנוש.

אכיפה סטטית – דוגמה ל-ESLint

js

// .eslintrc.js

כללים: {

'no-restricted-syntax': [

'שגיאה',

{ selector: "AssignmentExpression[left.property.name='innerHTML']", message: "השתמש ב-safe-dom.setSafeHTML או textContent." },

{ selector: "CallExpression[callee.name='eval']", message: "הימנע משימוש ב-eval()" },

{ selector: "CallExpression[callee.property.name='write']", message: "הימנע משימוש ב-document.write()" }

]

}

שלב עם ווים של pre-commit (צרוד, מוכן) כדי לחסום דפוסים מסוכנים.

בדיקת CI / Puppeteer – GitHub Actions

.github/workflows/dom-xss.yml מפעיל בדיקת Puppeteer:

// tests/puppeteer/dom-xss-test.js

js

const puppeteer = require('puppeteer');

(async ()=>{

const browser = await puppeteer.launch();

const page = await browser.newPage();

const logs = [];

page.on('console', msg => logs.push(msg.text()));

המתן page.goto(${URL}/page.html#<img src="x" onerror="console.log("xss")">);

המתן page.waitForTimeout(1000);

if (logs.some(l=>l.includes('XSS'))) process.exit(2);

המתן browser.close();

})();

כישלון בבנייה בעת זיהוי.

ניטור זמן ריצה – MutationObserver

js

(function(){

const obs = new MutationObserver(muts=>{

muts.forEach(m=>{

m.addedNodes.forEach(n=>{

if(n.nodeType===1){

const html = n.innerHTML || '';

if(/on(error|click|load)|<script\\b/i.test(html)){

navigator.sendBeacon('/_monitoring/xss', JSON.stringify({

כתובת: location.href, קטע: html.slice(0,200)

}));

}

}

});

});

});

obs.observe(document.documentElement, {childList:true, subtree:true});

})();

שימושי בשלב ההעמדה כדי להתריע על הזרקות DOM בלתי צפויות.

חיזוק אבטחת הדפדפן – CSP וסוגי אמינות

כותרת CSP:

pgsql

מדיניות אבטחת תוכן: default-src 'self'; script‑src 'self' 'nonce‑XYZ'; object‑src 'none'; base-uri 'self';

קטע קוד של Trusted Types:

js

window.trustedTypes?.createPolicy('safePolicy', {

createHTML: s => { throw new Error('הקצאת HTML ישירה נחסמה'); },

createScript: s => { throw new Error('יצירת סקריפט ישיר נחסמה'); }

});

זה מונע ברירות מחדל של כיורים לא אמינים.

אבטחת סקריפטים של צד שלישי – SRI

לנזוף

openssl dgst -sha384 -binary vendor.js | openssl base64 -A

שימוש <script src="vendor.js" integrity="sha384‑..." crossorigin="anonymous"></script> לנעוץ ולאמת.

שילוב עם Penligent לצורך אוטומציה

אם הצוות שלכם משתמש ב-Penligent, תוכלו לשדרג את ההגנה שלכם מפני DOM XSS לצינור רציף. במאמר המחקר של Penligent מצוין כיצד "איתור XSS מבוסס DOM באמצעות מעקב אחר זיהום בזמן ריצה... טכניקות בצד השרת אינן יכולות לאתר באופן מהימן הזרקות בצד הלקוח." Penligent

דוגמה לתהליך עבודה:

  1. ב-CI הפעל סריקת Penligent עם מערך כללים dom‑xss, המספק מטענים כמו #<img src="x" onerror="alert(1)">.
  2. Penligent מבצע זרימות ללא ראש, מייצר PoC, ומחזיר ממצאים באמצעות webhook.
  3. ניתוח ממצאי CI: אם החומרה ≥ גבוהה, כשל בבנייה והוסף הערות ל-PR עם מטען + שקע + המלצת תיקון (לדוגמה, "החלף innerHTML עם safe-dom.setSafeHTML).
  4. המפתחים מתקנים, מריצים שוב את CI, וממזגים רק כאשר התוצאה חיובית.

כך נסגר המעגל: מהפניה (דף העזר הזה) → מדיניות הקוד → זיהוי אוטומטי → תיקון מאורגן.

סיכום

החזית כבר אינה "רק ממשק משתמש". היא משטח התקפה. דף העזר הזה יסייע לכם להבין את נושא ההזרקה בצד הלקוח, להחליף מקלטים מסוכנים, לבנות ספריות עזר בטוחות, לפרוס זיהוי סטטי/CI/זמן ריצה, ולבצע אוטומציה באמצעות פלטפורמה כמו Penligent. השלבים הבאים שלכם: סרקו את בסיס הקוד שלכם כדי לאתר מקלטים אסורים (innerHTML, eval), לאמץ safe‑dom ספרייה, אכיפת כללי lint, שילוב בדיקות headless ולוגיקת pentest אמיתית, ניטור ייצור/העמדה, וקיבוע משאבים של צד שלישי. הגנה על DOM XSS נועדה להפוך אותו בלתי אפשרי להחליק דרך — לא רק להסתמך על המזל.

שתף את הפוסט:
פוסטים קשורים
he_ILHebrew