Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
Rouge Access Point Watcher
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Networking & Security
View previous topic :: View next topic  
Author Message
Bigun
Advocate
Advocate


Joined: 21 Sep 2003
Posts: 2196

PostPosted: Wed Jun 23, 2021 1:35 pm    Post subject: Rouge Access Point Watcher Reply with quote

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
View user's profile Send private message
eccerr0r
Watchman
Watchman


Joined: 01 Jul 2004
Posts: 9679
Location: almost Mile High in the USA

PostPosted: Wed Jun 23, 2021 5:18 pm    Post subject: Reply with quote

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
View user's profile Send private message
Bigun
Advocate
Advocate


Joined: 21 Sep 2003
Posts: 2196

PostPosted: Wed Jun 23, 2021 6:24 pm    Post subject: Reply with quote

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
View user's profile Send private message
Ralphred
Guru
Guru


Joined: 31 Dec 2013
Posts: 499

PostPosted: Thu Jun 24, 2021 12:48 am    Post subject: Reply with quote

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
View user's profile Send private message
Bigun
Advocate
Advocate


Joined: 21 Sep 2003
Posts: 2196

PostPosted: Wed Jun 30, 2021 2:44 pm    Post subject: Reply with quote

Ralphred wrote:
**snip**


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
View user's profile Send private message
Bigun
Advocate
Advocate


Joined: 21 Sep 2003
Posts: 2196

PostPosted: Wed Jun 30, 2021 6:04 pm    Post subject: Reply with quote

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
View user's profile Send private message
szatox
Advocate
Advocate


Joined: 27 Aug 2013
Posts: 3134

PostPosted: Wed Jun 30, 2021 7:05 pm    Post subject: Reply with quote

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
View user's profile Send private message
Ralphred
Guru
Guru


Joined: 31 Dec 2013
Posts: 499

PostPosted: Wed Jun 30, 2021 10:35 pm    Post subject: Reply with quote

Bigun wrote:
Ralphred wrote:
**snip**
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
View user's profile Send private message
Hu
Moderator
Moderator


Joined: 06 Mar 2007
Posts: 21624

PostPosted: Thu Jul 01, 2021 1:33 am    Post subject: Reply with quote

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
View user's profile Send private message
Bigun
Advocate
Advocate


Joined: 21 Sep 2003
Posts: 2196

PostPosted: Thu Jul 01, 2021 2:36 pm    Post subject: Reply with quote

Ralphred wrote:
Bigun wrote:
Ralphred wrote:
**snip**
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
View user's profile Send private message
Ralphred
Guru
Guru


Joined: 31 Dec 2013
Posts: 499

PostPosted: Thu Jul 01, 2021 3:58 pm    Post subject: Reply with quote

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 as
Code:
mac[0:3]
so 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
View user's profile Send private message
Bigun
Advocate
Advocate


Joined: 21 Sep 2003
Posts: 2196

PostPosted: Wed Aug 18, 2021 3:47 pm    Post subject: Reply with quote

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
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Networking & Security All times are GMT
Page 1 of 1

 
Jump to:  
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