├── .gitignore ├── README.md ├── settings.py ├── lib ├── templates │ └── eap_cnf.py └── conf_manager.py └── eaptyper.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | tmp/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eaptyper 2 | Eaptyper is an automated auditing tool designed to actively fingerprint which all EAP methods (not just the preferred method) that are supported by the target WPA2-Enterprise wireless infrastructure. By enumerating all methods supported, it is possible to detect potential misconfigurations in the server infrastructure which could mean the client stations can connect to the infrastructure using insecure settings. 3 | 4 | ## Example 5 | ### Usage 6 | ``` 7 | ┌──(vagrant㉿vagrant-kali-rolling-amd64)-[~/eaptyper] 8 | └─$ sudo python3 eaptyper.py -s rogue -i wlan0 9 | [+] Connecting to target "rogue" network 10 | [-] Target network "rogue" proposed EAP method: md5 11 | [-] Following methods are supported by target network: 12 | [-] md5 13 | [-] peap 14 | [-] ttls 15 | [-] Following methods were rejected by target network: 16 | [-] tls 17 | [-] Following methods are not supported by wpa_supplicant client: 18 | 19 | ``` 20 | 21 | ## Dependencies 22 | 1. `wpa_supplicant` 23 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # python modules 3 | import os 4 | 5 | __version__ = "1.1" 6 | 7 | # Directory Mapping 8 | root_dir, conf_file = os.path.split(os.path.abspath(__file__)) 9 | working_dir = root_dir + '/tmp' 10 | 11 | # File Mapping 12 | wpa_supplicant_conf_file_location = working_dir + '/wpa_supplicant.conf' 13 | wpa_supplicant_logfile_location = working_dir 14 | default_ca_cert_location = working_dir + '/radius.pem' 15 | argparser_default_client_cert_location = working_dir + '/client.pem' 16 | argparser_default_client_private_key_location = working_dir + '/private.key' 17 | 18 | # Default Argument Options 19 | argparser_eap_username = 'infamoussyn' 20 | argparser_eap_password = 'infamoussyn' 21 | argparser_pairwise_encryption = 'CCMP TKIP' 22 | argparser_group_encryption = 'CCMP TKIP' 23 | argparser_default_client_private_password = 'infamoussyn' 24 | argpaser_timeout = 15 25 | 26 | # 27 | supported_eap_methods = [ 28 | 'md5', 29 | 'peap', 30 | 'tls', 31 | 'ttls' 32 | ] 33 | 34 | # Do not modify 35 | wpa_supplicant_lock = '/var/run/wpa_supplicant' 36 | -------------------------------------------------------------------------------- /lib/templates/eap_cnf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # https://manpages.debian.org/experimental/wpasupplicant/wpa_supplicant.conf.5.en.html 3 | 4 | wpa_supplicant_conf_peap_template = ''' 5 | ctrl_interface=/var/run/wpa_supplicant 6 | network={{ 7 | ssid="{}" 8 | scan_ssid={} 9 | key_mgmt=WPA-EAP 10 | eap=PEAP 11 | identity="{}" 12 | password="{}" 13 | phase1="peapver={}" 14 | phase2="auth={}" 15 | }} 16 | ''' 17 | 18 | 19 | wpa_supplicant_conf_md5_template = ''' 20 | ctrl_interface=/var/run/wpa_supplicant 21 | network={{ 22 | ssid="{}" 23 | scan_ssid={} 24 | key_mgmt=WPA-EAP 25 | eap=MD5 26 | identity="{}" 27 | password="{}" 28 | }} 29 | ''' 30 | 31 | wpa_supplicant_conf_tls_template = ''' 32 | ctrl_interface=/var/run/wpa_supplicant 33 | network={{ 34 | ssid="{}" 35 | scan_ssid={} 36 | key_mgmt=WPA-EAP 37 | eap=TLS 38 | identity="{}" 39 | client_cert="{}" 40 | private_key="{}" 41 | private_key_passwd="{}" 42 | }} 43 | ''' 44 | 45 | wpa_supplicant_conf_ttls_template = ''' 46 | ctrl_interface=/var/run/wpa_supplicant 47 | network={{ 48 | ssid="{}" 49 | scan_ssid={} 50 | key_mgmt=WPA-EAP 51 | eap=TTLS 52 | identity="{}" 53 | password="{}" 54 | phase2="auth={}" 55 | }} 56 | ''' 57 | -------------------------------------------------------------------------------- /lib/conf_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import settings as config 3 | from lib.templates import eap_cnf 4 | 5 | class wpa_supplicant_conf_peap(object): 6 | path = config.wpa_supplicant_conf_file_location 7 | template = eap_cnf.wpa_supplicant_conf_peap_template 8 | 9 | @classmethod 10 | def configure(cls, verbose, ssid, scan_ssid, identity, password, phase1, phase2): 11 | try: 12 | if(verbose): 13 | print('[+] Creating wpa_supplicant.conf file: {}'.format(cls.path)) 14 | with open(cls.path, 'w') as fd: 15 | fd.write(cls.template.format( 16 | ssid, 17 | scan_ssid, 18 | identity, 19 | password, 20 | phase1, 21 | phase2 22 | ) 23 | ) 24 | return 0 25 | except Exception as e: 26 | print('[!] Error: {}'.format(e)) 27 | return 1 28 | 29 | class wpa_supplicant_conf_md5(object): 30 | path = config.wpa_supplicant_conf_file_location 31 | template = eap_cnf.wpa_supplicant_conf_md5_template 32 | 33 | @classmethod 34 | def configure(cls, verbose, ssid, scan_ssid, identity, password): 35 | try: 36 | if(verbose): 37 | print('[+] Creating wpa_supplicant.conf file: {}'.format(cls.path)) 38 | with open(cls.path, 'w') as fd: 39 | fd.write(cls.template.format( 40 | ssid, 41 | scan_ssid, 42 | identity, 43 | password 44 | ) 45 | ) 46 | return 0 47 | except Exception as e: 48 | print('[!] Error: {}'.format(e)) 49 | return 1 50 | 51 | class wpa_supplicant_conf_tls(object): 52 | path = config.wpa_supplicant_conf_file_location 53 | template = eap_cnf.wpa_supplicant_conf_tls_template 54 | 55 | @classmethod 56 | def configure(cls, verbose, ssid, scan_ssid, identity, client_cert, private_key, private_passwd): 57 | try: 58 | if(verbose): 59 | print('[+] Creating wpa_supplicant.conf file: {}'.format(cls.path)) 60 | with open(cls.path, 'w') as fd: 61 | fd.write(cls.template.format( 62 | ssid, 63 | scan_ssid, 64 | identity, 65 | client_cert, 66 | private_key, 67 | private_passwd 68 | ) 69 | ) 70 | return 0 71 | except Exception as e: 72 | print('[!] Error: {}'.format(e)) 73 | return 1 74 | 75 | class wpa_supplicant_conf_ttls(object): 76 | path = config.wpa_supplicant_conf_file_location 77 | template = eap_cnf.wpa_supplicant_conf_ttls_template 78 | 79 | @classmethod 80 | def configure(cls, verbose, ssid, scan_ssid, identity, password, phase2): 81 | try: 82 | if(verbose): 83 | print('[+] Creating wpa_supplicant.conf file: {}'.format(cls.path)) 84 | with open(cls.path, 'w') as fd: 85 | fd.write(cls.template.format( 86 | ssid, 87 | scan_ssid, 88 | identity, 89 | password, 90 | phase2 91 | ) 92 | ) 93 | return 0 94 | except Exception as e: 95 | print('[!] Error: {}'.format(e)) 96 | return 1 97 | -------------------------------------------------------------------------------- /eaptyper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import argparse, subprocess, os 4 | import time, datetime, signal 5 | 6 | from lib import conf_manager 7 | import settings as config 8 | 9 | parser = argparse.ArgumentParser(description='Automated Wireless Supported EAP-Method Fingerprinting Tool') 10 | eapSettingsOptions = parser.add_argument_group(description='EEAP Settings') 11 | eapPeapOptions = parser.add_argument_group(description='EAP-PEAP Specific Settings') 12 | eapInnerAuthOptions = parser.add_argument_group(description='Inner Auth Settings (PEAP/TTLS)') 13 | 14 | parser.add_argument('--version', action='version', version=config.__version__) 15 | parser.add_argument('-v', '--verbose', dest='verbose', default=False, action='store_true', help='toggle verbosity') 16 | parser.add_argument('-i', '--interface', dest='interface', help='Specify interface') 17 | parser.add_argument('-t', '--timeout', dest='timeout', type=int, default=config.argpaser_timeout, help='Control timeout for wpa_supplicant connection length. Default: {}'.format(config.argpaser_timeout)) 18 | parser.add_argument('-s', '--ssid', dest='ssid', help='Specify target SSID.') 19 | parser.add_argument('--hidden', dest='hidden', action='store_true', default=False, help='Toggle for hidden network detection. Default: False') 20 | 21 | eapSettingsOptions.add_argument('--pairwise', dest='pairwise', default=config.argparser_pairwise_encryption, choices=['CCMP TKIP', 'TKIP CCMP'], help='Specify pairwise encryption order. Default: {}'.format(config.argparser_pairwise_encryption)) 22 | eapSettingsOptions.add_argument('--group', dest='group', default=config.argparser_group_encryption, choices=['CCMP TKIP', 'TKIP CCMP'], help='Specify group encryption order. Default: {}'.format(config.argparser_group_encryption)) 23 | eapSettingsOptions.add_argument('--identity', dest='identity', default=config.argparser_eap_username, help='Specify EAP identity. Default: {}'.format(config.argparser_eap_username)) 24 | eapSettingsOptions.add_argument('--password', dest='password', default=config.argparser_eap_password, help='Specify EAP identity Password. Default: {}'.format(config.argparser_eap_password)) 25 | eapSettingsOptions.add_argument('--client-cert', dest='client_cert', default=config.argparser_default_client_cert_location, help='Specify Client public certificate. Default: {}'.format(config.argparser_default_client_cert_location)) 26 | eapSettingsOptions.add_argument('--private-key', dest='private_key', default=config.argparser_default_client_private_key_location, help='Specify Client private key location. Default: {}'.format(config.argparser_default_client_private_key_location)) 27 | eapSettingsOptions.add_argument('--private-passwd', dest='private_passwd', default=config.argparser_default_client_private_password, help='Specify Client private password. Default: {}'.format(config.argparser_default_client_private_password)) 28 | 29 | # PEAP 30 | eapPeapOptions.add_argument('--phase1', dest='phase1', default=0, help='') 31 | 32 | # Inner Auth 33 | eapInnerAuthOptions.add_argument('--phase2', dest='phase2', default='MSCHAPV2', choices=['MD5', 'MSCHAPV1', 'MSCHAPV2'], help='Select inner EAP tunnel authentication method') 34 | 35 | args, leftover = parser.parse_known_args() 36 | options = args.__dict__ 37 | 38 | class wpaSupplicantWrapper(): 39 | 40 | @classmethod 41 | def __init__(self, target_ssid): 42 | self.supported_eap_methods_list = list() 43 | self.unsupported_eap_methods_list = list() 44 | self.untested_eap_methods_list = list() 45 | self.proposedMethod='' 46 | self.target_ssid=target_ssid 47 | 48 | @classmethod 49 | def wpaSupplicantReporter(self): 50 | print('[-] Target network "{}" proposed EAP method: {}'.format(self.target_ssid, self.proposedMethod)) 51 | print('[-] Following methods are supported by target network:') 52 | for supported in self.supported_eap_methods_list: 53 | print('[-] {}'.format(supported)) 54 | print('[-] Following methods were rejected by target network:') 55 | for rejected in self.unsupported_eap_methods_list: 56 | print('[-] {}'.format(rejected)) 57 | print('[-] Following methods are not supported by wpa_supplicant client:') 58 | for untested in self.untested_eap_methods_list: 59 | print('[-] {}'.format(untested)) 60 | print('[-]') 61 | return 62 | 63 | 64 | @classmethod 65 | def wpaSupplicantOutput(self, wsRawOutput, target_method): 66 | import re 67 | 68 | # https://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml 69 | eapMethod_dict = { 70 | 4:'md5', 25:'peap', 52:'pwd', 71 | 43:'fast', 21:'ttls', 13:'tls', 72 | 17:'leap', 6:'gtc' 73 | } 74 | try: 75 | clientProposedMethodSelected = [] 76 | clientProposedMethodFailure = [] 77 | 78 | # quick logic check to make sure EAP method is supported by wpa_supplicant client 79 | wsCheckMethodSupport = [] 80 | wsCheckMethodSupport = [ s for s in wsRawOutput.decode('utf-8').split('\n') if 'unknown EAP' in s ] 81 | if(wsCheckMethodSupport): 82 | self.untested_eap_methods_list.append(target_method) 83 | return 1 84 | 85 | # Checks for what is being proposed by the Base Station 86 | proposedMethod = [ s for s in wsRawOutput.decode('utf-8').split('\n') if 'CTRL-EVENT-EAP-PROPOSED-METHOD' in s ] 87 | if(not proposedMethod): 88 | proposedMethod = [None] 89 | 90 | ## Dirty logic to clean up junk in proposed-method message 91 | if(int(proposedMethod[0].split('=')[2][0]) == 4): 92 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=4'] 93 | elif(int(proposedMethod[0].split('=')[2][0]) == 6): 94 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=6'] 95 | elif(int(proposedMethod[0].split('=')[2][0:2]) == 13): 96 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=13'] 97 | elif(int(proposedMethod[0].split('=')[2][0:2]) == 17): 98 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=17'] 99 | elif(int(proposedMethod[0].split('=')[2][0:2]) == 21): 100 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=21'] 101 | elif(int(proposedMethod[0].split('=')[2][0:2]) == 25): 102 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=25'] 103 | elif(int(proposedMethod[0].split('=')[2][0:2]) == 43): 104 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=43'] 105 | elif(int(proposedMethod[0].split('=')[2][0:2]) == 52): 106 | proposedMethod = ['wlan1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=52'] 107 | 108 | ## method look up func 109 | for key in eapMethod_dict.keys(): 110 | if((key == int(proposedMethod[0].split('=')[2])) and (self.proposedMethod == '')): 111 | self.proposedMethod = eapMethod_dict[key] 112 | 113 | # Checks for the acceptance of client proposed EAP method 114 | clientMethodSelected = [ s for s in wsRawOutput.decode('utf-8').split('\n') if 'selected' in s ] 115 | if(clientMethodSelected): 116 | if(target_method not in self.supported_eap_methods_list): 117 | self.supported_eap_methods_list.append(target_method) 118 | else: 119 | if(target_method not in self.unsupported_eap_methods_list): 120 | self.unsupported_eap_methods_list.append(target_method) 121 | except Exception as e: 122 | print('[!] Error: {}'.format(e)) 123 | print('wpa_supplicant output:\r\n{}'.format(wsRawOutput.decode('utf-8'))) 124 | return 1 125 | return 0 126 | 127 | @classmethod 128 | def wpaSupplicantCtrl(self, target_method): 129 | command = [ 130 | 'wpa_supplicant', 131 | '-i{}'.format(options['interface']), 132 | '-c{}'.format(config.wpa_supplicant_conf_file_location), 133 | '-f{}/{}_{}_wpa_supplicant.log'.format(config.wpa_supplicant_logfile_location, self.target_ssid, target_method) 134 | ] 135 | if(options['verbose']): 136 | print('[+] Executing wpa_supplicant command: {}'.format(command)) 137 | try: 138 | # http://howto.philippkeller.com/2007/02/18/set-timeout-for-a-shell-command-in-python/ 139 | start = datetime.datetime.now() 140 | ps = subprocess.Popen(command, 141 | shell=False, 142 | stdin=subprocess.PIPE, 143 | stdout=subprocess.PIPE, 144 | stderr=subprocess.PIPE 145 | ) 146 | while(ps.poll() is None): 147 | time.sleep(0.1) 148 | now = datetime.datetime.now() 149 | if((now-start).seconds > options['timeout']): 150 | os.kill(ps.pid, signal.SIGKILL) 151 | os.waitpid(-1, os.WNOHANG) 152 | #print(ps.stdout.read()) 153 | self.wpaSupplicantOutput(wsRawOutput=ps.stdout.read(), target_method=target_method) 154 | except Exception as e: 155 | print('[!] Error: {}'.format(e)) 156 | return 1 157 | return 0 158 | 159 | if __name__ == '__main__': 160 | try: 161 | if(os.path.isfile(config.default_ca_cert_location) is not True): 162 | print('[!] You\'ve forgotten about creating the radius.pem file in: {}'.format(config.default_ca_cert_location)) 163 | exit(1) 164 | w = wpaSupplicantWrapper( 165 | target_ssid=options['ssid'] 166 | ) 167 | print('[+] Connecting to target "{}" network'.format(options['ssid'])) 168 | options['hidden'] = 1 if options['hidden'] else 0 169 | for method in config.supported_eap_methods: 170 | if(method == 'peap'): 171 | conf_manager.wpa_supplicant_conf_peap.configure( 172 | verbose=options['verbose'], 173 | ssid=options['ssid'], 174 | scan_ssid=options['hidden'], 175 | identity=options['identity'], 176 | password=options['password'], 177 | phase1=options['phase1'], 178 | phase2=options['phase2'] 179 | ) 180 | if(method == 'md5'): 181 | conf_manager.wpa_supplicant_conf_md5.configure( 182 | verbose=options['verbose'], 183 | ssid=options['ssid'], 184 | scan_ssid=options['hidden'], 185 | identity=options['identity'], 186 | password=options['password'] 187 | ) 188 | if(method == 'ttls'): 189 | conf_manager.wpa_supplicant_conf_ttls.configure( 190 | verbose=options['verbose'], 191 | ssid=options['ssid'], 192 | scan_ssid=options['hidden'], 193 | identity=options['identity'], 194 | password=options['password'], 195 | phase2=options['phase2'] 196 | ) 197 | if(method == 'tls'): 198 | conf_manager.wpa_supplicant_conf_tls.configure( 199 | verbose=options['verbose'], 200 | ssid=options['ssid'], 201 | scan_ssid=options['hidden'], 202 | identity=options['identity'], 203 | client_cert=options['client_cert'], 204 | private_key=options['private_key'], 205 | private_passwd=options['private_passwd'] 206 | ) 207 | w.wpaSupplicantCtrl(target_method=method) 208 | w.wpaSupplicantReporter() 209 | print('[-] Finished!') 210 | except Exception as e: 211 | print('Error: {}'.format(e)) 212 | exit(1) 213 | --------------------------------------------------------------------------------