Skip to content

Commit 4272c43

Browse files
fix(NODE-5200): relax SRV record validation to account for a dot suffix (#3640)
Co-authored-by: Neal Beeken <neal.beeken@mongodb.com>
1 parent c11e2cf commit 4272c43

File tree

4 files changed

+71
-31
lines changed

4 files changed

+71
-31
lines changed

src/connection_string.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
HostAddress,
3636
isRecord,
3737
makeClientMetadata,
38+
matchesParentDomain,
3839
parseInteger,
3940
setDifference
4041
} from './utils';
@@ -47,21 +48,6 @@ const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSe
4748
const LB_DIRECT_CONNECTION_ERROR =
4849
'loadBalanced option not supported when directConnection is provided';
4950

50-
/**
51-
* Determines whether a provided address matches the provided parent domain in order
52-
* to avoid certain attack vectors.
53-
*
54-
* @param srvAddress - The address to check against a domain
55-
* @param parentDomain - The domain to check the provided address against
56-
* @returns Whether the provided address matches the parent domain
57-
*/
58-
function matchesParentDomain(srvAddress: string, parentDomain: string): boolean {
59-
const regex = /^.*?\./;
60-
const srv = `.${srvAddress.replace(regex, '')}`;
61-
const parent = `.${parentDomain.replace(regex, '')}`;
62-
return srv.endsWith(parent);
63-
}
64-
6551
/**
6652
* Lookup a `mongodb+srv` connection string, combine the parts and reparse it as a normal
6753
* connection string.

src/sdam/srv_polling.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,7 @@ import { clearTimeout, setTimeout } from 'timers';
44
import { MongoRuntimeError } from '../error';
55
import { Logger, LoggerOptions } from '../logger';
66
import { TypedEventEmitter } from '../mongo_types';
7-
import { HostAddress } from '../utils';
8-
9-
/**
10-
* Determines whether a provided address matches the provided parent domain in order
11-
* to avoid certain attack vectors.
12-
*
13-
* @param srvAddress - The address to check against a domain
14-
* @param parentDomain - The domain to check the provided address against
15-
* @returns Whether the provided address matches the parent domain
16-
*/
17-
function matchesParentDomain(srvAddress: string, parentDomain: string): boolean {
18-
const regex = /^.*?\./;
19-
const srv = `.${srvAddress.replace(regex, '')}`;
20-
const parent = `.${parentDomain.replace(regex, '')}`;
21-
return srv.endsWith(parent);
22-
}
7+
import { HostAddress, matchesParentDomain } from '../utils';
238

249
/**
2510
* @internal

src/utils.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,3 +1430,29 @@ export function parseUnsignedInteger(value: unknown): number | null {
14301430

14311431
return parsedInt != null && parsedInt >= 0 ? parsedInt : null;
14321432
}
1433+
1434+
/**
1435+
* Determines whether a provided address matches the provided parent domain.
1436+
*
1437+
* If a DNS server were to become compromised SRV records would still need to
1438+
* advertise addresses that are under the same domain as the srvHost.
1439+
*
1440+
* @param address - The address to check against a domain
1441+
* @param srvHost - The domain to check the provided address against
1442+
* @returns Whether the provided address matches the parent domain
1443+
*/
1444+
export function matchesParentDomain(address: string, srvHost: string): boolean {
1445+
// Remove trailing dot if exists on either the resolved address or the srv hostname
1446+
const normalizedAddress = address.endsWith('.') ? address.slice(0, address.length - 1) : address;
1447+
const normalizedSrvHost = srvHost.endsWith('.') ? srvHost.slice(0, srvHost.length - 1) : srvHost;
1448+
1449+
const allCharacterBeforeFirstDot = /^.*?\./;
1450+
// Remove all characters before first dot
1451+
// Add leading dot back to string so
1452+
// an srvHostDomain = '.trusted.site'
1453+
// will not satisfy an addressDomain that endsWith '.fake-trusted.site'
1454+
const addressDomain = `.${normalizedAddress.replace(allCharacterBeforeFirstDot, '')}`;
1455+
const srvHostDomain = `.${normalizedSrvHost.replace(allCharacterBeforeFirstDot, '')}`;
1456+
1457+
return addressDomain.endsWith(srvHostDomain);
1458+
}

test/unit/utils.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
HostAddress,
1212
isHello,
1313
List,
14+
matchesParentDomain,
1415
maybeCallback,
1516
MongoDBNamespace,
1617
shuffle
@@ -858,4 +859,46 @@ describe('driver utils', function () {
858859
it(title, () => expect(compareObjectId(oid1, oid2)).to.equal(result));
859860
}
860861
});
862+
863+
describe('matchesParentDomain()', () => {
864+
const exampleSrvName = 'i-love-javascript.mongodb.io';
865+
const exampleSrvNameWithDot = 'i-love-javascript.mongodb.io.';
866+
const exampleHostNameWithoutDot = 'i-love-javascript-00.mongodb.io';
867+
const exampleHostNamesWithDot = exampleHostNameWithoutDot + '.';
868+
const exampleHostNamThatDoNotMatchParent = 'i-love-javascript-00.evil-mongodb.io';
869+
const exampleHostNamThatDoNotMatchParentWithDot = 'i-love-javascript-00.evil-mongodb.io.';
870+
871+
context('when address does not match parent domain', () => {
872+
it('without a trailing dot returns false', () => {
873+
expect(matchesParentDomain(exampleHostNamThatDoNotMatchParent, exampleSrvName)).to.be.false;
874+
});
875+
876+
it('with a trailing dot returns false', () => {
877+
expect(matchesParentDomain(exampleHostNamThatDoNotMatchParentWithDot, exampleSrvName)).to.be
878+
.false;
879+
});
880+
});
881+
882+
context('when addresses in SRV record end with a dot', () => {
883+
it('accepts address since it is considered to still match the parent domain', () => {
884+
expect(matchesParentDomain(exampleHostNamesWithDot, exampleSrvName)).to.be.true;
885+
});
886+
});
887+
888+
context('when SRV host ends with a dot', () => {
889+
it('accepts address if it ends with a dot', () => {
890+
expect(matchesParentDomain(exampleHostNamesWithDot, exampleSrvNameWithDot)).to.be.true;
891+
});
892+
893+
it('accepts address if it does not end with a dot', () => {
894+
expect(matchesParentDomain(exampleHostNameWithoutDot, exampleSrvName)).to.be.true;
895+
});
896+
});
897+
898+
context('when addresses in SRV record end without dots', () => {
899+
it('accepts address since it matches the parent domain', () => {
900+
expect(matchesParentDomain(exampleHostNamesWithDot, exampleSrvName)).to.be.true;
901+
});
902+
});
903+
});
861904
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy