Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,14 @@ function trustMulti (subnets) {
return function trust (addr) {
if (!isip(addr)) return false

const ip = parseip(addr)
let ip = parseip(addr)

if (ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) {
// Canonicalize IPv4-mapped candidates to IPv4 so a mapped address
// cannot bypass the cross-family guard via the same-family path
ip = ip.toIPv4Address()
}

let ipconv
const kind = ip.kind()

Expand All @@ -276,14 +283,23 @@ function trustMulti (subnets) {
continue
}

if (!subnetisipv4 && !(subnetrange >= 96 && subnetip.isIPv4MappedAddress())) {
// IPv6 subnet only spans IPv4 when it is a mapped subnet whose
// prefix covers the ::ffff: marker; otherwise it must not match IPv4
continue
}

if (!ipconv) {
// Convert IP to match subnet IP kind
ipconv = subnetisipv4
? ip.toIPv4Address()
: ip.toIPv4MappedAddress()
// IPv4-mapped candidates were canonicalized to IPv4 above, so a
// cross-family IPv4 subnet is handled via the same-family path and
// this conversion only ever produces an IPv4-mapped address
ipconv = ip.toIPv4MappedAddress()
}

trusted = ipconv
} else if (kind === 'ipv6' && subnetip.isIPv4MappedAddress()) {
// A native IPv6 candidate cannot match an IPv4-mapped subnet
continue
}

if (trusted.match(subnetip, subnetrange)) {
Expand Down Expand Up @@ -312,6 +328,13 @@ function trustSingle (subnet) {
if (!isip(addr)) return false

let ip = parseip(addr)

if (ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) {
// Canonicalize IPv4-mapped candidates to IPv4 so a mapped address
// cannot bypass the cross-family guard via the same-family path
ip = ip.toIPv4Address()
}

const kind = ip.kind()

if (kind !== subnetkind) {
Expand All @@ -320,10 +343,19 @@ function trustSingle (subnet) {
return false
}

// Convert IP to match subnet IP kind
ip = subnetisipv4
? ip.toIPv4Address()
: ip.toIPv4MappedAddress()
if (!subnetisipv4 && !(subnetrange >= 96 && subnetip.isIPv4MappedAddress())) {
// IPv6 subnet only spans IPv4 when it is a mapped subnet whose
// prefix covers the ::ffff: marker; otherwise it must not match IPv4
return false
}

// IPv4-mapped candidates were canonicalized to IPv4 above, so a
// cross-family IPv4 subnet is handled via the same-family path and
// this conversion only ever produces an IPv4-mapped address
ip = ip.toIPv4MappedAddress()
} else if (kind === 'ipv6' && subnetip.isIPv4MappedAddress()) {
// A native IPv6 candidate cannot match an IPv4-mapped subnet
return false
}

return ip.match(subnetip, subnetrange)
Expand Down
35 changes: 35 additions & 0 deletions test/base.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,41 @@ test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped
t.assert.strictEqual(proxyaddr(req, ['::ffff:a00:2/122', '127.0.0.1']), '10.0.0.200')
})

test('when IPv4-mapped IPv6 trust subnet has a short prefix it should not trust arbitrary IPv4', function (t) {
const req = createReq('1.1.1.1', {
'x-forwarded-for': '6.6.6.6'
})
t.assert.strictEqual(proxyaddr(req, '::ffff:10.0.0.0/8'), '1.1.1.1')
})

test('when IPv6 trust subnet has zero leading bits it should not trust arbitrary IPv4', function (t) {
const req = createReq('1.1.1.1', {
'x-forwarded-for': '6.6.6.6'
})
t.assert.strictEqual(proxyaddr(req, '::/1'), '1.1.1.1')
})

test('when IPv4-mapped IPv6 trust subnet is used a native IPv6 address should not match', function (t) {
const req = createReq('2001:db8::1', {
'x-forwarded-for': '6.6.6.6'
})
t.assert.strictEqual(proxyaddr(req, '::ffff:10.0.0.0/104'), '2001:db8::1')
})

test('when IPv4-mapped IPv6 trust subnet in a list has a short prefix it should not trust arbitrary IPv4', function (t) {
const req = createReq('1.1.1.1', {
'x-forwarded-for': '6.6.6.6'
})
t.assert.strictEqual(proxyaddr(req, ['::ffff:10.0.0.0/8', '127.0.0.1']), '1.1.1.1')
})

test('when IPv4-mapped IPv6 trust subnet in a list is used a native IPv6 address should not match', function (t) {
const req = createReq('2001:db8::1', {
'x-forwarded-for': '6.6.6.6'
})
t.assert.strictEqual(proxyaddr(req, ['::ffff:10.0.0.0/104', 'fe80::/125']), '2001:db8::1')
})

test('when given predefined names should accept single pre-defined name', function (t) {
const req = createReq('fe80::1', {
'x-forwarded-for': '2002:c000:203::1, fe80::2'
Expand Down