The Poor Man's (or Woman's) Intrusion Detection System
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

224 lines
5.7 KiB

  1. //////////////////////////////////////////////////////////////////////
  2. // IP traffic analyzer
  3. // Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net>
  4. // Started April 23rd, 2021
  5. //
  6. // The idea is to analyze iptables LOG entries in combination with
  7. // DNSmasq's query log entries and combine them to list the hosts
  8. // that were accessed. The main reasons for not just inspecting HTTP
  9. // packets through a netfilter socket is due to HTTPS hiding the
  10. // "host" field. So I'm deducing based on DNS query timing.
  11. //
  12. // NOTE: its assumed that the log being processed is in chronological
  13. // order. This is the usual way things get logged. ;-)
  14. //
  15. // 2021-05-14 <ChipMaster@YeOlPiShack.net>
  16. // Dumbed down for < C++11.
  17. // Split into modules
  18. //
  19. // 2021-06-18 <ChipMaster@YeOlPiShack.net>
  20. // Wrapped in application object and added command line handling.
  21. // This includes:
  22. // - Getting config file name from CLI args
  23. // - Getting input and output filenams from CLI args
  24. // - Reading and writing from STDIN & STDOUT
  25. // - Send all non-data output to stderr
  26. //////////////////////////////////////////////////////////////////////
  27. // TODO: map names according to time and requesting host. time is probably automatic
  28. #include <string.h>
  29. #include <string>
  30. #include <iostream>
  31. #include <fstream>
  32. #include <stdexcept>
  33. #include <vector>
  34. #include <map>
  35. #include "cli.h"
  36. #include "strutil.h"
  37. #include "data.h"
  38. #include "config.h"
  39. using namespace std;
  40. //////////////////////////////////////////////////////////////////////
  41. // Application class to process files.
  42. //////////////////////////////////////////////////////////////////////
  43. //#define DEBUG
  44. struct IPtraffic: public cBaseApp {
  45. Config config;
  46. StringList dns_ignore, dns_del;
  47. NameVal rdns; // Reverse DNS lookup cache
  48. istream *log;
  49. ostream *out;
  50. LiveBug bug;
  51. int line_no;
  52. IPtraffic(): out(&cout), log(0)
  53. { // I'd rather this initialization be static...
  54. dns_ignore.push_back("v=spf1");
  55. dns_ignore.push_back("https:");
  56. dns_del.push_back("NODATA-");
  57. dns_del.push_back("NXDOMAIN-");
  58. }
  59. ~IPtraffic() { if(log && log!=&cin) delete(log); }
  60. void dlog(const string msg) {
  61. #ifdef DEBUG
  62. cerr << line_no << ": " << msg << endl;
  63. #endif
  64. }
  65. // TODO: elaborate
  66. void help() {
  67. cerr <<
  68. "\n"
  69. "iptraffic -c {config file} [-o {output file}] [{input file} [...]]\n";
  70. ExitCode = 1;
  71. }
  72. unsigned do_switch(const char *sw) {
  73. if((sw[0]=='c' || sw[0]=='o') && sw[1]==0) return 1;
  74. throw CLIerror("Unrecognized Switch");
  75. }
  76. void do_switch_arg(const char *sw, const std::string &val) {
  77. switch(*sw) {
  78. case 'c':
  79. config.load(val);
  80. if(!config.us.size()) throw CLIerror(
  81. "The configuration files MUST contain an [us] section with "
  82. "appropriate values"
  83. );
  84. break;
  85. case 'o':
  86. if(out!=&cout)
  87. throw CLIerror("Output file has already been specified");
  88. out = new ofstream(val.c_str()); // c_str(), really?!?!
  89. }
  90. }
  91. void do_arg(const char *fname) {
  92. if(log && log!=&cin) delete(log);
  93. log = 0;
  94. log = new ifstream(fname);
  95. ExitCode = do_log();
  96. }
  97. // NOTE: the return values isn't really used yet but the channel is here if
  98. // it can be of use.
  99. int do_log() {
  100. if(!config.us.size()) throw CLIerror(
  101. "A configuration file must be specified before input files."
  102. );
  103. Splits ln;
  104. int ict=0;
  105. NameVal::iterator nvp;
  106. string name, address, s;
  107. Conn conn;
  108. bool match;
  109. /// parse log file ///
  110. line_no=0;
  111. while((*log >> ln)) {
  112. line_no++;
  113. cerr << bug << ' ' << line_no << '\r' << flush;
  114. /// DNS query result ///
  115. // TODO: need to get more specific on tying us + them + time to DNS
  116. // TODO: doesn't seem that CNAMEs are getting attached to requests properly.
  117. // the logs are cryptic on this front.
  118. if(ln.count>8 && strncmp(ln.fields[4], "dnsmasq[", 8)==0) {
  119. if(ln[5]=="reply" || ln[5]=="cached") {
  120. name = ln[6];
  121. address = ln[8];
  122. // Hmm... is this reply an address?
  123. if(pre_match(dns_ignore, address)) continue; // nope
  124. if(pre_match(dns_del, address)) continue; // does not exist reply
  125. if((nvp=rdns.find(address))!=rdns.end()) {
  126. if(nvp->second==name) continue;
  127. dlog("WARN: DNS address overlap "+address+": "+nvp->second+" : "+name);
  128. }
  129. rdns[address] = name;
  130. dlog("Added "+address+" = "+name);
  131. continue;
  132. }
  133. }
  134. /// process connections ///
  135. if((ln.count>5 // old style
  136. && ln[4]=="kernel:"
  137. && ln[5]=="ACCEPT"
  138. ) || (ln.count>6 // new style
  139. && ln[4]=="vmunix:"
  140. && ln[6]=="ACCEPT")
  141. ) {
  142. conn = ln;
  143. conn.compact();
  144. if(!pre_match(config.us, conn.us)) conn.swap();
  145. if((nvp=rdns.find(conn.them))!=rdns.end())
  146. conn.name = nvp->second;
  147. if(config.ignores.find(conn)<0)
  148. *out << ln[0] << " " << ln[1] << " " << ln[2] << " " << conn << "\n";
  149. else
  150. ict++;
  151. }
  152. }
  153. *out << flush; // make sure all data gets written.
  154. cerr << "\nIgnored: " << ict << endl;
  155. cerr << "Total rDNS: " << rdns.size() << "\n";
  156. return 0;
  157. }
  158. int main() {
  159. try {
  160. cBaseApp::main();
  161. if(!log) {
  162. // no inputs were specified run stdin.
  163. log = &cin;
  164. ExitCode = do_log();
  165. }
  166. } catch(const CLIerror &e) {
  167. cerr << "ERROR: " << e.what() << "\n";
  168. help();
  169. }
  170. return ExitCode;
  171. }
  172. };
  173. //////////////////////////////////////////////////////////////////////
  174. // Run it
  175. //////////////////////////////////////////////////////////////////////
  176. MAIN(IPtraffic)