Filter out private IPs in addr messages

Fixes #10
This commit is contained in:
Kagami Hiiragi 2015-02-27 15:54:05 +03:00
parent 84ad9eb2c2
commit d68e5adf0b
4 changed files with 171 additions and 84 deletions

View File

@ -94,3 +94,84 @@ exports.popkey = function(obj, key) {
delete obj[key];
return value;
};
// See https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
var IPv4_MAPPING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]);
exports.IPv4_MAPPING = IPv4_MAPPING;
// Very simple inet_pton(3) equivalent.
exports.inet_pton = function(str) {
var buf = new Buffer(16);
buf.fill(0);
// IPv4-mapped IPv6.
if (str.slice(0, 7) === "::ffff:") {
str = str.slice(7);
}
// IPv4.
if (str.indexOf(":") === -1) {
IPv4_MAPPING.copy(buf);
var octets = str.split(/\./g).map(function(o) {
assert(/^\d+$/.test(o), "Bad octet");
return parseInt(o, 10);
});
// Support short form from inet_aton(3) man page.
if (octets.length === 1) {
buf.writeUInt32BE(octets[0], 12);
} else {
// Check against 1000.bad.addr
octets.forEach(function(octet) {
assert(octet >= 0, "Bad IPv4 address");
assert(octet <= 255, "Bad IPv4 address");
});
if (octets.length === 2) {
buf[12] = octets[0];
buf[15] = octets[1];
} else if (octets.length === 3) {
buf[12] = octets[0];
buf[13] = octets[1];
buf[15] = octets[2];
} else if (octets.length === 4) {
buf[12] = octets[0];
buf[13] = octets[1];
buf[14] = octets[2];
buf[15] = octets[3];
} else {
throw new Error("Bad IPv4 address");
}
}
// IPv6.
} else {
var dgroups = str.split(/::/g);
// Check against 1::1::1
assert(dgroups.length <= 2, "Bad IPv6 address");
var groups = [];
var i;
if (dgroups[0]) {
groups.push.apply(groups, dgroups[0].split(/:/g));
}
if (dgroups.length === 2) {
if (dgroups[1]) {
var splitted = dgroups[1].split(/:/g);
var fill = 8 - (groups.length + splitted.length);
// Check against 1:1:1:1::1:1:1:1
assert(fill > 0, "Bad IPv6 address");
for (i = 0; i < fill; i++) {
groups.push(0);
}
groups.push.apply(groups, splitted);
} else {
// Check against 1:1:1:1:1:1:1:1::
assert(groups.length <= 7, "Bad IPv6 address");
}
} else {
// Check against 1:1:1
assert(groups.length === 8, "Bad IPv6 address");
}
for (i = 0; i < Math.min(groups.length, 8); i++) {
// Check against parseInt("127.0.0.1", 16) -> 295
assert(/^[0-9a-f]+$/.test(groups[i]), "Bad group");
buf.writeUInt16BE(parseInt(groups[i], 16), i * 2);
}
}
return buf;
};

View File

