mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge pull request #423 from xandervr/security/proxy-loopback-only
Bind registry proxy to loopback only
This commit is contained in:
commit
00be33aa10
2 changed files with 73 additions and 3 deletions
|
|
@ -42,7 +42,7 @@ function getSafeChainProxyEnvironmentVariables() {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyUrl = `http://localhost:${state.port}`;
|
const proxyUrl = `http://127.0.0.1:${state.port}`;
|
||||||
const caCertPath = getCombinedCaBundlePath();
|
const caCertPath = getCombinedCaBundlePath();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -95,8 +95,11 @@ function createProxyServer() {
|
||||||
*/
|
*/
|
||||||
function startServer(server) {
|
function startServer(server) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Passing port 0 makes the OS assign an available port
|
// Bind to loopback only. Without an explicit host, Node listens on every
|
||||||
server.listen(0, () => {
|
// interface, turning the proxy into an unauthenticated forward proxy that
|
||||||
|
// anyone reachable on the network can use to hit the victim's localhost,
|
||||||
|
// intranet, or cloud metadata endpoints. Port 0 lets the OS pick a port.
|
||||||
|
server.listen(0, "127.0.0.1", () => {
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
if (address && typeof address === "object") {
|
if (address && typeof address === "object") {
|
||||||
state.port = address.port;
|
state.port = address.port;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { before, after, describe, it } from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import net from "node:net";
|
||||||
|
import os from "node:os";
|
||||||
|
import {
|
||||||
|
createSafeChainProxy,
|
||||||
|
mergeSafeChainProxyEnvironmentVariables,
|
||||||
|
} from "./registryProxy.js";
|
||||||
|
|
||||||
|
describe("registryProxy loopback binding", () => {
|
||||||
|
let proxy, proxyPort;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
proxy = createSafeChainProxy();
|
||||||
|
await proxy.startServer();
|
||||||
|
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
|
||||||
|
proxyPort = parseInt(new URL(envVars.HTTPS_PROXY).port, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await proxy.stopServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("advertises a loopback HTTPS_PROXY URL", () => {
|
||||||
|
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
|
||||||
|
const hostname = new URL(envVars.HTTPS_PROXY).hostname;
|
||||||
|
assert.ok(
|
||||||
|
hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost",
|
||||||
|
`expected loopback hostname, got ${hostname}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refuses connections on non-loopback interfaces", async () => {
|
||||||
|
const externalAddrs = Object.values(os.networkInterfaces())
|
||||||
|
.flat()
|
||||||
|
.filter((iface) => iface && iface.family === "IPv4" && !iface.internal)
|
||||||
|
.map((iface) => iface.address);
|
||||||
|
|
||||||
|
if (externalAddrs.length === 0) {
|
||||||
|
// No non-loopback interface available (e.g. locked-down CI) - skip.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const addr of externalAddrs) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const sock = net.createConnection({ host: addr, port: proxyPort });
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
sock.destroy();
|
||||||
|
resolve(); // Filtered / dropped is also fine - we just don't want success.
|
||||||
|
}, 500);
|
||||||
|
sock.once("connect", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
sock.destroy();
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`proxy accepted a connection on non-loopback ${addr}:${proxyPort}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
sock.once("error", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue