unblock, info commands. no login supicious

This commit is contained in:
root
2023-04-09 21:19:36 +02:00
parent d985dff839
commit 369a1a90cd

112
waf3.py
View File

@ -24,6 +24,7 @@ else:
# with open('names.yaml', 'w') as file: # with open('names.yaml', 'w') as file:
now = arrow.utcnow() now = arrow.utcnow()
older_than = now.shift(days=-3).floor('day')
last_hour = now.shift(hours=-1).floor('hour') last_hour = now.shift(hours=-1).floor('hour')
last_thirty_min = now.shift(minutes=-30) last_thirty_min = now.shift(minutes=-30)
# last_period = last_thirty_min # last_period = last_thirty_min
@ -49,10 +50,11 @@ Attack.create_table(True)
def cli(): def cli():
pass pass
async def nginx_reload(): async def nginx_reload():
returned_value = subprocess.call('/usr/bin/systemctl reload nginx', shell=True) returned_value = subprocess.call('/usr/bin/systemctl reload nginx', shell=True)
if returned_value == 0: if returned_value == 0:
print('Nginx reloaded') click.echo(click.style('Nginx reloaded', fg="blue"))
async def get_denied(): async def get_denied():
denied = [] denied = []
@ -61,9 +63,22 @@ async def get_denied():
denied.append(rule.split(' ')[-1]) denied.append(rule.split(' ')[-1])
return denied return denied
async def deny(ip): async def deny(ip):
ufw.add('deny from ' + ip + ' to any', number=1) ufw.add('deny from ' + ip + ' to any', number=1)
def undeny(ip):
# Not async to avoid deleting wrong ufw rule.
for key, rule in ufw.get_rules().items():
if rule == "deny from " + ip:
ufw.delete(key)
# attack = Attack.get_or_none(Attack.ip == ip)
# if attack:
# attack.delete_instance()
async def check(ip, host, date_position): async def check(ip, host, date_position):
date = arrow.get(date_position,'DD/MMM/YYYY:HH:mm:ss') date = arrow.get(date_position,'DD/MMM/YYYY:HH:mm:ss')
if date > last_period: if date > last_period:
@ -75,9 +90,10 @@ async def check(ip, host, date_position):
data = {'ip': ip, 'date':date.datetime, 'host': host, 'count':1} data = {'ip': ip, 'date':date.datetime, 'host': host, 'count':1}
Attack.create(**data) Attack.create(**data)
async def scan(log): async def scan(log):
suspects = [] suspects = []
suspects_login = {} # suspects_login = {}
suspects_404 = {} suspects_404 = {}
for line in log.lines(): for line in log.lines():
splitted = line.split() splitted = line.split()
@ -92,25 +108,28 @@ async def scan(log):
suspects.append(check(ip, host, date_position)) suspects.append(check(ip, host, date_position))
elif 'xmlrpc.php' in url: elif 'xmlrpc.php' in url:
suspects.append(check(ip, host, date_position)) suspects.append(check(ip, host, date_position))
elif 'login.php' in url and method == 'POST': # elif 'login.php' in url and method == 'POST':
if ip in suspects_login: # if ip in suspects_login:
suspects_login[ip].append( (ip, host, date_position) ) # suspects_login[ip].append( (ip, host, date_position) )
else: # else:
suspects_login[ip] = [(ip, host, date_position),] # suspects_login[ip] = [(ip, host, date_position),]
elif 'wp-admin' in url and status not in ['200','302','499']: elif 'wp-admin' in url and status not in ['200','302','499']:
suspects.append(check(ip, host, date_position)) suspects.append(check(ip, host, date_position))
def is_suspicious_login(item):
return len(item[1]) > 18 # def is_suspicious_login(item):
filtered = dict(filter(is_suspicious_login, suspects_login.items())) # return len(item[1]) > 18
for ip,suspect in filtered.items(): # filtered = dict(filter(is_suspicious_login, suspects_login.items()))
suspects.append(check(ip, suspect[-1][1], suspect[-1][2]))
# for ip,suspect in filtered.items():
# suspects.append(check(ip, suspect[-1][1], suspect[-1][2]))
await asyncio.gather(*suspects) await asyncio.gather(*suspects)
async def block(): async def block():
denied = await get_denied() denied = await get_denied()
found = Attack.select().where( found = Attack.select().where(
(Attack.ip.not_in(denied)) & (Attack.ip.not_in(denied)) &
(Attack.count > 7) (Attack.count > 3)
) )
if found.count() > 0: if found.count() > 0:
click.echo(click.style('New IPs to block: {}'.format(found.count()), fg="yellow")) click.echo(click.style('New IPs to block: {}'.format(found.count()), fg="yellow"))
@ -118,9 +137,19 @@ async def block():
for attack in found: for attack in found:
to_deny.append( deny(attack.ip) ) to_deny.append( deny(attack.ip) )
await asyncio.gather(*to_deny) await asyncio.gather(*to_deny)
await nginx_reload()
else: else:
click.echo(click.style('No IPs to block', fg="blue")) click.echo(click.style('No IPs to block', fg="blue"))
def report_attacks():
click.echo(
click.style(
f"Attacks in database: {Attack.select().count()}",
fg="cyan"
)
)
def report(): def report():
click.echo( click.echo(
click.style( click.style(
@ -134,12 +163,7 @@ def report():
fg="cyan" fg="cyan"
) )
) )
click.echo( report_attacks()
click.style(
f"Attacks in database: {Attack.select().count()}",
fg="cyan"
)
)
for ip in whitelist_ips: for ip in whitelist_ips:
click.echo( click.echo(
click.style( click.style(
@ -148,6 +172,7 @@ def report():
) )
) )
async def start(): async def start():
report() report()
scans = [] scans = []
@ -155,17 +180,65 @@ async def start():
scans.append(scan(log)) scans.append(scan(log))
await asyncio.gather(*scans) await asyncio.gather(*scans)
await block() await block()
report_attacks()
#print("Updated number of attacks in database: {}".format( Attack.select().count() ) ) #print("Updated number of attacks in database: {}".format( Attack.select().count() ) )
async def info(ip, unblock=False):
denied = await get_denied()
find = Attack.get_or_none(Attack.ip == ip)
if find:
click.echo(click.style(f'IP {find.ip} found with {find.count} attacks', fg="yellow", bold=True))
# print(find)
if unblock is True:
find.delete_instance()
click.echo(click.style(f'IP {ip} removed from attacks', fg="green", bold=True))
else:
click.echo(click.style(f'IP {ip} no attacks found', fg="blue"))
if ip in denied:
if unblock is False:
click.echo(click.style(f'IP {ip} is BLOCKED', fg="yellow", bold=True))
else:
undeny(ip)
click.echo(click.style(f'IP {ip} UNBLOCKED', fg="green", bold=True))
async def purge():
print(older_than)
found = Attack.select(Attack.ip).where(
(Attack.date < older_than.datetime ) )
for attack in found:
undeny(attack.ip)
attack.delete_instance()
@cli.command('info')
@click.argument('ip')
def waf_info(ip):
click.echo('Find blocked IP')
asyncio.run(info(ip))
@cli.command('unblock')
@click.argument('ip')
def waf_unblock(ip):
click.echo('Unblock blocked IP')
asyncio.run(info(ip,unblock=True))
@cli.command('purge') @cli.command('purge')
def waf_purge(): def waf_purge():
click.echo('Clean all blocked IPs') click.echo('Clean all blocked IPs')
asyncio.run(purge())
@cli.command('report') @cli.command('report')
def waf_report(): def waf_report():
click.echo(click.style('Report WAF attacks', fg="blue", bold=True)) click.echo(click.style('Report WAF attacks', fg="blue", bold=True))
report() report()
@cli.command('scan') @cli.command('scan')
def waf_scan(): def waf_scan():
before = arrow.utcnow() before = arrow.utcnow()
@ -175,5 +248,6 @@ def waf_scan():
after = arrow.utcnow() after = arrow.utcnow()
click.echo(click.style(f'Finished in {after - before}', fg="blue")) click.echo(click.style(f'Finished in {after - before}', fg="blue"))
if __name__ == '__main__': if __name__ == '__main__':
cli() cli()