Всем привет. Недавно тов. R_Voland рассказал о своём http ханипоте. Он меня и вдохновил к написанию этого поста. Но в этом случае, будем ловить все tcp и udp сканы, а не только http. Запросы будем ловить с помощью tcpdump.

Для tcp ловим только syn пакеты:

tcpdump -n "tcp[tcpflags] & (tcp-syn) != 0"

Для udp все входящие udp пакеты

 tcpdump -n inbound and udp

В теории вывод от tcpdump'а можно перенаправить в файл и дальше парсить его по необходимости, но я ещё тот извращенец, поэтому напишем сервис на nodejs, который будет слушать tcpdump и сохранять результаты в mysql базу.

Шапка скрипта со всем необходимым:

#!/usr/bin/nodejs
'use strict';

const net = require('net');
const spawn = require('child_process').spawn;
const mysql = require('mysql2');

const config = require('./config');
const connection = mysql.createConnection(config.mysql);
const tcpdump = spawn('tcpdump', ['-n', 'tcp[tcpflags] & (tcp-syn) != 0']);

const excludePorts = [ 80 ];
const excludeAddrs = [
  '127.0.0.1',
];

let lastTcpLine = '';

lastTcpLine — нужен для временного хранения последней строки полученной из stdout. Т.к мы получаем данные не строка за строкой, а блоками, в которых последняя строка может быть не полной, и её 2я половина прилетит со вторым блоком данных.

excludePorts и excludeAddrs нам нужны для исключения каких-нибудь своих собственных коннектов. На 80 порту у нас будет отдельный расширенный логгер, локалхост тоже слушать не будем.

Вешаем листенер:

tcpdump.stdout.on('data', (data) => {
  let lines = `${data}`.split('\n'); // разбиваем блок данных на строки

  // далее идёт немного логики которая узнаёт нужно или нет дополнять первую строку сохранённым в lastTcpLine куском, и нужно ли сохранять новый фрагмент в lastTcpLine
  let lastTcpLineNum = lines.length - 1;
  let toNum = lines.length - 1;

  lines[ 0 ] = lines[ 0 ] + lastTcpLine;
  
  if( lines[ lastTcpLineNum ].indexOf('\n') == -1 ) {
    lastTcpLine = lines[ lastTcpLineNum ];
    toNum --;
  } else
    lastTcpLine = '';

  for(let i=0; i<=toNum; i++) {
    saveLog( parseLine(lines[ i ], 'tcp') ); // парсим и сохраняем всё в бд
  } 
});

IP к нам прилетают в виде address.port, например 192.168.1.1.443, разбираем на адрес и порт:

function parseLine(line, proto) {
  let parts = line.split(' '); // разбиваем строку на запчасти
  let dstAddrParts = parseIP(parts[ 4 ]); // тут будет адрес, куда прилетел запрос 
  let srcAddrParts = parseIP(parts[ 2 ]); // тут откуда

  return {
    addr: srcAddrParts.addr,
    port: dstAddrParts.port,
    proto: proto,
    req_time: parseInt(new Date() / 1000),
  };
}

function parseIP(ipStr) {
  let addrParts = ipStr.split('.'); // разбиваем строку на куски, разделённые точкой
  let port = addrParts[ addrParts.length - 1 ];  // последний кусок - это порт
  let ipOctets = []; // собираем оставшиеся куски в строку

  for(let i=0; i<=(addrParts.length-2); i++)
    ipOctets.push(addrParts[ i ]);
  
  let addr = ipOctets.join('.');

  if( !net.isIP(addr) ) // и, на всякий случай проверяем, что мы разобрали, если что-то не так, отдаём null вместо адреса
    addr = null;

  return {
    addr: addr,
    port: parseInt(port)
  };
}

Сохраняем результат в базу данных:

function saveLog(info) {
  if( excludePorts.indexOf(info.port) > -1 ) // если порт в исключениях - ничего не делаем
    return;
    
  if( excludeAddrs.indexOf(info.addr) > -1 ) // если ип в исключениях - ничего не делаем
    return;

  for(let key in info) // если какое-то из значений не определено - ничего не делаем
    if( !info[ key ] ) {
      console.log('Bad info:', info);
      return;
    }

  let fields = []; // собираем заголовки столбцов
  for(let key in info)
    fields.push('`' + key + '`'); 

  let values = []; // и значений
  for(let key in info) {
    if( typeof(info[ key ]) == 'number' )
      values.push(info[ key ]);
    else
      values.push(`'` + info[ key ] + `'`);
  }

  let query = 'INSERT INTO access_logs (' + fields.join(',') + ') VALUES(' + values.join(',') + ')'; // и фигачим это всё в запрос
  connection.query(query);
}

Код для udp листенера совподает на 100%, не буду повторяться, исходники можно посмотреть на гитхабе: github.com/hololoev/honeypot_tcpdump_logger.git

Теперь нам нужна новая виртуалочка, на которую ставим nginx с заглушкой «Under construction» и наш логгер. НЕ направляем на него никакой домен, всячески изображаем вид свежесозданного сервера, среднестатистического вебмастера. Через пару дней (с 5 по 9 мая) получаем результаты:

Total adresses tcp Scans udp Scans http Scans
4324 38558 543 101


Топ 5 tcp портов:
tcp port Scans
445 2538
23 1515
22 1304
3306 151
3389 148


Топ 5 udp портов:
udp port Scans
5060 95
161 41
1900 32
123 30
137 23


Топ 10 самых активных ip адресов:
Address Total scans Location http tcp udp
40.115.124.127 25822 IE/Dublin 0 25822 0
77.72.82.101 861 RU/ 0 861 0
77.72.82.22 760 RU/ 0 760 0
145.239.134.1 550 GB/ 0 550 0
101.128.72.140 282 ID/Jakarta 0 282 0
77.72.85.25 244 RU/ 0 244 0
181.214.87.34 208 US/Las Vegas 0 208 0
5.188.11.91 189 RU/Saint Petersburg 0 189 0
128.199.141.239 173 SG/Singapore 0 173 0
5.188.11.79 156 RU/Saint Petersburg 0 156 0


Карта с топ 100 самых активных адресов:

Комментарии (2)


  1. Alevat
    11.05.2018 10:18

    По утверждению 2ip адреса подсети 77.72.82.0/24 розданы бритам


    1. hololoev Автор
      11.05.2018 10:20

      Для геолокации использовал www.npmjs.com/package/geoip-local в нём могут быть неполные/неактуальные данные, тк. юзается бесплатна база.