Add SHRoNK tester
This commit is contained in:
parent
b644a3f411
commit
1f350d8399
2 changed files with 231 additions and 0 deletions
46
cert/index.html
Normal file
46
cert/index.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>SHRoNK IP Specification test</title>
|
||||
<script src="script.js" defer></script>
|
||||
<style>
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
background: #29242A;
|
||||
color: #dddadd
|
||||
}
|
||||
code, pre {
|
||||
overflow: wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: "Ubuntu Mono", "Jetbrains Mono", "Consolas", monospace;
|
||||
max-width: 100%;
|
||||
white-space: pre-wrap
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SHRoNK IP Online specification test</h1>
|
||||
<p>This tool allows you to check the specification-following status of a SHRoNK IP server.</p>
|
||||
<p>
|
||||
This online tool allows you to quickly verify which parts of the
|
||||
<a href="https://github.com/SHRoNK-Corporation/shronk-ip-spec/blob/d1ca0b7/RFS0001.md" target="_blank" rel="noopener">SHRoNK IP specification</a>
|
||||
are properly implemented by a SHRoNK IP server.
|
||||
</p>
|
||||
<blockquote>
|
||||
Warning! This tool cannot test for <strong>non-CORS enabled</strong> servers!
|
||||
If the server does not have CORS enabled, this tool will not be able to test the server.
|
||||
</blockquote>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<form>
|
||||
<label for="server">Server URL:</label>
|
||||
<input type="url" id="server" name="server" placeholder="https://example.com" required>
|
||||
<button type="submit">Test</button>
|
||||
</form>
|
||||
<br/>
|
||||
<code><pre id="results"></pre></code>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
185
cert/script.js
Normal file
185
cert/script.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
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);
|
Reference in a new issue