123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- /**
- * TraCINg-Server - Gathering and visualizing cyber incidents on the world
- *
- * Copyright 2013 Matthias Gazzari, Annemarie Mattmann, André Wolski
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /*
- * This module provides methods of receiving POST data and storing them in a
- * database. Additionally it is send via socket.io to every client connected to
- * the server.
- */
- // load node modules
- var City = require("geoip").City; // requires 'npm install geoip' and on ubuntu 'aptitude install libgeoip-dev'
- var city = new City("GeoLiteCity.dat");
- var stream = require("stream");
- var Parser = require("newline-json").Parser;
- var Chance = require("chance");
- // load internal modules
- //var db = require("./db/db.js");
- var db = require("./db/database.js");
- var debugInstance = 0;
- var Debug = function(){
- var output = "";
- var instance = debugInstance++;
- this.get = function(){
- return output;
- };
- this.add = function(text){
- text = "PostHandler["+instance+"]: " + text;
- console.log(text);
- output += text + "\n";
- };
- };
- var cacheIPLocations = {};
- function lookUpIp(ip){
- cacheIPLocations[ip] = cacheIPLocations[ip] || city.lookupSync(ip);
- return cacheIPLocations[ip];
- }
- /* Countermeasure logic */
- var TIMESLOT_SECONDS = 10;
- //var WINDOW_MAX_TIMESLOTS = 60*60;
- var WINDOW_MAX_TIMESLOTS = 60;
- var monitor_count = {};
- var window_list = [];
- var ALARM_RATIO_THRESHOLD = 1.89;
- function init_stats_window(initial_value) {
- var debug = new Debug();
- debug.add("initiating window with ratio " + initial_value);
- for(var x=0; x < WINDOW_MAX_TIMESLOTS; ++x){
- window_list[x] = initial_value;
- }
- }
- function update_slot(monitor_id) {
- if (monitor_id in monitor_count) {
- monitor_count[monitor_id] += 1;
- } else {
- monitor_count[monitor_id] = 1;
- }
- }
- function update_window() {
- var debug = new Debug();
- var timeslot = Math.round(new Date().getTime() / (1000 * TIMESLOT_SECONDS)) % WINDOW_MAX_TIMESLOTS;
- var amount_monitors = Object.keys(monitor_count).length;
- var amount_attacks = 0;
- for (var monitor_id in monitor_count) {
- amount_attacks += monitor_count[monitor_id];
- }
- if (amount_monitors == 0 || amount_attacks == 0) {
- debug.add("No monitors counted, setting to last value");
- if (timeslot - 1 > 0) {
- window_list[timeslot] = window_list[timeslot - 1];
- } else {
- window_list[timeslot] = window_list[WINDOW_MAX_TIMESLOTS - 1];
- }
- } else {
- window_list[timeslot] = amount_attacks / amount_monitors;
- debug.add("Timeslot / attacks / monitors / ratio: "+ timeslot +" / "+ amount_attacks +" / "+ amount_monitors +
- " / "+ window_list[timeslot]);
- }
- // reset monitor counts
- monitor_count = {};
- var sum_ratio = 0;
- for (var key in window_list) {
- sum_ratio += window_list[key];
- }
- var ratio = sum_ratio / WINDOW_MAX_TIMESLOTS;
- debug.add("SUM(ratio) / ( SUM(ratio)/cnt ): " + sum_ratio + " / " + ratio);
- if (ratio < ALARM_RATIO_THRESHOLD) {
- debug.add("!!!!!!!!! ALAAAARM!!!! Ratio is too low! we get attacked!!!");
- }
- }
- var interval_update_window = setInterval(update_window, TIMESLOT_SECONDS * 1000);
- var data_empty = {
- sensorname: "",
- src: {
- ip : "",
- port : ""
- },
- dst: {
- ip : "",
- port : ""
- },
- type: ""
- };
- function addEntry(data_new) {
- var debug = new Debug();
- if (data_new === undefined) {
- debug.add("NO data given: " + data_new);
- data_new = data_empty;
- }
- if (typeof io == 'undefined') {
- //debug.add("no io, no emit -> I quit");
- return;
- }
- var chance = new Chance();
- // Add a random entry to tracing. This will affect source and destination port,
- // all other values wil remain constant.
- ip_src = data_new.src.ip || chance.ip();
- ip_dst = data_new.dst.ip || chance.ip();
- var data = {
- sensorname: data_new.sensorname || "RandomMonitor",
- sensortype: "RandomType",
- src: {
- ip: ip_src,
- port: data_new.src.port || chance.integer({min: 0, max: 65535}),
- ll: [0,0],
- country: "",
- cc: 0,
- city: ""
- },
- dst: {
- ip: ip_dst,
- port: data_new.dst.port || chance.integer({min: 0, max: 65535}),
- ll: [0,0],
- country: "",
- cc: 0,
- city: ""
- },
- type: data_new.type || 11,
- log: null,
- md5sum: "0123456789",
- date: new Date(),
- authorized: false
- };
- debug.add("adding, name/ip/port: " + data.sensorname + " / " + data.src.ip + " / " +
- data.dst.ip + " / " + data.src.port + " / " + data.dst.port);
- ipa = lookUpIp(ip_src);
- if(ipa){
- data.src["ll"] = [ipa.longitude, ipa.latitude];
- data.src["country"] = ipa.country_name;
- data.src["cc"] = ipa.country_code;
- data.src["city"] = ipa.city || "";
- }
- ipd = lookUpIp(ip_dst);
- if(ipd){
- data.dst["ll"] = [ipd.longitude, ipd.latitude];
- data.dst["country"] = ipd.country_name;
- data.dst["cc"] = ipd.country_code;
- data.dst["city"] = ipd.city || "";
- }
- //debug.add("> " + chance.integer({min: 0, max: 65535}));
- //debug.add("> " + chance.ip());
- //debug.add("Sending random entry: " + data["src"]["ip"]);
- io.sockets.emit("markIncident", data);
- update_slot(data.sensorname);
- }
- // Add random entries
- function add_entry_random() {
- addEntry();
- }
- var data_attacker = [];
- var monitors = [10, 10, 10, 11, 11];
- for (var key in monitors) {
- data_attacker.push(
- {
- sensorname: "monitor_" + key,
- src: {
- ip : "1.2.3." + key,
- port : 12345,
- },
- dst: {
- ip : "1.2.3.4",
- port : 33,
- },
- type: monitors[key]
- }
- );
- }
- var chance_addentry = new Chance();
- // Add Entries from a set of known attackers
- function add_entry_random_known() {
- var debug = new Debug();
- var pos = chance_addentry.integer({min: 0, max: monitors.length - 1});
- //debug.add("using: " + pos + " / " + data_attacker[pos]+ " / " + data_attacker[1].sensorname);
- addEntry(data_attacker[pos]);
- }
- init_stats_window(2);
- //var interval_countermeasure = setInterval(add_entry_random, 1000);
- var interval_countermeasure = setInterval(add_entry_random_known, 1000);
- // process incomding data, respond with a status code, broadcast the received
- // data and store the data in a database
- function process (response, postData, authorized, sensor, io, request_ip) {
- var debug = new Debug();
- //debug.add("Processing " + (authorized?"authorized":"unauthorized") + " incoming data"); // "from '" + response.connection.remoteAddress + "'"
- if(postData.length == 0) {
- debug.add("Received no POST data.");
- response.writeHead(400, {"Content-Type": "text/plain"});
- response.write(debug.get());
- response.end();
- }
- else {
- try {
- var items = [];
- var dataStr = postData.trim() + "\n";
- //console.log(dataStr);
- var nlj = new stream.Readable();
- var parser = new Parser();
- var read = false;
- nlj._read = function _read(){
- if(!read) {
- nlj.push(dataStr);
- read = true;
- }
- else nlj.push(null);
- };
- parser.on('data', function(parsedData){
- // get geodata from ip
- var ipa, ipd;
- ipa = lookUpIp(parsedData.src.ip);
- if(parsedData.dst) {
- ipd = lookUpIp(parsedData.dst.ip);
- }
- // generate data
- var data = {
- sensorname: sensor.name || parsedData.sensor && parsedData.sensor.name || "Unknown",
- sensortype: sensor.type || parsedData.sensor && parsedData.sensor.type || "Unknown",
- src: {
- ip: parsedData.src && parsedData.src.ip || null,
- port: parsedData.src && parsedData.src.port && (parsedData.src.port >= 0) && (parsedData.src.port <= 65535) && parsedData.src.port || 0,
- ll: [0,0],
- country: "",
- cc: 0,
- city: ""
- },
- dst: {
- ip: parsedData.dst && parsedData.dst.ip || null,
- port: parsedData.dst && parsedData.dst.port && (parsedData.dst.port >= 0) && (parsedData.dst.port <= 65535) && parsedData.dst.port || 0,
- ll: [0,0],
- country: "",
- cc: 0,
- city: ""
- },
- type: parsedData.type || 0,
- log: parsedData.log || null,
- md5sum: parsedData.md5sum || null,
- date: (parsedData.date || parsedData.date === 0) && new Date(parsedData.date*1000) || new Date(), // now
- authorized: authorized
- };
- if(!data.dst.ip || parsedData.internal_attack || !ipd){
- ipd = lookUpIp(request_ip);
- }
- if(ipa){
- data.src["ll"] = [ipa.longitude, ipa.latitude];
- data.src["country"] = ipa.country_name;
- data.src["cc"] = ipa.country_code;
- data.src["city"] = ipa.city || "";
- }
- if(ipd){
- data.dst["ll"] = [ipd.longitude, ipd.latitude];
- data.dst["country"] = ipd.country_name;
- data.dst["cc"] = ipd.country_code;
- data.dst["city"] = ipd.city || "";
- }
- if(parsedData.hasOwnProperty("sync_id")){
- data["sync_id"] = parseInt(parsedData.sync_id, 10);
- }
- if(parsedData.hasOwnProperty("device")){
- data["device"] = parsedData.device;
- }
- if(parsedData.hasOwnProperty("bssid")){
- data["bssid"] = parsedData.bssid;
- }
- if(parsedData.hasOwnProperty("ssid")){
- data["ssid"] = parsedData.ssid;
- }
- if(parsedData.hasOwnProperty("ssid")){
- data["internal_attack"] = parsedData.internal_attack;
- }
- if(parsedData.hasOwnProperty("external_ip")){
- data["external_ip"] = parsedData.external_ip;
- }
- items.push(data);
- update_slot( data["sensorname"] );
- if(!ipa) {
- debug.add("An invalid source IP: " + parsedData.src.ip + " (need to be a valid IP that could be resolved to a location via GeoIP)");
- }
- });
- parser.on('end', function(){
- if(items.length > 0){
- //debug.add("try to insert " + items.length + " item(s) to the database");
- // function(..) gets called on succesfull insert into database
- db.insert(items, function(err, items) {
- //console.log("dbInsertCallback", arguments);
- if(err){
- debug.add("dbInsert error: " + err);
- response.writeHead(400, {"Content-Type": "text/plain"});
- }
- else if(!items || items.length == 0){
- debug.add("no items inserted to the database");
- response.writeHead(400, {"Content-Type": "text/plain"});
- err = true;
- }
- else{
- // debug.add("successfully inserted " + items.length + " item(s) to the database");
- response.writeHead(200, {"Content-Type": "text/plain"});
- }
- if(!err){
- var forwarded = 0;
- var now = new Date().getTime();
- var minDate = now - 60*60*1000;
- var maxDate = now + 60*60*1000;
- for(var i = 0; i < items.length; i++){
- var date = items[i].date.getTime();
- // only send items that are not older than an hour and not more than an hour in the future
- if(date >= minDate && date < maxDate){
- io.sockets.emit("markIncident", items[i]); // TODO send as one packet / one array
- forwarded++;
- }
- }
- //debug.add("forwarded " + forwarded + " item(s) live to the webclients");
- }
- response.write(debug.get());
- response.end();
- });
- }
- else{
- debug.add("no items left to insert to the database!");
- response.writeHead(400, {"Content-Type": "text/plain"});
- response.write(debug.get());
- response.end();
- }
- });
- parser.on('error', function(e){
- console.log(arguments);
- debug.add("Received invalid POST data. ");
- response.writeHead(400, {"Content-Type": "text/plain"});
- response.write(debug.get());
- response.end();
- });
- nlj.pipe(parser);
- }
- catch (e) {
- debug.add("Received invalid POST data. " + e);
- debug.add(e.stack);
- response.writeHead(400, {"Content-Type": "text/plain"});
- response.write(debug.get());
- response.end();
- }
- }
- }
- exports.process = process;
|