Skip to content

Commit 0435e6e

Browse files
committedJul 15, 2022
[security] Fix same host check for ws+unix: redirects
Drop the `Authorization` and `Cookie` headers if the original request for the opening handshake is sent to an IPC server and the client is redirected to a TCP server (ws+unix: to ws: or wss:), and vice versa (ws: or wss: to ws+unix). Also drop the `Authorization` and `Cookie` headers if the original request for the opening handshake is sent to an IPC server and the client is redirected to another IPC server. Refs: 6946f5fe
1 parent 4271f07 commit 0435e6e

File tree

2 files changed

+237
-13
lines changed

2 files changed

+237
-13
lines changed
 

‎lib/websocket.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -684,8 +684,11 @@ function initAsClient(websocket, address, protocols, options) {
684684

685685
if (opts.followRedirects) {
686686
if (websocket._redirects === 0) {
687+
websocket._originalUnixSocket = isUnixSocket;
687688
websocket._originalSecure = isSecure;
688-
websocket._originalHost = parsedUrl.host;
689+
websocket._originalHostOrSocketPath = isUnixSocket
690+
? opts.socketPath
691+
: parsedUrl.host;
689692

690693
const headers = options && options.headers;
691694

@@ -701,7 +704,13 @@ function initAsClient(websocket, address, protocols, options) {
701704
}
702705
}
703706
} else {
704-
const isSameHost = parsedUrl.host === websocket._originalHost;
707+
const isSameHost = isUnixSocket
708+
? websocket._originalUnixSocket
709+
? opts.socketPath === websocket._originalHostOrSocketPath
710+
: false
711+
: websocket._originalUnixSocket
712+
? false
713+
: parsedUrl.host === websocket._originalHostOrSocketPath;
705714

706715
if (!isSameHost || (websocket._originalSecure && !isSecure)) {
707716
//

‎test/websocket.test.js

+226-11
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ const assert = require('assert');
66
const crypto = require('crypto');
77
const https = require('https');
88
const http = require('http');
9+
const path = require('path');
910
const net = require('net');
1011
const tls = require('tls');
12+
const os = require('os');
1113
const fs = require('fs');
1214
const { URL } = require('url');
1315

@@ -1201,7 +1203,9 @@ describe('WebSocket', () => {
12011203
});
12021204
});
12031205

1204-
it('drops the Authorization, Cookie, and Host headers', (done) => {
1206+
it('drops the Authorization, Cookie and Host headers (1/4)', (done) => {
1207+
// Test the `ws:` to `ws:` case.
1208+
12051209
const wss = new WebSocket.Server({ port: 0 }, () => {
12061210
const port = wss.address().port;
12071211

@@ -1211,21 +1215,25 @@ describe('WebSocket', () => {
12111215
);
12121216
});
12131217

1218+
const headers = {
1219+
authorization: 'Basic Zm9vOmJhcg==',
1220+
cookie: 'foo=bar',
1221+
host: 'foo'
1222+
};
1223+
12141224
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
1215-
headers: {
1216-
Authorization: 'Basic Zm9vOmJhcg==',
1217-
Cookie: 'foo=bar',
1218-
Host: 'foo'
1219-
},
1220-
followRedirects: true
1225+
followRedirects: true,
1226+
headers
12211227
});
12221228

1229+
const firstRequest = ws._req;
1230+
12231231
assert.strictEqual(
1224-
ws._req.getHeader('Authorization'),
1225-
'Basic Zm9vOmJhcg=='
1232+
firstRequest.getHeader('Authorization'),
1233+
headers.authorization
12261234
);
1227-
assert.strictEqual(ws._req.getHeader('Cookie'), 'foo=bar');
1228-
assert.strictEqual(ws._req.getHeader('Host'), 'foo');
1235+
assert.strictEqual(firstRequest.getHeader('Cookie'), headers.cookie);
1236+
assert.strictEqual(firstRequest.getHeader('Host'), headers.host);
12291237

12301238
ws.on('close', (code) => {
12311239
assert.strictEqual(code, 1005);
@@ -1243,8 +1251,215 @@ describe('WebSocket', () => {
12431251
req.headers.host,
12441252
`localhost:${wss.address().port}`
12451253
);
1254+
1255+
ws.close();
1256+
});
1257+
});
1258+
1259+
it('drops the Authorization, Cookie and Host headers (2/4)', function (done) {
1260+
if (process.platform === 'win32') return this.skip();
1261+
1262+
// Test the `ws:` to `ws+unix:` case.
1263+
1264+
const socketPath = path.join(
1265+
os.tmpdir(),
1266+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1267+
);
1268+
1269+
server.once('upgrade', (req, socket) => {
1270+
socket.end(
1271+
`HTTP/1.1 302 Found\r\nLocation: ws+unix://${socketPath}\r\n\r\n`
1272+
);
1273+
});
1274+
1275+
const redirectedServer = http.createServer();
1276+
const wss = new WebSocket.Server({ server: redirectedServer });
1277+
1278+
wss.on('connection', (ws, req) => {
1279+
assert.strictEqual(req.headers.authorization, undefined);
1280+
assert.strictEqual(req.headers.cookie, undefined);
1281+
assert.strictEqual(req.headers.host, 'localhost');
1282+
1283+
ws.close();
1284+
});
1285+
1286+
redirectedServer.listen(socketPath, () => {
1287+
const headers = {
1288+
authorization: 'Basic Zm9vOmJhcg==',
1289+
cookie: 'foo=bar',
1290+
host: 'foo'
1291+
};
1292+
1293+
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
1294+
followRedirects: true,
1295+
headers
1296+
});
1297+
1298+
const firstRequest = ws._req;
1299+
1300+
assert.strictEqual(
1301+
firstRequest.getHeader('Authorization'),
1302+
headers.authorization
1303+
);
1304+
assert.strictEqual(firstRequest.getHeader('Cookie'), headers.cookie);
1305+
assert.strictEqual(firstRequest.getHeader('Host'), headers.host);
1306+
1307+
ws.on('close', (code) => {
1308+
assert.strictEqual(code, 1005);
1309+
assert.strictEqual(ws.url, `ws+unix://${socketPath}`);
1310+
assert.strictEqual(ws._redirects, 1);
1311+
1312+
redirectedServer.close(done);
1313+
});
1314+
});
1315+
});
1316+
1317+
it('drops the Authorization, Cookie and Host headers (3/4)', function (done) {
1318+
if (process.platform === 'win32') return this.skip();
1319+
1320+
// Test the `ws+unix:` to `ws+unix:` case.
1321+
1322+
const redirectingServerSocketPath = path.join(
1323+
os.tmpdir(),
1324+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1325+
);
1326+
const redirectedServerSocketPath = path.join(
1327+
os.tmpdir(),
1328+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1329+
);
1330+
1331+
const redirectingServer = http.createServer();
1332+
1333+
redirectingServer.on('upgrade', (req, socket) => {
1334+
socket.end(
1335+
'HTTP/1.1 302 Found\r\n' +
1336+
`Location: ws+unix://${redirectedServerSocketPath}\r\n\r\n`
1337+
);
1338+
});
1339+
1340+
const redirectedServer = http.createServer();
1341+
const wss = new WebSocket.Server({ server: redirectedServer });
1342+
1343+
wss.on('connection', (ws, req) => {
1344+
assert.strictEqual(req.headers.authorization, undefined);
1345+
assert.strictEqual(req.headers.cookie, undefined);
1346+
assert.strictEqual(req.headers.host, 'localhost');
1347+
1348+
ws.close();
1349+
});
1350+
1351+
redirectingServer.listen(redirectingServerSocketPath, listening);
1352+
redirectedServer.listen(redirectedServerSocketPath, listening);
1353+
1354+
let callCount = 0;
1355+
1356+
function listening() {
1357+
if (++callCount !== 2) return;
1358+
1359+
const headers = {
1360+
authorization: 'Basic Zm9vOmJhcg==',
1361+
cookie: 'foo=bar',
1362+
host: 'foo'
1363+
};
1364+
1365+
const ws = new WebSocket(`ws+unix://${redirectingServerSocketPath}`, {
1366+
followRedirects: true,
1367+
headers
1368+
});
1369+
1370+
const firstRequest = ws._req;
1371+
1372+
assert.strictEqual(
1373+
firstRequest.getHeader('Authorization'),
1374+
headers.authorization
1375+
);
1376+
assert.strictEqual(firstRequest.getHeader('Cookie'), headers.cookie);
1377+
assert.strictEqual(firstRequest.getHeader('Host'), headers.host);
1378+
1379+
ws.on('close', (code) => {
1380+
assert.strictEqual(code, 1005);
1381+
assert.strictEqual(
1382+
ws.url,
1383+
`ws+unix://${redirectedServerSocketPath}`
1384+
);
1385+
assert.strictEqual(ws._redirects, 1);
1386+
1387+
redirectingServer.close();
1388+
redirectedServer.close(done);
1389+
});
1390+
}
1391+
});
1392+
1393+
it('drops the Authorization, Cookie and Host headers (4/4)', function (done) {
1394+
if (process.platform === 'win32') return this.skip();
1395+
1396+
// Test the `ws+unix:` to `ws:` case.
1397+
1398+
const redirectingServer = http.createServer();
1399+
const redirectedServer = http.createServer();
1400+
const wss = new WebSocket.Server({ server: redirectedServer });
1401+
1402+
wss.on('connection', (ws, req) => {
1403+
assert.strictEqual(req.headers.authorization, undefined);
1404+
assert.strictEqual(req.headers.cookie, undefined);
1405+
assert.strictEqual(
1406+
req.headers.host,
1407+
`localhost:${redirectedServer.address().port}`
1408+
);
1409+
12461410
ws.close();
12471411
});
1412+
1413+
const socketPath = path.join(
1414+
os.tmpdir(),
1415+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1416+
);
1417+
1418+
redirectingServer.listen(socketPath, listening);
1419+
redirectedServer.listen(0, listening);
1420+
1421+
let callCount = 0;
1422+
1423+
function listening() {
1424+
if (++callCount !== 2) return;
1425+
1426+
const port = redirectedServer.address().port;
1427+
1428+
redirectingServer.on('upgrade', (req, socket) => {
1429+
socket.end(
1430+
`HTTP/1.1 302 Found\r\nLocation: ws://localhost:${port}\r\n\r\n`
1431+
);
1432+
});
1433+
1434+
const headers = {
1435+
authorization: 'Basic Zm9vOmJhcg==',
1436+
cookie: 'foo=bar',
1437+
host: 'foo'
1438+
};
1439+
1440+
const ws = new WebSocket(`ws+unix://${socketPath}`, {
1441+
followRedirects: true,
1442+
headers
1443+
});
1444+
1445+
const firstRequest = ws._req;
1446+
1447+
assert.strictEqual(
1448+
firstRequest.getHeader('Authorization'),
1449+
headers.authorization
1450+
);
1451+
assert.strictEqual(firstRequest.getHeader('Cookie'), headers.cookie);
1452+
assert.strictEqual(firstRequest.getHeader('Host'), headers.host);
1453+
1454+
ws.on('close', (code) => {
1455+
assert.strictEqual(code, 1005);
1456+
assert.strictEqual(ws.url, `ws://localhost:${port}/`);
1457+
assert.strictEqual(ws._redirects, 1);
1458+
1459+
redirectingServer.close();
1460+
redirectedServer.close(done);
1461+
});
1462+
}
12481463
});
12491464
});
12501465
});

0 commit comments

Comments
 (0)
Please sign in to comment.