const VALID_KEYS = { ip: { required: true, match: /^(\d{1,3}\.){3}\d{1,3}$/ }, city: { required: false, match: /^[a-zA-Z ]+$/ }, country: { required: false, match: /^[a-zA-Z ]+$/ }, countryCode: { required: false, match: /^[A-Z]{2}$/ }, asn: { required: false, match: /^\d+$/ }, isp: { required: false, match: /^.+$/ // ISP names can be anything really }, lat: { required: false, match: /^-?\d+(\.\d+)?$/ }, lon: { required: false, match: /^-?\d+(\.\d+)?$/ }, hostname: { required: false, match: /^.+$/ }, timezone: { required: false, match: /^[^\/]\/.+$/ }, locale: { required: false, match: /^[a-z]{2}([_-][A-Z]{2})?$/ }, subnet: { required: false, match: /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/ }, abuse: { required: false, match: /^.+$/ } } const BOBS = [ "|", "/", "-", "\\" ] async function isCORSEnabled(url) { try { const response = await fetch(url, { method: "GET", mode: "cors"}); return response.ok; } catch { return false; }; } async function getRealIP() { const response = await fetch("https://api.ipify.org?format=json"); const data = await response.json(); return data.ip; } async function checkRaw(url, real) { const response = await fetch(url); const text = await response.text(); return text === real; } async function checkRoot(url) { const response = await fetch(url); const data = await response.json(); return data } async function checkLookup(url) { const response = await fetch(url); const data = await response.json(); return data } async function checkImFeelingLucky(url) { const response = await fetch(url); const data = await response.json(); return data; } function checkReturnedJSON(data) { let dataIncluded = 0; for (let key in VALID_KEYS) { if (data[key]) { if(VALID_KEYS[key].match.test(data[key])) { console.debug("Server included key %s (value: '%s')", key, data[key]); dataIncluded++; } else { console.warn("Server included key %s (value: '%s'), but it's not valid", key, data[key]); } } else { if (VALID_KEYS[key].required) { console.warn("Server didn't include required key %s", key); } else { console.log("Server didn't include optional key %s", key) } } } return dataIncluded; } async function main(e) { e.preventDefault(); const base_url = document.querySelector("input").value; const log = document.getElementById("results"); log.textContent = "Checking: " + base_url + "\n"; log.textContent += "Checking if CORS is enabled on the target... "; const corsEnabled = await isCORSEnabled(base_url); log.textContent += corsEnabled ? "Yes\n" : "No\n"; if (!corsEnabled) { log.textContent += "CORS is not enabled, the script will not work\n"; return; } log.textContent += "Getting external IP from trusted source... "; const realIP = await getRealIP(); log.textContent += realIP + "\n"; log.textContent += "Checking /... "; const root = await checkRoot(base_url); const rootFeatures = checkReturnedJSON(root) let rootOK = rootFeatures >= 1 && root.ip === realIP; log.textContent += rootOK ? `OK (${rootFeatures}/${Object.keys(VALID_KEYS).length} features)\n` : "FAILED\n"; log.textContent += "Checking /lookup... "; const lookup = await checkLookup(base_url + "/lookup?ip=" + realIP); const lookupFeatures = checkReturnedJSON(lookup) let lookupOK = lookupFeatures >= 1 && lookup.ip === realIP; log.textContent += lookupOK ? `OK (${rootFeatures}/${Object.keys(VALID_KEYS).length} features)\n` : "FAILED\n"; log.textContent += "Checking /imfeelinglucky (this may take a while) "; let changed = false; for(let i=0; i<512; i++) { console.debug("Checking /imfeelinglucky iteration %d/512", i); const lucky = await checkImFeelingLucky(base_url + "/imfeelinglucky"); const luckyFeatures = checkReturnedJSON(lucky) let luckyOK = luckyFeatures >= 1 && lucky.ip != realIP; if (luckyOK) { console.debug("Server modified IP, OK") log.textContent = log.textContent.slice(0, -1) log.textContent += `OK (${luckyFeatures}/${Object.keys(VALID_KEYS).length} features, took ${i} attempts for a modified IP)\n`; changed = true; break; } else { console.debug("Server did not modify IP, retrying") log.textContent = log.textContent.slice(0, -1) log.textContent += BOBS[i % 4]; } } if(!changed) { log.textContent += "FAILED (did not modify returned IP within 512 tries)\n"; } log.textContent += "Checking /raw... "; const raw = await checkRaw(base_url + "/raw", realIP); log.textContent += raw ? "OK\n" : "FAILED\n"; } document.querySelector("form").addEventListener("submit", main);