Files
esh-pfi-infrastructure/scripts/meshcentral-amt-relay-probe.js
T

47 lines
3.3 KiB
JavaScript

// Probe an Intel AMT device through MeshCentral's web relay (webrelay.ashx), which is the same path the
// browser's "HW Connect" (KVM), SOL, IDE-R and the embedded MeshCommander use.
// KVMR | SOL | IDER : StartRedirectionSession, then the authentication-methods query. It never
// authenticates, so no session is opened on the target.
// HTTP : one empty WS-Man POST (any HTTP reply proves channel + TLS + auth injection).
// A healthy device answers in well under a second. Silence until the timeout means the relay channel is dead.
// Seen 2026-10-02: a stale second CIRA tunnel (servers/pfi-tacticalrmm/README.md).
//
// Runs ON pfi-tacticalrmm as `tactical` (needs MeshCentral's `ws` module). stdin lines: login user, login pass,
// node id, mode. Nothing secret is in this file. Example (from eshpfi-management on nh3-dev):
// scp scripts/meshcentral-amt-relay-probe.js infra-ops@10.250.50.57:/tmp/p.js
// { secret get pfi-tacticalrmm/meshcentral-login-token | python3 -c "import sys; f=sys.stdin.read().split(); print(f[1]); print(f[3])"
// printf '%s\n%s\n' '<node id>' KVMR; } | ssh infra-ops@10.250.50.57 'sudo -n -u tactical node /tmp/p.js; rm -f /tmp/p.js'
// Always run a known-good device as a positive control next to the one under suspicion.
const [U, P, NODE, MODE] = require("fs").readFileSync(0, "utf8").split("\n");
const WebSocket = require("/meshcentral/node_modules/ws");
const t0 = Date.now();
const log = (...a) => console.log(`[+${((Date.now() - t0) / 1000).toFixed(1)}s]`, ...a);
const start = {
KVMR: [0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52],
SOL: [0x10, 0x00, 0x00, 0x00, 0x53, 0x4f, 0x4c, 0x20],
IDER: [0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52],
}[MODE.trim()];
const HTTP = MODE.trim() === "HTTP";
const url = "wss://rmm-mesh.phasefinal.com/webrelay.ashx?p=" + (HTTP ? 1 : 2) + "&host=" + encodeURIComponent(NODE.trim()) + "&port=" + (HTTP ? 16993 : 16995) + "&tls=1&tls1only=0";
const ws = new WebSocket(url, { headers: { "x-meshauth": Buffer.from(U).toString("base64") + "," + Buffer.from(P).toString("base64") } });
ws.on("open", () => {
if (HTTP) { log("relay websocket open (p=1); sending HTTP POST /wsman"); ws.send(Buffer.from("POST /wsman HTTP/1.1\r\nHost: amt:16993\r\nContent-Type: application/soap+xml\r\nContent-Length: 0\r\n\r\n")); return; }
log("relay websocket open; sending StartRedirectionSession", MODE.trim()); ws.send(Buffer.from(start));
});
ws.on("message", (d, isBinary) => {
const s = isBinary ? d.toString("latin1") : d.toString("utf8");
if (HTTP) { log("AMT HTTP ->", JSON.stringify(s.split("\r\n").slice(0, 3))); ws.close(); return; }
const b = Array.from(s, (c) => c.charCodeAt(0));
log("AMT ->", b.length, "bytes:", b.slice(0, 24).map((x) => x.toString(16).padStart(2, "0")).join(" "));
if (b[0] === 0x11) {
log("StartRedirectionSessionReply status", b[1], b[1] === 0 ? "(success)" : "(FAIL)");
if (b[1] === 0) ws.send(Buffer.from([0x13, 0, 0, 0, 0, 0, 0, 0, 0]));
} else if (b[0] === 0x14) {
log("AuthenticateSessionReply status", b[1], "auth types offered:", b.slice(9).join(","), "(4=digest, 3=kerberos? per Intel)");
ws.close();
}
});
ws.on("close", (c, r) => { log("closed", c, String(r)); process.exit(0); });
ws.on("error", (e) => log("error", e.message));
setTimeout(() => { log("TIMEOUT: no further reply in 25 s"); process.exit(0); }, 25000);