Skip to content

Commit bc8bd34

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 0ae302a commit bc8bd34

File tree

2 files changed

+231
-3
lines changed

2 files changed

+231
-3
lines changed
 

‎lib/websocket.js

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

772772
if (opts.followRedirects) {
773773
if (websocket._redirects === 0) {
774+
websocket._originalUnixSocket = isUnixSocket;
774775
websocket._originalSecure = isSecure;
775-
websocket._originalHost = parsedUrl.host;
776+
websocket._originalHostOrSocketPath = isUnixSocket
777+
? opts.socketPath
778+
: parsedUrl.host;
776779

777780
const headers = options && options.headers;
778781

@@ -788,7 +791,13 @@ function initAsClient(websocket, address, protocols, options) {
788791
}
789792
}
790793
} else if (websocket.listenerCount('redirect') === 0) {
791-
const isSameHost = parsedUrl.host === websocket._originalHost;
794+
const isSameHost = isUnixSocket
795+
? websocket._originalUnixSocket
796+
? opts.socketPath === websocket._originalHostOrSocketPath
797+
: false
798+
: websocket._originalUnixSocket
799+
? false
800+
: parsedUrl.host === websocket._originalHostOrSocketPath;
792801

793802
if (!isSameHost || (websocket._originalSecure && !isSecure)) {
794803
//

‎test/websocket.test.js

+220-1
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

@@ -1477,7 +1479,9 @@ describe('WebSocket', () => {
14771479
});
14781480
});
14791481

