postHandler.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /**
  2. * TraCINg-Server - Gathering and visualizing cyber incidents on the world
  3. *
  4. * Copyright 2013 Matthias Gazzari, Annemarie Mattmann, André Wolski
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. /*
  19. * This module provides methods of receiving POST data and storing them in a
  20. * database. Additionally it is send via socket.io to every client connected to
  21. * the server.
  22. */
  23. // load node modules
  24. var City = require("geoip").City; // requires 'npm install geoip' and on ubuntu 'aptitude install libgeoip-dev'
  25. var city = new City("GeoLiteCity.dat");
  26. var stream = require("stream");
  27. var Parser = require("newline-json").Parser;
  28. var Chance = require("chance");
  29. // load internal modules
  30. //var db = require("./db/db.js");
  31. var db = require("./db/database.js");
  32. var debugInstance = 0;
  33. var Debug = function(){
  34. var output = "";
  35. var instance = debugInstance++;
  36. this.get = function(){
  37. return output;
  38. };
  39. this.add = function(text){
  40. text = "PostHandler["+instance+"]: " + text;
  41. console.log(text);
  42. output += text + "\n";
  43. };
  44. };
  45. var cacheIPLocations = {};
  46. function lookUpIp(ip){
  47. cacheIPLocations[ip] = cacheIPLocations[ip] || city.lookupSync(ip);
  48. return cacheIPLocations[ip];
  49. }
  50. /* Countermeasure logic */
  51. var TIMESLOT_SECONDS = 10;
  52. //var WINDOW_MAX_TIMESLOTS = 60*60;
  53. var WINDOW_MAX_TIMESLOTS = 60;
  54. var monitor_count = {};
  55. var window_list = [];
  56. var ALARM_RATIO_THRESHOLD = 1.89;
  57. function init_stats_window(initial_value) {
  58. var debug = new Debug();
  59. debug.add("initiating window with ratio " + initial_value);
  60. for(var x=0; x < WINDOW_MAX_TIMESLOTS; ++x){
  61. window_list[x] = initial_value;
  62. }
  63. }
  64. function update_slot(monitor_id) {
  65. if (monitor_id in monitor_count) {
  66. monitor_count[monitor_id] += 1;
  67. } else {
  68. monitor_count[monitor_id] = 1;
  69. }
  70. }
  71. function update_window() {
  72. var debug = new Debug();
  73. var timeslot = Math.round(new Date().getTime() / (1000 * TIMESLOT_SECONDS)) % WINDOW_MAX_TIMESLOTS;
  74. var amount_monitors = Object.keys(monitor_count).length;
  75. var amount_attacks = 0;
  76. for (var monitor_id in monitor_count) {
  77. amount_attacks += monitor_count[monitor_id];
  78. }
  79. if (amount_monitors == 0 || amount_attacks == 0) {
  80. debug.add("No monitors counted, setting to last value");
  81. if (timeslot - 1 > 0) {
  82. window_list[timeslot] = window_list[timeslot - 1];
  83. } else {
  84. window_list[timeslot] = window_list[WINDOW_MAX_TIMESLOTS - 1];
  85. }
  86. } else {
  87. window_list[timeslot] = amount_attacks / amount_monitors;
  88. debug.add("Timeslot / attacks / monitors / ratio: "+ timeslot +" / "+ amount_attacks +" / "+ amount_monitors +
  89. " / "+ window_list[timeslot]);
  90. }
  91. // reset monitor counts
  92. monitor_count = {};
  93. var sum_ratio = 0;
  94. for (var key in window_list) {
  95. sum_ratio += window_list[key];
  96. }
  97. var ratio = sum_ratio / WINDOW_MAX_TIMESLOTS;
  98. debug.add("SUM(ratio) / ( SUM(ratio)/cnt ): " + sum_ratio + " / " + ratio);
  99. if (ratio < ALARM_RATIO_THRESHOLD) {
  100. debug.add("!!!!!!!!! ALAAAARM!!!! Ratio is too low! we get attacked!!!");
  101. }
  102. }
  103. var interval_update_window = setInterval(update_window, TIMESLOT_SECONDS * 1000);
  104. var data_empty = {
  105. sensorname: "",
  106. src: {
  107. ip : "",
  108. port : ""
  109. },
  110. dst: {
  111. ip : "",
  112. port : ""
  113. },
  114. type: ""
  115. };
  116. function addEntry(data_new) {
  117. var debug = new Debug();
  118. if (data_new === undefined) {
  119. debug.add("NO data given: " + data_new);
  120. data_new = data_empty;
  121. }
  122. if (typeof io == 'undefined') {
  123. //debug.add("no io, no emit -> I quit");
  124. return;
  125. }
  126. var chance = new Chance();
  127. // Add a random entry to tracing. This will affect source and destination port,
  128. // all other values wil remain constant.
  129. ip_src = data_new.src.ip || chance.ip();
  130. ip_dst = data_new.dst.ip || chance.ip();
  131. var data = {
  132. sensorname: data_new.sensorname || "RandomMonitor",
  133. sensortype: "RandomType",
  134. src: {
  135. ip: ip_src,
  136. port: data_new.src.port || chance.integer({min: 0, max: 65535}),
  137. ll: [0,0],
  138. country: "",
  139. cc: 0,
  140. city: ""
  141. },
  142. dst: {
  143. ip: ip_dst,
  144. port: data_new.dst.port || chance.integer({min: 0, max: 65535}),
  145. ll: [0,0],
  146. country: "",
  147. cc: 0,
  148. city: ""
  149. },
  150. type: data_new.type || 11,
  151. log: null,
  152. md5sum: "0123456789",
  153. date: new Date(),
  154. authorized: false
  155. };
  156. debug.add("adding, name/ip/port: " + data.sensorname + " / " + data.src.ip + " / " +
  157. data.dst.ip + " / " + data.src.port + " / " + data.dst.port);
  158. ipa = lookUpIp(ip_src);
  159. if(ipa){
  160. data.src["ll"] = [ipa.longitude, ipa.latitude];
  161. data.src["country"] = ipa.country_name;
  162. data.src["cc"] = ipa.country_code;
  163. data.src["city"] = ipa.city || "";
  164. }
  165. ipd = lookUpIp(ip_dst);
  166. if(ipd){
  167. data.dst["ll"] = [ipd.longitude, ipd.latitude];
  168. data.dst["country"] = ipd.country_name;
  169. data.dst["cc"] = ipd.country_code;
  170. data.dst["city"] = ipd.city || "";
  171. }
  172. //debug.add("> " + chance.integer({min: 0, max: 65535}));
  173. //debug.add("> " + chance.ip());
  174. //debug.add("Sending random entry: " + data["src"]["ip"]);
  175. io.sockets.emit("markIncident", data);
  176. update_slot(data.sensorname);
  177. }
  178. // Add random entries
  179. function add_entry_random() {
  180. addEntry();
  181. }
  182. var data_attacker = [];
  183. var monitors = [10, 10, 10, 11, 11];
  184. for (var key in monitors) {
  185. data_attacker.push(
  186. {
  187. sensorname: "monitor_" + key,
  188. src: {
  189. ip : "1.2.3." + key,
  190. port : 12345,
  191. },
  192. dst: {
  193. ip : "1.2.3.4",
  194. port : 33,
  195. },
  196. type: monitors[key]
  197. }
  198. );
  199. }
  200. var chance_addentry = new Chance();
  201. // Add Entries from a set of known attackers
  202. function add_entry_random_known() {
  203. var debug = new Debug();
  204. var pos = chance_addentry.integer({min: 0, max: monitors.length - 1});
  205. //debug.add("using: " + pos + " / " + data_attacker[pos]+ " / " + data_attacker[1].sensorname);
  206. addEntry(data_attacker[pos]);
  207. }
  208. init_stats_window(2);
  209. //var interval_countermeasure = setInterval(add_entry_random, 1000);
  210. var interval_countermeasure = setInterval(add_entry_random_known, 1000);
  211. // process incomding data, respond with a status code, broadcast the received
  212. // data and store the data in a database
  213. function process (response, postData, authorized, sensor, io, request_ip) {
  214. var debug = new Debug();
  215. //debug.add("Processing " + (authorized?"authorized":"unauthorized") + " incoming data"); // "from '" + response.connection.remoteAddress + "'"
  216. if(postData.length == 0) {
  217. debug.add("Received no POST data.");
  218. response.writeHead(400, {"Content-Type": "text/plain"});
  219. response.write(debug.get());
  220. response.end();
  221. }
  222. else {
  223. try {
  224. var items = [];
  225. var dataStr = postData.trim() + "\n";
  226. //console.log(dataStr);
  227. var nlj = new stream.Readable();
  228. var parser = new Parser();
  229. var read = false;
  230. nlj._read = function _read(){
  231. if(!read) {
  232. nlj.push(dataStr);
  233. read = true;
  234. }
  235. else nlj.push(null);
  236. };
  237. parser.on('data', function(parsedData){
  238. // get geodata from ip
  239. var ipa, ipd;
  240. ipa = lookUpIp(parsedData.src.ip);
  241. if(parsedData.dst) {
  242. ipd = lookUpIp(parsedData.dst.ip);
  243. }
  244. // generate data
  245. var data = {
  246. sensorname: sensor.name || parsedData.sensor && parsedData.sensor.name || "Unknown",
  247. sensortype: sensor.type || parsedData.sensor && parsedData.sensor.type || "Unknown",
  248. src: {
  249. ip: parsedData.src && parsedData.src.ip || null,
  250. port: parsedData.src && parsedData.src.port && (parsedData.src.port >= 0) && (parsedData.src.port <= 65535) && parsedData.src.port || 0,
  251. ll: [0,0],
  252. country: "",
  253. cc: 0,
  254. city: ""
  255. },
  256. dst: {
  257. ip: parsedData.dst && parsedData.dst.ip || null,
  258. port: parsedData.dst && parsedData.dst.port && (parsedData.dst.port >= 0) && (parsedData.dst.port <= 65535) && parsedData.dst.port || 0,
  259. ll: [0,0],
  260. country: "",
  261. cc: 0,
  262. city: ""
  263. },
  264. type: parsedData.type || 0,
  265. log: parsedData.log || null,
  266. md5sum: parsedData.md5sum || null,
  267. date: (parsedData.date || parsedData.date === 0) && new Date(parsedData.date*1000) || new Date(), // now
  268. authorized: authorized
  269. };
  270. if(!data.dst.ip || parsedData.internal_attack || !ipd){
  271. ipd = lookUpIp(request_ip);
  272. }
  273. if(ipa){
  274. data.src["ll"] = [ipa.longitude, ipa.latitude];
  275. data.src["country"] = ipa.country_name;
  276. data.src["cc"] = ipa.country_code;
  277. data.src["city"] = ipa.city || "";
  278. }
  279. if(ipd){
  280. data.dst["ll"] = [ipd.longitude, ipd.latitude];
  281. data.dst["country"] = ipd.country_name;
  282. data.dst["cc"] = ipd.country_code;
  283. data.dst["city"] = ipd.city || "";
  284. }
  285. if(parsedData.hasOwnProperty("sync_id")){
  286. data["sync_id"] = parseInt(parsedData.sync_id, 10);
  287. }
  288. if(parsedData.hasOwnProperty("device")){
  289. data["device"] = parsedData.device;
  290. }
  291. if(parsedData.hasOwnProperty("bssid")){
  292. data["bssid"] = parsedData.bssid;
  293. }
  294. if(parsedData.hasOwnProperty("ssid")){
  295. data["ssid"] = parsedData.ssid;
  296. }
  297. if(parsedData.hasOwnProperty("ssid")){
  298. data["internal_attack"] = parsedData.internal_attack;
  299. }
  300. if(parsedData.hasOwnProperty("external_ip")){
  301. data["external_ip"] = parsedData.external_ip;
  302. }
  303. items.push(data);
  304. update_slot( data["sensorname"] );
  305. if(!ipa) {
  306. debug.add("An invalid source IP: " + parsedData.src.ip + " (need to be a valid IP that could be resolved to a location via GeoIP)");
  307. }
  308. });
  309. parser.on('end', function(){
  310. if(items.length > 0){
  311. //debug.add("try to insert " + items.length + " item(s) to the database");
  312. // function(..) gets called on succesfull insert into database
  313. db.insert(items, function(err, items) {
  314. //console.log("dbInsertCallback", arguments);
  315. if(err){
  316. debug.add("dbInsert error: " + err);
  317. response.writeHead(400, {"Content-Type": "text/plain"});
  318. }
  319. else if(!items || items.length == 0){
  320. debug.add("no items inserted to the database");
  321. response.writeHead(400, {"Content-Type": "text/plain"});
  322. err = true;
  323. }
  324. else{
  325. // debug.add("successfully inserted " + items.length + " item(s) to the database");
  326. response.writeHead(200, {"Content-Type": "text/plain"});
  327. }
  328. if(!err){
  329. var forwarded = 0;
  330. var now = new Date().getTime();
  331. var minDate = now - 60*60*1000;
  332. var maxDate = now + 60*60*1000;
  333. for(var i = 0; i < items.length; i++){
  334. var date = items[i].date.getTime();
  335. // only send items that are not older than an hour and not more than an hour in the future
  336. if(date >= minDate && date < maxDate){
  337. io.sockets.emit("markIncident", items[i]); // TODO send as one packet / one array
  338. forwarded++;
  339. }
  340. }
  341. //debug.add("forwarded " + forwarded + " item(s) live to the webclients");
  342. }
  343. response.write(debug.get());
  344. response.end();
  345. });
  346. }
  347. else{
  348. debug.add("no items left to insert to the database!");
  349. response.writeHead(400, {"Content-Type": "text/plain"});
  350. response.write(debug.get());
  351. response.end();
  352. }
  353. });
  354. parser.on('error', function(e){
  355. console.log(arguments);
  356. debug.add("Received invalid POST data. ");
  357. response.writeHead(400, {"Content-Type": "text/plain"});
  358. response.write(debug.get());
  359. response.end();
  360. });
  361. nlj.pipe(parser);
  362. }
  363. catch (e) {
  364. debug.add("Received invalid POST data. " + e);
  365. debug.add(e.stack);
  366. response.writeHead(400, {"Content-Type": "text/plain"});
  367. response.write(debug.get());
  368. response.end();
  369. }
  370. }
  371. }
  372. exports.process = process;