mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
fix: force classic auth for macOS VNC (#1313)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import net from "net";
|
import net from "net";
|
||||||
|
|
||||||
const RFB_BANNER_LENGTH = 12;
|
const RFB_BANNER_LENGTH = 12;
|
||||||
|
const VNC_AUTH_SECURITY_TYPE = 2;
|
||||||
const MACOS_RFB_BANNER = Buffer.from("RFB 003.889\n", "ascii");
|
const MACOS_RFB_BANNER = Buffer.from("RFB 003.889\n", "ascii");
|
||||||
const STANDARD_RFB_BANNER = Buffer.from("RFB 003.008\n", "ascii");
|
const STANDARD_RFB_BANNER = Buffer.from("RFB 003.008\n", "ascii");
|
||||||
|
|
||||||
@@ -32,17 +33,47 @@ export async function createMacosVncCompatibilityProxy({
|
|||||||
client.pipe(upstream);
|
client.pipe(upstream);
|
||||||
|
|
||||||
let pending = Buffer.alloc(0);
|
let pending = Buffer.alloc(0);
|
||||||
|
const forwardMacosSecurityTypes = (chunk: Buffer) => {
|
||||||
|
pending = Buffer.concat([pending, chunk]);
|
||||||
|
if (pending.length < 1) return;
|
||||||
|
|
||||||
|
const securityTypeCount = pending[0];
|
||||||
|
if (securityTypeCount === 0) {
|
||||||
|
upstream.off("data", forwardMacosSecurityTypes);
|
||||||
|
client.write(pending);
|
||||||
|
upstream.pipe(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pending.length < 1 + securityTypeCount) return;
|
||||||
|
|
||||||
|
upstream.off("data", forwardMacosSecurityTypes);
|
||||||
|
const securityTypes = pending.subarray(1, 1 + securityTypeCount);
|
||||||
|
if (securityTypes.includes(VNC_AUTH_SECURITY_TYPE)) {
|
||||||
|
client.write(Buffer.from([1, VNC_AUTH_SECURITY_TYPE]));
|
||||||
|
} else {
|
||||||
|
client.write(pending.subarray(0, 1 + securityTypeCount));
|
||||||
|
}
|
||||||
|
client.write(pending.subarray(1 + securityTypeCount));
|
||||||
|
upstream.pipe(client);
|
||||||
|
};
|
||||||
|
|
||||||
const forwardServerBanner = (chunk: Buffer) => {
|
const forwardServerBanner = (chunk: Buffer) => {
|
||||||
pending = Buffer.concat([pending, chunk]);
|
pending = Buffer.concat([pending, chunk]);
|
||||||
if (pending.length < RFB_BANNER_LENGTH) return;
|
if (pending.length < RFB_BANNER_LENGTH) return;
|
||||||
|
|
||||||
upstream.off("data", forwardServerBanner);
|
upstream.off("data", forwardServerBanner);
|
||||||
const banner = pending.subarray(0, RFB_BANNER_LENGTH);
|
const banner = pending.subarray(0, RFB_BANNER_LENGTH);
|
||||||
client.write(
|
const remainder = pending.subarray(RFB_BANNER_LENGTH);
|
||||||
banner.equals(MACOS_RFB_BANNER) ? STANDARD_RFB_BANNER : banner,
|
if (banner.equals(MACOS_RFB_BANNER)) {
|
||||||
);
|
client.write(STANDARD_RFB_BANNER);
|
||||||
client.write(pending.subarray(RFB_BANNER_LENGTH));
|
pending = Buffer.alloc(0);
|
||||||
upstream.pipe(client);
|
upstream.on("data", forwardMacosSecurityTypes);
|
||||||
|
if (remainder.length > 0) forwardMacosSecurityTypes(remainder);
|
||||||
|
} else {
|
||||||
|
client.write(banner);
|
||||||
|
client.write(remainder);
|
||||||
|
upstream.pipe(client);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
upstream.on("data", forwardServerBanner);
|
upstream.on("data", forwardServerBanner);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,14 +32,20 @@ async function read(socket: net.Socket, length: number): Promise<Buffer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("createMacosVncCompatibilityProxy", () => {
|
describe("createMacosVncCompatibilityProxy", () => {
|
||||||
it("normalizes Apple's private RFB banner and preserves later traffic", async () => {
|
it("normalizes Apple's private RFB banner and forces classic VNC auth", async () => {
|
||||||
let clientBanner = "";
|
let clientBanner = "";
|
||||||
|
let selectedSecurityType = 0;
|
||||||
const target = net.createServer((socket) => {
|
const target = net.createServer((socket) => {
|
||||||
socket.write("RFB 003.");
|
socket.write("RFB 003.");
|
||||||
socket.write("889\n");
|
socket.write("889\n");
|
||||||
socket.once("data", (data) => {
|
socket.once("data", (data) => {
|
||||||
clientBanner = data.toString("ascii");
|
clientBanner = data.toString("ascii");
|
||||||
socket.write("security-types");
|
socket.write(Buffer.from([5, 30, 33]));
|
||||||
|
socket.write(Buffer.from([36, 2, 35]));
|
||||||
|
socket.once("data", (selection) => {
|
||||||
|
selectedSecurityType = selection[0];
|
||||||
|
socket.write("desktop-data");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const targetPort = await listen(target);
|
const targetPort = await listen(target);
|
||||||
@@ -58,12 +64,18 @@ describe("createMacosVncCompatibilityProxy", () => {
|
|||||||
"RFB 003.008\n",
|
"RFB 003.008\n",
|
||||||
);
|
);
|
||||||
client.write("RFB 003.008\n");
|
client.write("RFB 003.008\n");
|
||||||
expect((await read(client, 14)).toString("ascii")).toBe("security-types");
|
expect([...(await read(client, 2))]).toEqual([1, 2]);
|
||||||
|
client.write(Buffer.from([2]));
|
||||||
|
expect((await read(client, 12)).toString("ascii")).toBe("desktop-data");
|
||||||
expect(clientBanner).toBe("RFB 003.008\n");
|
expect(clientBanner).toBe("RFB 003.008\n");
|
||||||
|
expect(selectedSecurityType).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes standard RFB banners through unchanged", async () => {
|
it("passes standard RFB negotiation through unchanged", async () => {
|
||||||
const target = net.createServer((socket) => socket.write("RFB 003.008\n"));
|
const target = net.createServer((socket) => {
|
||||||
|
socket.write("RFB 003.008\n");
|
||||||
|
socket.once("data", () => socket.write(Buffer.from([2, 30, 2])));
|
||||||
|
});
|
||||||
const targetPort = await listen(target);
|
const targetPort = await listen(target);
|
||||||
closers.push(() => target.close());
|
closers.push(() => target.close());
|
||||||
const proxy = await createMacosVncCompatibilityProxy({
|
const proxy = await createMacosVncCompatibilityProxy({
|
||||||
@@ -76,5 +88,28 @@ describe("createMacosVncCompatibilityProxy", () => {
|
|||||||
const client = net.createConnection(proxy.port, "127.0.0.1");
|
const client = net.createConnection(proxy.port, "127.0.0.1");
|
||||||
closers.push(() => client.destroy());
|
closers.push(() => client.destroy());
|
||||||
expect((await read(client, 12)).toString("ascii")).toBe("RFB 003.008\n");
|
expect((await read(client, 12)).toString("ascii")).toBe("RFB 003.008\n");
|
||||||
|
client.write("RFB 003.008\n");
|
||||||
|
expect([...(await read(client, 3))]).toEqual([2, 30, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes Apple's security types through when VNC auth is unavailable", async () => {
|
||||||
|
const target = net.createServer((socket) => {
|
||||||
|
socket.write("RFB 003.889\n");
|
||||||
|
socket.once("data", () => socket.write(Buffer.from([2, 30, 33])));
|
||||||
|
});
|
||||||
|
const targetPort = await listen(target);
|
||||||
|
closers.push(() => target.close());
|
||||||
|
const proxy = await createMacosVncCompatibilityProxy({
|
||||||
|
targetHost: "127.0.0.1",
|
||||||
|
targetPort,
|
||||||
|
bindHost: "127.0.0.1",
|
||||||
|
});
|
||||||
|
closers.push(proxy.close);
|
||||||
|
|
||||||
|
const client = net.createConnection(proxy.port, "127.0.0.1");
|
||||||
|
closers.push(() => client.destroy());
|
||||||
|
expect((await read(client, 12)).toString("ascii")).toBe("RFB 003.008\n");
|
||||||
|
client.write("RFB 003.008\n");
|
||||||
|
expect([...(await read(client, 3))]).toEqual([2, 30, 33]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user