1480-
it('drops the Authorization, Cookie and Host headers', (done) => {
1482+
it('drops the Authorization, Cookie and Host headers (1/4)', (done) => {
1483+
// Test the `ws:` to `ws:` case.
1484+
14811485
const wss = new WebSocket.Server({ port: 0 }, () => {
14821486
const port = wss.address().port;
14831487

@@ -1531,6 +1535,221 @@ describe('WebSocket', () => {
15311535
ws.close();
15321536
});
15331537
});
1538+
1539+
it('drops the Authorization, Cookie and Host headers (2/4)', function (done) {
1540+
if (process.platform === 'win32') return this.skip();
1541+
1542+
// Test the `ws:` to `ws+unix:` case.
1543+
1544+
const socketPath = path.join(
1545+
os.tmpdir(),
1546+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1547+
);
1548+
1549+
server.once('upgrade', (req, socket) => {
1550+
socket.end(
1551+
`HTTP/1.1 302 Found\r\nLocation: ws+unix://${socketPath}\r\n\r\n`
1552+
);
1553+
});
1554+
1555+
const redirectedServer = http.createServer();
1556+
const wss = new WebSocket.Server({ server: redirectedServer });
1557+
1558+
wss.on('connection', (ws, req) => {
1559+
assert.strictEqual(req.headers.authorization, undefined);
1560+
assert.strictEqual(req.headers.cookie, undefined);
1561+
assert.strictEqual(req.headers.host, 'localhost');
1562+
1563+
ws.close();
1564+
});
1565+
1566+
redirectedServer.listen(socketPath, () => {
1567+
const headers = {
1568+
authorization: 'Basic Zm9vOmJhcg==',
1569+
cookie: 'foo=bar',
1570+
host: 'foo'
1571+
};
1572+
1573+
const ws = new WebSocket(
1574+
`ws://localhost:${server.address().port}`,
1575+
{ followRedirects: true, headers }
1576+
);
1577+
1578+
const firstRequest = ws._req;
1579+
1580+
assert.strictEqual(
1581+
firstRequest.getHeader('Authorization'),
1582+
headers.authorization
1583+
);
1584+
assert.strictEqual(
1585+
firstRequest.getHeader('Cookie'),
1586+
headers.cookie
1587+
);
1588+
assert.strictEqual(firstRequest.getHeader('Host'), headers.host);
1589+
1590+
ws.on('close', (code) => {
1591+
assert.strictEqual(code, 1005);
1592+
assert.strictEqual(ws.url, `ws+unix://${socketPath}`);
1593+
assert.strictEqual(ws._redirects, 1);
1594+
1595+
redirectedServer.close(done);
1596+
});
1597+
});
1598+
});
1599+
1600+
it('drops the Authorization, Cookie and Host headers (3/4)', function (done) {
1601+
if (process.platform === 'win32') return this.skip();
1602+
1603+
// Test the `ws+unix:` to `ws+unix:` case.
1604+
1605+
const redirectingServerSocketPath = path.join(
1606+
os.tmpdir(),
1607+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1608+
);
1609+
const redirectedServerSocketPath = path.join(
1610+
os.tmpdir(),
1611+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1612+
);
1613+
1614+
const redirectingServer = http.createServer();
1615+
1616+
redirectingServer.on('upgrade', (req, socket) => {
1617+
socket.end(
1618+
'HTTP/1.1 302 Found\r\n' +
1619+
`Location: ws+unix://${redirectedServerSocketPath}\r\n\r\n`
1620+
);
1621+
});
1622+
1623+
const redirectedServer = http.createServer();
1624+
const wss = new WebSocket.Server({ server: redirectedServer });
1625+
1626+
wss.on('connection', (ws, req) => {
1627+
assert.strictEqual(req.headers.authorization, undefined);
1628+
assert.strictEqual(req.headers.cookie, undefined);
1629+
assert.strictEqual(req.headers.host, 'localhost');
1630+
1631+
ws.close();
1632+
});
1633+
1634+
redirectingServer.listen(redirectingServerSocketPath, listening);
1635+
redirectedServer.listen(redirectedServerSocketPath, listening);
1636+
1637+
let callCount = 0;
1638+
1639+
function listening() {
1640+
if (++callCount !== 2) return;
1641+
1642+
const headers = {
1643+
authorization: 'Basic Zm9vOmJhcg==',
1644+
cookie: 'foo=bar',
1645+
host: 'foo'
1646+
};
1647+
1648+
const ws = new WebSocket(
1649+
`ws+unix://${redirectingServerSocketPath}`,
1650+
{ followRedirects: true, headers }
1651+
);
1652+
1653+
const firstRequest = ws._req;
1654+
1655+
assert.strictEqual(
1656+
firstRequest.getHeader('Authorization'),
1657+
headers.authorization
1658+
);
1659+
assert.strictEqual(
1660+
firstRequest.getHeader('Cookie'),
1661+
headers.cookie
1662+
);
1663+
assert.strictEqual(firstRequest.getHeader('Host'), headers.host);
1664+
1665+
ws.on('close', (code) => {
1666+
assert.strictEqual(code, 1005);
1667+
assert.strictEqual(
1668+
ws.url,
1669+
`ws+unix://${redirectedServerSocketPath}`
1670+
);
1671+
assert.strictEqual(ws._redirects, 1);
1672+
1673+
redirectingServer.close();
1674+
redirectedServer.close(done);
1675+
});
1676+
}
1677+
});
1678+
1679+
it('drops the Authorization, Cookie and Host headers (4/4)', function (done) {
1680+
if (process.platform === 'win32') return this.skip();
1681+
1682+
// Test the `ws+unix:` to `ws:` case.
1683+
1684+
const redirectingServer = http.createServer();
1685+
const redirectedServer = http.createServer();
1686+
const wss = new WebSocket.Server({ server: redirectedServer });
1687+
1688+
wss.on('connection', (ws, req) => {
1689+
assert.strictEqual(req.headers.authorization, undefined);
1690+
assert.strictEqual(req.headers.cookie, undefined);
1691+
assert.strictEqual(
1692+
req.headers.host,
1693+
`localhost:${redirectedServer.address().port}`
1694+
);
1695+
1696+
ws.close();
1697+
});
1698+
1699+
const socketPath = path.join(
1700+
os.tmpdir(),
1701+
`ws.${crypto.randomBytes(16).toString('hex')}.sock`
1702+
);
1703+
1704+
redirectingServer.listen(socketPath, listening);
1705+
redirectedServer.listen(0, listening);
1706+
1707+
let callCount = 0;
1708+
1709+
function listening() {
1710+
if (++callCount !== 2) return;
1711+
1712+
const port = redirectedServer.address().port;
1713+
1714+
redirectingServer.on('upgrade', (req, socket) => {
1715+
socket.end(
1716+
`HTTP/1.1 302 Found\r\nLocation: ws://localhost:${port}\r\n\r\n`
1717+
);
1718+
});
1719+
1720+
const headers = {
1721+
authorization: 'Basic Zm9vOmJhcg==',
1722+
cookie: 'foo=bar',
1723+
host: 'foo'
1724+
};
1725+
1726+
const ws = new WebSocket(`ws+unix://${socketPath}`, {
1727+
followRedirects: true,
1728+
headers
1729+
});
1730+
1731+
const firstRequest = ws._req;
1732+
1733+
assert.strictEqual(
1734+
firstRequest.getHeader('Authorization'),
1735+
headers.authorization
1736+
);
1737+
assert.strictEqual(
1738+
firstRequest.getHeader('Cookie'),
1739+
headers.cookie
1740+
);
1741+
assert.strictEqual(firstRequest.getHeader('Host'), headers.host);
1742+
1743+
ws.on('close', (code) => {
1744+
assert.strictEqual(code, 1005);
1745+
assert.strictEqual(ws.url, `ws://localhost:${port}/`);
1746+
assert.strictEqual(ws._redirects, 1);
1747+
1748+
redirectingServer.close();
1749+
redirectedServer.close(done);
1750+
});
1751+
}
1752+
});
15341753
});
15351754

15361755
describe("If there is at least one 'redirect' event listener", () => {

0 commit comments

Comments
 (0)
Please sign in to comment.