4 changed files with 187 additions and 0 deletions
@ -0,0 +1,39 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
if ! sudo which nft |
||||||
|
then |
||||||
|
echo "install nftables" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
if ! sudo nft list ruleset |grep output |
||||||
|
then |
||||||
|
sudo nft -f log.nft |
||||||
|
fi |
||||||
|
|
||||||
|
if ! sudo nft list ruleset |grep output |
||||||
|
then |
||||||
|
echo "nft error" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
if ! id | grep systemd-journal |
||||||
|
then |
||||||
|
echo "sudo adduser $(whoami) systemd-journal" |
||||||
|
sudo adduser "$(whoami)" systemd-journal |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
packages="python3-systemd python3-tabulate python3-dns dnscap python3-pure-pcapy3 tshark" |
||||||
|
|
||||||
|
apt -s install "${packages}" |
||||||
|
echo "apt-get -s ${packages}" |
||||||
|
if apt-get -s install "${packages}" |grep ^Inst |
||||||
|
then |
||||||
|
echo "install python packages" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
echo "####################" |
||||||
|
echo "### System Ready " |
||||||
|
echo "####################" |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
[Unit] |
||||||
|
Description=Dnscap Daemon |
||||||
|
After=network-online.target |
||||||
|
|
||||||
|
[Service] |
||||||
|
Type=simple |
||||||
|
User=root |
||||||
|
Group=root |
||||||
|
UMask=007 |
||||||
|
|
||||||
|
ExecStart=/usr/bin/dnscap -b -o pid_file=/var/run/dnscap.pid -i any -p -w - -t 600 -k /usr/bin/tshark -r - -T json | /usr/local/bin/report.py |
||||||
|
Restart=on-failure |
||||||
|
|
||||||
|
# Configures the time to wait before service is stopped forcefully. |
||||||
|
TimeoutStopSec=10 |
||||||
|
|
||||||
|
[Install] |
||||||
|
WantedBy=multi-user.target |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
#!/usr/sbin/nft -f |
||||||
|
|
||||||
|
flush ruleset |
||||||
|
|
||||||
|
table inet filter { |
||||||
|
chain input { |
||||||
|
type filter hook input priority 0; policy drop; |
||||||
|
iif lo accept |
||||||
|
ct state established,related accept |
||||||
|
# Log and drop invalid packets |
||||||
|
ct state invalid log prefix "INVALID: " level warn drop |
||||||
|
# Log and accept SSH |
||||||
|
tcp dport 22 ct state new log prefix "SSH: " level info accept |
||||||
|
# Allow web traffic (no logging to reduce volume) |
||||||
|
#tcp dport { 80, 443 } accept |
||||||
|
# Log everything else that reaches the end (default drop) |
||||||
|
log prefix "INPUT DROP: " level warn |
||||||
|
} |
||||||
|
|
||||||
|
chain forward { |
||||||
|
type filter hook forward priority 0; policy drop; |
||||||
|
|
||||||
|
log prefix "FORWARD DROP: " level warn |
||||||
|
} |
||||||
|
|
||||||
|
chain output { |
||||||
|
type filter hook output priority 0; policy accept; |
||||||
|
# dns request |
||||||
|
udp dport 53 log prefix "DNS:" level info |
||||||
|
# accept local connection |
||||||
|
ip daddr 192.168.122.1 counter accept |
||||||
|
ip daddr 192.168.0.253 counter accept |
||||||
|
ip daddr 192.168.0.254 counter accept |
||||||
|
# counter output packets |
||||||
|
counter comment "Count accepted packets" |
||||||
|
# not logging established |
||||||
|
ct state established,related accept |
||||||
|
# Log and accept output packets |
||||||
|
log prefix "OUTPUT:" level info flags skuid accept |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,89 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
|
||||||
|
from systemd import journal |
||||||
|
from datetime import datetime, timedelta |
||||||
|
import re # read lines |
||||||
|
# Import smtplib for the actual sending function |
||||||
|
import smtplib |
||||||
|
# Import the email modules we'll need |
||||||
|
from email.message import EmailMessage |
||||||
|
from tabulate import tabulate |
||||||
|
import os |
||||||
|
import socket |
||||||
|
import logging |
||||||
|
import logging.config |
||||||
|
import sys |
||||||
|
import json |
||||||
|
|
||||||
|
class connection: |
||||||
|
def __init__(self,logger): |
||||||
|
self.logger = logger |
||||||
|
self.records = [] |
||||||
|
def __get_list(self): |
||||||
|
liste = [] |
||||||
|
for e in self.records: |
||||||
|
self.logger.debug("interface {} source {} destination {} proto {} dport {} uid {} gid {}".format(e['interface'],e['source'],e['destination'],e['proto'],e['dport'],e['uid'],e['gid'])) |
||||||
|
try: |
||||||
|
self.logger.debug("--- ask reverse lookup for : {}".format(e['destination'])) |
||||||
|
domain, alias, addresslist = socket.gethostbyaddr(e['destination']) |
||||||
|
except socket.herror as he: |
||||||
|
self.logger.debug("--- can't resolv {}".format(e['destination'])) |
||||||
|
domain = 'unknown' |
||||||
|
item = [ e['interface'], e['source'], e['destination'], e['proto'], e['dport'], e['uid'], e['gid'] , domain ] |
||||||
|
if not item in liste: |
||||||
|
liste.append(item) |
||||||
|
self.logger.debug(liste) |
||||||
|
return liste |
||||||
|
def report(self): |
||||||
|
headers = { 'Interface':'', 'Source':'', 'Destination':'', 'Protocol':'', 'DestinationPort':'', 'UID':'', 'GID':'' , 'domain':'' } |
||||||
|
header = headers.keys() |
||||||
|
msg = EmailMessage() |
||||||
|
msg.set_content(tabulate(self.__get_list(),headers)) |
||||||
|
msg['Subject'] = f'[ssh][attempts] Reporting' |
||||||
|
msg['From'] = "{}@{}".format(os.getlogin(),socket.gethostname()) |
||||||
|
msg['To'] = "{}@{}".format(os.getlogin(),'192.168.0.238') |
||||||
|
print(msg) |
||||||
|
#s = smtplib.SMTP('localhost') |
||||||
|
#s.send_message(msg) |
||||||
|
#s.quit() |
||||||
|
def addmessage(self, message:str): |
||||||
|
self.logger.debug("-- scan {}".format(message)) |
||||||
|
nftop = r'''^OUTPUT:IN= OUT=(?P<interface>[a-z0-9]+)[ ]SRC=(?P<source>[0-9.]+)[ ]DST=(?P<destination>[0-9.]+)[ ]LEN=([0-9]+)[ ]TOS=([0-9x]+)[ ]PREC=([0-9x]+)[ ]TTL=([0-9]+)[ ]ID=([0-9]+)[ ]DF[ ]PROTO=(?P<proto>[CDTUP]+)[ ]SPT=([0-9]+)[ ]DPT=(?P<dport>[0-9]+)[ ]WINDOW=([0-9]+)[ ]RES=([0-9x]+)[ ]SYN[ ]URGP=([0-9]+)[ ]UID=(?P<uid>[0-9]+)[ ]GID=(?P<gid>[0-9]+)[ ]+$''' |
||||||
|
nftsp = r'''^SSH:[ ]IN=(?P<interface>[a-z0-9]+)[ ]OUT=[ ]MAC=(?P<gid>[0-9a-f:]+)[ ]SRC=(?P<source>[0-9.]+)[ ]DST=(?P<destination>[0-9.]+)[ ]LEN=([0-9]+)[ ]TOS=([0-9x]+)[ ]PREC=([0-9A-Fx]+)[ ]TTL=([0-9]+)[ ]ID=([0-9]+)[ ]DF[ ]PROTO=(?P<proto>[CDTUP]+)[ ]SPT=([0-9]+)[ ]DPT=(?P<dport>[0-9]+)[ ]WINDOW=([0-9]+)[ ]RES=([0-9x]+)[ ](SYN |ACK )(PSH |)URGP=(?P<uid>[0-9]+)[ ]+$''' |
||||||
|
nftom = re.match(re.compile(nftop),message) |
||||||
|
nftsm = re.match(re.compile(nftsp),message) |
||||||
|
if nftom: |
||||||
|
self.logger.debug("$$$ matched: {}".format(nftom)) |
||||||
|
self.records.append(nftom.groupdict()) |
||||||
|
elif nftsm: |
||||||
|
self.logger.debug("$$$ matched: {}".format(nftsm)) |
||||||
|
self.records.append(nftsm.groupdict()) |
||||||
|
|
||||||
|
# initiate |
||||||
|
logging.basicConfig(filename='/tmp/outlog_reporting.log',level=logging.DEBUG) |
||||||
|
logger=logging.getLogger(__name__) |
||||||
|
c = connection(logger) |
||||||
|
|
||||||
|
# read from dnscap |
||||||
|
try: |
||||||
|
queries = [] |
||||||
|
js = json.load(sys.stdin) |
||||||
|
logger.debug("Json : {}".format(js)) |
||||||
|
for request in js: |
||||||
|
for k,v in request['_source']['layers']['dns']['Queries'].items(): |
||||||
|
query = v['dns.qry.name'] |
||||||
|
logger.debug("query: {}".format(query)) |
||||||
|
queries.append(query) |
||||||
|
logger.debug("queries: {}".format(queries)) |
||||||
|
except json.decoder.JSONDecodeError as jde: |
||||||
|
logger.warning("No json found as input") |
||||||
|
|
||||||
|
# read logs from kernel journal |
||||||
|
j = journal.Reader() |
||||||
|
j.seek_realtime(datetime.now() - timedelta(hours=1)) |
||||||
|
j.add_match('_TRANSPORT=kernel') |
||||||
|
for entry in j: |
||||||
|
logger.debug(entry['MESSAGE']) |
||||||
|
c.addmessage(entry['MESSAGE']) |
||||||
|
logger.debug("--- Generating report") |
||||||
|
c.report() |
||||||
Loading…
Reference in new issue