unblock, info commands. no login supicious
This commit is contained in:
112
waf3.py
112
waf3.py
@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user