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