From 369a1a90cdc6dd46888a4f0dd82869395789b57c Mon Sep 17 00:00:00 2001 From: root Date: Sun, 9 Apr 2023 21:19:36 +0200 Subject: [PATCH] unblock, info commands. no login supicious --- waf3.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/waf3.py b/waf3.py index b5f5884..ab4ead4 100644 --- a/waf3.py +++ b/waf3.py @@ -24,6 +24,7 @@ else: # with open('names.yaml', 'w') as file: now = arrow.utcnow() +older_than = now.shift(days=-3).floor('day') last_hour = now.shift(hours=-1).floor('hour') last_thirty_min = now.shift(minutes=-30) # last_period = last_thirty_min @@ -49,10 +50,11 @@ Attack.create_table(True) def cli(): pass + async def nginx_reload(): returned_value = subprocess.call('/usr/bin/systemctl reload nginx', shell=True) if returned_value == 0: - print('Nginx reloaded') + click.echo(click.style('Nginx reloaded', fg="blue")) async def get_denied(): denied = [] @@ -61,9 +63,22 @@ async def get_denied(): denied.append(rule.split(' ')[-1]) return denied + async def deny(ip): 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): date = arrow.get(date_position,'DD/MMM/YYYY:HH:mm:ss') 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} Attack.create(**data) + async def scan(log): suspects = [] - suspects_login = {} + # suspects_login = {} suspects_404 = {} for line in log.lines(): splitted = line.split() @@ -92,25 +108,28 @@ async def scan(log): suspects.append(check(ip, host, date_position)) elif 'xmlrpc.php' in url: suspects.append(check(ip, host, date_position)) - elif 'login.php' in url and method == 'POST': - if ip in suspects_login: - suspects_login[ip].append( (ip, host, date_position) ) - else: - suspects_login[ip] = [(ip, host, date_position),] + # elif 'login.php' in url and method == 'POST': + # if ip in suspects_login: + # suspects_login[ip].append( (ip, host, date_position) ) + # else: + # suspects_login[ip] = [(ip, host, date_position),] elif 'wp-admin' in url and status not in ['200','302','499']: suspects.append(check(ip, host, date_position)) - def is_suspicious_login(item): - return len(item[1]) > 18 - filtered = dict(filter(is_suspicious_login, suspects_login.items())) - for ip,suspect in filtered.items(): - suspects.append(check(ip, suspect[-1][1], suspect[-1][2])) + + # def is_suspicious_login(item): + # return len(item[1]) > 18 + # filtered = dict(filter(is_suspicious_login, suspects_login.items())) + + # for ip,suspect in filtered.items(): + # suspects.append(check(ip, suspect[-1][1], suspect[-1][2])) await asyncio.gather(*suspects) + async def block(): denied = await get_denied() found = Attack.select().where( (Attack.ip.not_in(denied)) & - (Attack.count > 7) + (Attack.count > 3) ) if found.count() > 0: click.echo(click.style('New IPs to block: {}'.format(found.count()), fg="yellow")) @@ -118,9 +137,19 @@ async def block(): for attack in found: to_deny.append( deny(attack.ip) ) await asyncio.gather(*to_deny) + await nginx_reload() else: 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(): click.echo( click.style( @@ -134,12 +163,7 @@ def report(): fg="cyan" ) ) - click.echo( - click.style( - f"Attacks in database: {Attack.select().count()}", - fg="cyan" - ) - ) + report_attacks() for ip in whitelist_ips: click.echo( click.style( @@ -148,6 +172,7 @@ def report(): ) ) + async def start(): report() scans = [] @@ -155,17 +180,65 @@ async def start(): scans.append(scan(log)) await asyncio.gather(*scans) await block() + report_attacks() #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') def waf_purge(): click.echo('Clean all blocked IPs') + asyncio.run(purge()) + @cli.command('report') def waf_report(): click.echo(click.style('Report WAF attacks', fg="blue", bold=True)) report() + @cli.command('scan') def waf_scan(): before = arrow.utcnow() @@ -175,5 +248,6 @@ def waf_scan(): after = arrow.utcnow() click.echo(click.style(f'Finished in {after - before}', fg="blue")) + if __name__ == '__main__': cli()