@ -33,6 +33,7 @@
"use strict";
var objectAssign = Object.assign || require("object-assign");
var bufferEqual = require("buffer-equal");
var assert = require("./_util").assert;
var structs = require("./structs");
var bmcrypto = require("./crypto");
@ -41,6 +42,8 @@ var util = require("./_util");
var message = structs.message;
var ServicesBitfield = structs.ServicesBitfield;
var IPv4_MAPPING = util.IPv4_MAPPING;
var inet_pton = util.inet_pton;
/**
* Try to get command of the given encoded message.
@ -226,6 +229,54 @@ var version = exports.version = {
},
};
var IPv6_LOOPBACK = new Buffer(
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
);
// Check whether given encoded IPv6 or IPv4-mapped IPv6 is in private
// network range. See
// <https://en.wikipedia.org/wiki/Reserved_IP_addresses> for details.
// TODO(Kagami): Do we also need to filter multicasts and other reserved
// ranges?
function isPrivateIp(buf) {
// IPv4.
if (bufferEqual(buf.slice(0, 12), IPv4_MAPPING)) {
buf = buf.slice(12);
if (buf[0] === 127) {
return true;
} else if (buf[0] === 10) {
return true;
} else if (buf[0] === 192 && buf[1] === 168) {
return true;
// XXX(Kagami): ignore:start and ignore:end doesn't ignore this for
// some reason. Probably related:
// <https://github.com/jshint/jshint/issues/1465>.
} else if (buf[0] === 172 && (buf[1] & 0xf0) === 0x10) {//jshint ignore:line
return true;
} else if (buf[0] === 169 && buf[1] === 254) {
return true;
} else {
return false;
}
// IPv6.
} else {
if (bufferEqual(buf, IPv6_LOOPBACK)) {
return true;
} else if (buf[0] === 0xfe && (buf[1] & 0xc0) === 0x80) {//jshint ignore:line
return true;
} else if ((buf[0] & 0xfe) === 0xfc) { // jshint ignore:line
return true;
} else {
return false;
}
}
}
// Helper to make it easier to filter out private IPs.
function checkAddrOpts(opts) {
return !isPrivateIp(inet_pton(opts.host));
}
/**
* `addr` message. Provide information on known nodes of the network.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#addr}
@ -260,13 +311,19 @@ var addr = exports.addr = {
decodePayload: function(buf) {
var decoded = structs.var_int.decode(buf);
var listLength = decoded.value;
// NOTE(Kagami): Check length before filtering private IPs because
// we shouldn't even receive them.
assert(listLength <= 1000, "Too many address entires");
var length = decoded.length + listLength * 38;
assert(buf.length >= length, "Buffer is too small");
var rest = decoded.rest;
var addrs = new Array(listLength);
var addrs = [];
var addrBuf;
for (var i = 0; i < listLength; i++) {
addrs[i] = structs.net_addr.decode(rest.slice(i*38, (i+1)*38));
addrBuf = rest.slice(i*38, (i+1)*38);
if (!isPrivateIp(addrBuf.slice(20, 36))) {
addrs.push(structs.net_addr.decode(addrBuf));
}
}
return {
addrs: addrs,
@ -291,6 +348,7 @@ var addr = exports.addr = {
* The same as [encode]{@link module:bitmessage/messages.addr.encode}.
*/
encodePayload: function(addrs) {
addrs = addrs.filter(checkAddrOpts);
assert(addrs.length <= 1000, "Too many address entires");
var addrBufs = addrs.map(structs.net_addr.encode);
var bufs = [structs.var_int.encode(addrs.length)].concat(addrBufs);

View File

@ -27,11 +27,14 @@
var objectAssign = Object.assign || require("object-assign");
var bufferEqual = require("buffer-equal");
var assert = require("./_util").assert;
var bmcrypto = require("./crypto");
var POW = require("./pow");
var util = require("./_util");
var assert = util.assert;
var IPv4_MAPPING = util.IPv4_MAPPING;
var inet_pton = util.inet_pton;
function isAscii(str) {
for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 127) {
@ -678,9 +681,6 @@ exports.var_int_list = {
},
};
// See https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
var IPv4_MAPPING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]);
// Very simple inet_ntop(3) equivalent.
function inet_ntop(buf) {
assert(buf.length === 16, "Bad buffer size");
@ -689,6 +689,7 @@ function inet_ntop(buf) {
return Array.prototype.join.call(buf.slice(12), ".");
// IPv6.
} else {
// TODO(Kagami): Join empty groups to make address looks nicer.
var groups = [];
for (var i = 0; i < 8; i++) {
groups.push(buf.readUInt16BE(i * 2, true).toString(16));
@ -697,83 +698,6 @@ function inet_ntop(buf) {
}
}
// Very simple inet_pton(3) equivalent.
function inet_pton(str) {
var buf = new Buffer(16);
buf.fill(0);
// IPv4-mapped IPv6.
if (str.slice(0, 7) === "::ffff:") {
str = str.slice(7);
}
// IPv4.
if (str.indexOf(":") === -1) {
IPv4_MAPPING.copy(buf);
var octets = str.split(/\./g).map(function(o) {
assert(/^\d+$/.test(o), "Bad octet");
return parseInt(o, 10);
});
// Support short form from inet_aton(3) man page.
if (octets.length === 1) {
buf.writeUInt32BE(octets[0], 12);
} else {
// Check against 1000.bad.addr
octets.forEach(function(octet) {
assert(octet >= 0, "Bad IPv4 address");
assert(octet <= 255, "Bad IPv4 address");
});
if (octets.length === 2) {
buf[12] = octets[0];
buf[15] = octets[1];
} else if (octets.length === 3) {
buf[12] = octets[0];
buf[13] = octets[1];
buf[15] = octets[2];
} else if (octets.length === 4) {
buf[12] = octets[0];
buf[13] = octets[1];
buf[14] = octets[2];
buf[15] = octets[3];
} else {
throw new Error("Bad IPv4 address");
}
}
// IPv6.
} else {
var dgroups = str.split(/::/g);
// Check against 1::1::1
assert(dgroups.length <= 2, "Bad IPv6 address");
var groups = [];
var i;
if (dgroups[0]) {
groups.push.apply(groups, dgroups[0].split(/:/g));
}
if (dgroups.length === 2) {
if (dgroups[1]) {
var splitted = dgroups[1].split(/:/g);
var fill = 8 - (groups.length + splitted.length);
// Check against 1:1:1:1::1:1:1:1
assert(fill > 0, "Bad IPv6 address");
for (i = 0; i < fill; i++) {
groups.push(0);
}
groups.push.apply(groups, splitted);
} else {
// Check against 1:1:1:1:1:1:1:1::
assert(groups.length <= 7, "Bad IPv6 address");
}
} else {
// Check against 1:1:1
assert(groups.length === 8, "Bad IPv6 address");
}
for (i = 0; i < Math.min(groups.length, 8); i++) {
// Check against parseInt("127.0.0.1", 16) -> 295
assert(/^[0-9a-f]+$/.test(groups[i]), "Bad group");
buf.writeUInt16BE(parseInt(groups[i], 16), i * 2);
}
}
return buf;
}
/**
* Network address.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Network_address}

26
test.js
View File

@ -556,9 +556,33 @@ describe("Message types", function() {
});
it("shouldn't encode/decode more than 1000 entires", function() {
expect(addr.encode.bind(null, Array(2000))).to.throw(/too many/i);
var addrs = new Array(1001);
var ip = {host: "1.2.3.4"};
for (var i = 0; i < 1001; i++) {
addrs[i] = ip;
}
expect(addr.encode.bind(null, addrs)).to.throw(/too many/i);
expect(addr.decodePayload.bind(null, var_int.encode(2000))).to.throw(/too many/i);
});
it("should filter out private IP ranges", function() {
expect(addr.encodePayload([
{host: "127.0.0.1", port: 1},
{host: "127.5.3.1", port: 2},
{host: "1.2.3.4", port: 3, time: new Date(1425034238202)},
{host: "192.168.15.20", port: 4},
{host: "10.10.10.10", port: 5},
{host: "172.17.42.1", port: 6},
{host: "::1", port: 7},
{host: "fe80::1:2:3", port: 8},
{host: "fc00::3:2:1", port: 9},
]).toString("hex")).to.equal("010000000054f04bfe00000001000000000000000100000000000000000000ffff010203040003");
var res = addr.decodePayload(Buffer("090000000054f04b9b00000001000000000000000100000000000000000000ffff7f00000100010000000054f04b9b00000001000000000000000100000000000000000000ffff7f05030100020000000054f04b9b00000001000000000000000100000000000000000000ffff0102030400030000000054f04b9b00000001000000000000000100000000000000000000ffffc0a80f1400040000000054f04b9b00000001000000000000000100000000000000000000ffff0a0a0a0a00050000000054f04b9b00000001000000000000000100000000000000000000ffffac112a0100060000000054f04b9b0000000100000000000000010000000000000000000000000000000100070000000054f04b9b000000010000000000000001fe80000000000000000000010002000300080000000054f04b9b000000010000000000000001fc0000000000000000000003000200010009", "hex"));
expect(res.addrs).to.have.length(1);
expect(res.addrs[0].host).to.equal("1.2.3.4");
expect(res.addrs[0].port).to.equal(3);
});
});
describe("inv", function() {