View previous topic :: View next topic |
Author |
Message |
Bigun Advocate
Joined: 21 Sep 2003 Posts: 2196
|
Posted: Wed Jun 23, 2021 1:35 pm Post subject: Rouge Access Point Watcher |
|
|
We have a potential security issue where peoples are setting up their own access points in an area that should never have one setup.
Is there a software suite that can be installed on a small portable device (like a raspberry pi or an Intel NUC) that can send an e-mail when an unauthorized AP is detected? _________________ "It's ok, they might have guns but we have flowers." - Perpetual Victim |
|
Back to top |
|
|
eccerr0r Watchman
Joined: 01 Jul 2004 Posts: 9601 Location: almost Mile High in the USA
|
Posted: Wed Jun 23, 2021 5:18 pm Post subject: |
|
|
Are you allowed to bring cell phones into that region?
Are ad hoc networks allowed?
Technically all that's needed is to get iwtools installed and scanning should find most networks as long as they beacon. _________________ Intel Core i7 2700K/Radeon R7 250/24GB DDR3/256GB SSD
What am I supposed watching? |
|
Back to top |
|
|
Bigun Advocate
Joined: 21 Sep 2003 Posts: 2196
|
Posted: Wed Jun 23, 2021 6:24 pm Post subject: |
|
|
eccerr0r wrote: | Are you allowed to bring cell phones into that region?
Are ad hoc networks allowed?
Technically all that's needed is to get iwtools installed and scanning should find most networks as long as they beacon. |
*I* am yes. But this is something that I would need to leave installed, in several different places. _________________ "It's ok, they might have guns but we have flowers." - Perpetual Victim |
|
Back to top |
|
|
Ralphred Guru
Joined: 31 Dec 2013 Posts: 466
|
Posted: Thu Jun 24, 2021 12:48 am Post subject: |
|
|
Bigun wrote: | But this is something that I would need to leave installed, in several different places. |
Here you go mate, wrote it on my Pi4, so know it runs on them. Need wpa_supplicant and sendmail, installed and running.
AP_check.py: | #!/usr/bin/env python
#AP_check.py
#Scans for AP's every 2 minutes, and sends emails about unknown/unauthorised AP's.
import sys, time, os, signal
from subprocess import Popen, PIPE
class bad_ap_checker():
def __init__(self):
### Start user variables ###
self.debug=True # True: only print email to be set, don't send. False: actually send emails
self.location='some identifying location'
self.email_to='some_admin@example.com some_security_bod@example.com'
self.email_from='Rogue_AP_Finder@example.com'
self.email_subject='Rogue AP found at: %s.'%self.location
self.email_sig='\nSent by a dodgy.py, a script running on some machine.'
self.allowed_ssid_macs=['f8:e9:03:dc:ab:68','f8:e9:03:dc:ab:67'] ##only needs to match one of these lines to be left alone, set to =[] to disable.
self.allowed_ssid_names=['VB0-2','VB0-2'] ##^^
self.purge_time=900 #time in seconds before sending a subsequent email about the same AP
self.wlan_interface='wlan0'
### end user variables ###
signal.signal(signal.SIGINT, self.eksit)
signal.signal(signal.SIGUSR1, self.eksit)
signal.signal(signal.SIGTERM, self.eksit)
self.found_aps={}
self.email_message=''
def eksit(self,sig,frame):
sys.exit(0)
def send_email(self):
email_message='%s\n\n%s\n.\n'%(self.email_message,self.email_sig)
if self.debug:
print('Would send the following email, but debug is set\n\n%s'%email_message)
else:
os.system('echo "%s"| sendmail %s'%(email_message,self.email_to))
def compose_message(self,message,clean=False):
if clean:
self.email_message='From: %s\nTo: %s\nSubject: %s'%(self.email_from,self.email_to,self.email_subject)
return
email_message='%s\n%s'%(self.email_message,message)
self.email_message=email_message
def run_scan(self):
if self.debug:print('Running scan...')
f=open('/dev/null','w')
args=['wpa_cli','-i',self.wlan_interface,'scan']
proc=Popen(args,stdout=f)
proc.wait()
def read_scan(self):
if self.debug:print('Reading scan results...')
args=['wpa_cli','-i',self.wlan_interface,'scan_results']
proc=Popen(args,stdout=PIPE, universal_newlines=True)
results=proc.communicate()[0].split('\n')
results.pop(0) #lose the header
results.pop(len(results)-1) #lose the '' at the end
bad_aps=[]
for result in results:
bad_aps.append(result)
test=result.split('\t')
if test[0] in self.allowed_ssid_macs or test[4] in self.allowed_ssid_names:
bad_aps.pop(bad_aps.index(result))
continue
for known in self.found_aps:
if test[0]==self.found_aps[known]:
bad_aps.pop(bad_aps.index(result))
continue
if self.debug:print('%s rogue AP\'s found.'%len(bad_aps))
return bad_aps
def main_loop(self):
while True:
purge=time.time()-self.purge_time
whens=[]
for key in self.found_aps:whens.append(key)
for when in whens:
if when < purge:
self.found_aps.pop(when)
self.run_scan()
timer=20 #Give 20 seconds for scan to complete
while timer >0:
time.sleep(1)
timer-=1
bad_aps=self.read_scan()
if bad_aps:
self.compose_message('',clean=True)
for bad_ap in bad_aps:
test=bad_ap.split('\t')
self.compose_message('A rogue AP with SSID: %s and MAC: %s has been found at %s.'%(test[4],test[0],self.location))
self.found_aps[time.time()]=bad_ap.split('\t')[0]
self.send_email()
timer=100 #rest of finish the 2 minute scan interval
while timer >0:
time.sleep(1)
timer-=1
if __name__=='__main__':
checker=bad_ap_checker()
checker.main_loop() |
That won't catch folks not broadcasting the AP's SSID, you'd need to scan for people 'looking for a specific ssid', and then check if it exists to detect those, but by then you are not dealing with your average Joe off the street. |
|
Back to top |
|
|
Bigun Advocate
Joined: 21 Sep 2003 Posts: 2196
|
Posted: Wed Jun 30, 2021 2:44 pm Post subject: |
|
|
The script works brilliantly!
I'm having issues trying to start it automatically via crontab:
Code: | @reboot python /home/pi/AP_checker.py > /home/pi/error.log 2>&1 |
and it keeps bombing out:
Code: | Traceback (most recent call last):
File "/home/pi/AP_checker.py", line 102, in <module>
checker.main_loop()
File "/home/pi/AP_checker.py", line 81, in main_loop
self.run_scan()
File "/home/pi/AP_checker.py", line 49, in run_scan
proc=Popen(args,stdout=f)
File "/usr/lib/python2.7/subprocess.py", line 394, in __init__
errread, errwrite)
File "/usr/lib/python2.7/subprocess.py", line 1047, in _execute_child
raise child_exception
OSError: [Errno 2] No such file or directory |
_________________ "It's ok, they might have guns but we have flowers." - Perpetual Victim |
|
Back to top |
|
|
Bigun Advocate
Joined: 21 Sep 2003 Posts: 2196
|
Posted: Wed Jun 30, 2021 6:04 pm Post subject: |
|
|
Way overkill, put I build a system.d daemon to handle starting it up and it worked. _________________ "It's ok, they might have guns but we have flowers." - Perpetual Victim |
|
Back to top |
|
|
szatox Advocate
Joined: 27 Aug 2013 Posts: 3095
|
Posted: Wed Jun 30, 2021 7:05 pm Post subject: |
|
|
I suppose it bombed because cron launches jobs in very limited environment.
Like in there are very few variables set and those that are set may differ from your (user's) environmental variables.
Try running "set" as a user and as a cron job (dump output to a file to inspect it), the difference is massive. |
|
Back to top |
|
|
Ralphred Guru
Joined: 31 Dec 2013 Posts: 466
|
Posted: Wed Jun 30, 2021 10:35 pm Post subject: |
|
|
Bigun wrote: | The script works brilliantly! |
Glad it works for you: About 5 years ago I suffered a brain injury and lost ~20 IQ points, doing things like this successfully are a win on two points, my attempts to claw back lost abilities are working, and your problem is solved |
|
Back to top |
|
|
Hu Moderator
Joined: 06 Mar 2007 Posts: 21431
|
Posted: Thu Jul 01, 2021 1:33 am Post subject: |
|
|
The cited line tries to run an external program, and does not specify an absolute path. For this to work, the program must be on $PATH. Consider: Code: | $ python -c 'import subprocess;subprocess.Popen(args=("ls", "--version"));' | head -n1
ls (GNU coreutils) 8.32
$ PATH=/ /usr/bin/python -c 'import subprocess;subprocess.Popen(args=("ls", "--version"));'
Traceback (most recent call last): | In the latter case, since I cleared $PATH, ls cannot be found. (Nor could a bare python, which is why it is shown qualified.) As szatox says, cron has likely done a similar thing. It offered a severely reduced $PATH, and the external program cannot be found. You could patch the script to reset $PATH to a more expansive value, or patch the script to call wpa_cli with an absolute path. |
|
Back to top |
|
|
Bigun Advocate
Joined: 21 Sep 2003 Posts: 2196
|
Posted: Thu Jul 01, 2021 2:36 pm Post subject: |
|
|
Ralphred wrote: | Bigun wrote: | The script works brilliantly! |
Glad it works for you: About 5 years ago I suffered a brain injury and lost ~20 IQ points, doing things like this successfully are a win on two points, my attempts to claw back lost abilities are working, and your problem is solved |
That's awesome! Not the injury, the accomplishment. You may have had a bad day on the test, I'd maybe give some thought at re-trying the test.
I added one more function to the script, in case anyone else starts using it. I added mac address manufacturer support (the first 3 sets from the mac address):
Code: | #!/usr/bin/env python
#AP_check.py
#Scans for AP's every 2 minutes, and sends emails about unknown/unauthorized AP's.
import sys, time, os, signal
from subprocess import Popen, PIPE
class bad_ap_checker():
def __init__(self):
### Start user variables ###
self.debug=True # True: only print email to be set, don't send. False: actually send emails
self.location='Somewhere'
self.email_to='someone@something.com another_someone@something.com'
self.email_from='someoneelse@something.com'
self.email_subject='Rogue AP found at: %s.'%self.location
self.email_sig='\nFrom Pi'
self.allowed_ssid_macs=['12:34:56:78:90:ab'] ##only needs to match one of these lines to be left alone, set to =[] to disable.
self.allowed_ssid_names=['some_trusted_AP'] ##^^
self.allowed_mac_manufacturers=['12:34:56'] ##^^
self.purge_time=900 #time in seconds before sending a subsequent email about the same AP
self.wlan_interface='wlan0'
### end user variables ###
signal.signal(signal.SIGINT, self.eksit)
signal.signal(signal.SIGUSR1, self.eksit)
signal.signal(signal.SIGTERM, self.eksit)
self.found_aps={}
self.email_message=''
def eksit(self,sig,frame):
sys.exit(0)
def send_email(self):
email_message='%s\n\n%s\n.\n'%(self.email_message,self.email_sig)
if self.debug:
print('Would send the following email, but debug is set\n\n%s'%email_message)
else:
os.system('echo "%s"| sendmail %s'%(email_message,self.email_to))
def compose_message(self,message,clean=False):
if clean:
self.email_message='From: %s\nTo: %s\nSubject: %s'%(self.email_from,self.email_to,self.email_subject)
return
email_message='%s\n%s'%(self.email_message,message)
self.email_message=email_message
def run_scan(self):
if self.debug:print('Running scan...')
f=open('/dev/null','w')
args=['wpa_cli','-i',self.wlan_interface,'scan']
proc=Popen(args,stdout=f)
proc.wait()
def read_scan(self):
if self.debug:print('Reading scan results...')
args=['wpa_cli','-i',self.wlan_interface,'scan_results']
proc=Popen(args,stdout=PIPE, universal_newlines=True)
results=proc.communicate()[0].split('\n')
results.pop(0) #lose the header
results.pop(len(results)-1) #lose the '' at the end
bad_aps=[]
for result in results:
bad_aps.append(result)
test=result.split('\t')
mac=test[0].split(':')
manufacturer=mac[0]+':'+mac[1]+':'+mac[2]
if test[0] in self.allowed_ssid_macs or test[4] in self.allowed_ssid_names or manufacturer in self.allowed_mac_manufacturers:
bad_aps.pop(bad_aps.index(result))
continue
for known in self.found_aps:
if test[0]==self.found_aps[known]:
bad_aps.pop(bad_aps.index(result))
continue
if self.debug:print('%s rogue AP\'s found.'%len(bad_aps))
return bad_aps
def main_loop(self):
while True:
purge=time.time()-self.purge_time
whens=[]
for key in self.found_aps:whens.append(key)
for when in whens:
if when < purge:
self.found_aps.pop(when)
self.run_scan()
timer=20 #Give 20 seconds for scan to complete
while timer >0:
time.sleep(1)
timer-=1
bad_aps=self.read_scan()
if bad_aps:
self.compose_message('',clean=True)
for bad_ap in bad_aps:
test=bad_ap.split('\t')
self.compose_message('A rogue AP with SSID: %s and MAC: %s has been found at %s.'%(test[4],test[0],self.location))
self.found_aps[time.time()]=bad_ap.split('\t')[0]
self.send_email()
timer=100 #rest of finish the 2 minute scan interval
while timer >0:
time.sleep(1)
timer-=1
if __name__=='__main__':
checker=bad_ap_checker()
checker.main_loop() |
_________________ "It's ok, they might have guns but we have flowers." - Perpetual Victim |
|
Back to top |
|
|
Ralphred Guru
Joined: 31 Dec 2013 Posts: 466
|
Posted: Thu Jul 01, 2021 3:58 pm Post subject: |
|
|
I'm gonna go all python tutor on your Code: | mac=test[0].split(':')
manufacturer=mac[0]+':'+mac[1]+':'+mac[2] |
you can use the string class' built join function to change the last line to Code: | manufacturer=':'.join([mac[0],mac[1],mac[2]]) | because ':' is a string, but Code: | [mac[0],mac[1],mac[2]] | is the same asso you are only using it once, so you may as well not assign a variable to it and use Code: | manufacturer=':'.join(test[0].split(':')[0:3]) | but that's ugly, and Code: | manufacturer=test[0][0:8] | does the job just fine |
|
Back to top |
|
|
Bigun Advocate
Joined: 21 Sep 2003 Posts: 2196
|
Posted: Wed Aug 18, 2021 3:47 pm Post subject: |
|
|
I'm having some issues getting things working using sendmail (not sure why because it was working before). Is there a way we can use python's smtplib library to handle sending the mail?
I'm trying to get it working using the library and I'm having some issues. It does work, but I can't get the body of the e-mail to populate.
*edit*
I got it working, but I'm sure you could make it more elegant than I can. No pressure. _________________ "It's ok, they might have guns but we have flowers." - Perpetual Victim |
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|