Guest User

Untitled

a guest
Sep 6th, 2018
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 37.14 KB | None | 0 0
  1. #!/usr/bin/env python2
  2.  
  3. from os import geteuid, devnull
  4. import logging
  5. # shut up scapy
  6. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  7. from scapy.all import *
  8. conf.verb=0
  9. from sys import exit
  10. import binascii
  11. import struct
  12. import argparse
  13. import signal
  14. import base64
  15. from urllib import unquote
  16. import platform
  17. from subprocess import Popen, PIPE, check_output
  18. from collections import OrderedDict
  19. from BaseHTTPServer import BaseHTTPRequestHandler
  20. from StringIO import StringIO
  21. from urllib import unquote
  22. #import binascii #already imported on line 10
  23. # Debug
  24. #from IPython import embed
  25.  
  26. ##########################
  27. # Potention ToDo:
  28. # MySQL seed:hash
  29. # VNC
  30. # Oracle?
  31. # Add file carving from dissectors.py
  32. #########################
  33.  
  34. # Unintentional code contributors:
  35. # Laurent Gaffie
  36. # psychomario
  37.  
  38. logging.basicConfig(filename='credentials.txt',level=logging.INFO)
  39. DN = open(devnull, 'w')
  40. pkt_frag_loads = OrderedDict()
  41. challenge_acks = OrderedDict()
  42. mail_auths = OrderedDict()
  43. telnet_stream = OrderedDict()
  44.  
  45. # Regexs
  46. authenticate_re = '(www-|proxy-)?authenticate'
  47. authorization_re = '(www-|proxy-)?authorization'
  48. ftp_user_re = r'USER (.+)\r\n'
  49. ftp_pw_re = r'PASS (.+)\r\n'
  50. irc_user_re = r'NICK (.+?)((\r)?\n|\s)'
  51. irc_pw_re = r'NS IDENTIFY (.+)'
  52. irc_pw_re2 = 'nickserv :identify (.+)'
  53. mail_auth_re = '(\d+ )?(auth|authenticate) (login|plain)'
  54. mail_auth_re1 = '(\d+ )?login '
  55. NTLMSSP2_re = 'NTLMSSP\x00\x02\x00\x00\x00.+'
  56. NTLMSSP3_re = 'NTLMSSP\x00\x03\x00\x00\x00.+'
  57. # Prone to false+ but prefer that to false-
  58. http_search_re = '((search|query|&q|\?q|search\?p|searchterm|keywords|keyword|command|terms|keys|question|kwd|searchPhrase)=([^&][^&]*))'
  59.  
  60. #Console colors
  61. W = '\033[0m' # white (normal)
  62. T = '\033[93m' # tan
  63.  
  64. def parse_args():
  65. """Create the arguments"""
  66. parser = argparse.ArgumentParser()
  67. parser.add_argument("-i", "--interface", help="Choose an interface")
  68. parser.add_argument("-p", "--pcap", help="Parse info from a pcap file; -p <pcapfilename>")
  69. parser.add_argument("-f", "--filterip", help="Do not sniff packets from this IP address; -f 192.168.0.4")
  70. parser.add_argument("-v", "--verbose", help="Display entire URLs and POST loads rather than truncating at 100 characters", action="store_true")
  71. return parser.parse_args()
  72.  
  73. def iface_finder():
  74. system_platform = platform.system()
  75. if system_platform == 'Linux':
  76. ipr = Popen(['/sbin/ip', 'route'], stdout=PIPE, stderr=DN)
  77. for line in ipr.communicate()[0].splitlines():
  78. if 'default' in line:
  79. l = line.split()
  80. iface = l[4]
  81. return iface
  82. elif system_platform == 'Darwin': # OSX support
  83. return check_output("route get 0.0.0.0 2>/dev/null| sed -n '5p' | cut -f4 -d' '", shell=True).rstrip()
  84. else:
  85. exit('[-] Could not find an internet active interface; please specify one with -i <interface>')
  86.  
  87. def frag_remover(ack, load):
  88. '''
  89. Keep the FILO OrderedDict of frag loads from getting too large
  90. 3 points of limit:
  91. Number of ip_ports < 50
  92. Number of acks per ip:port < 25
  93. Number of chars in load < 5000
  94. '''
  95. global pkt_frag_loads
  96.  
  97. # Keep the number of IP:port mappings below 50
  98. # last=False pops the oldest item rather than the latest
  99. while len(pkt_frag_loads) > 50:
  100. pkt_frag_loads.popitem(last=False)
  101.  
  102. # Loop through a deep copy dict but modify the original dict
  103. copy_pkt_frag_loads = copy.deepcopy(pkt_frag_loads)
  104. for ip_port in copy_pkt_frag_loads:
  105. if len(copy_pkt_frag_loads[ip_port]) > 0:
  106. # Keep 25 ack:load's per ip:port
  107. while len(copy_pkt_frag_loads[ip_port]) > 25:
  108. pkt_frag_loads[ip_port].popitem(last=False)
  109.  
  110. # Recopy the new dict to prevent KeyErrors for modifying dict in loop
  111. copy_pkt_frag_loads = copy.deepcopy(pkt_frag_loads)
  112. for ip_port in copy_pkt_frag_loads:
  113. # Keep the load less than 75,000 chars
  114. for ack in copy_pkt_frag_loads[ip_port]:
  115. # If load > 5000 chars, just keep the last 200 chars
  116. if len(copy_pkt_frag_loads[ip_port][ack]) > 5000:
  117. pkt_frag_loads[ip_port][ack] = pkt_frag_loads[ip_port][ack][-200:]
  118.  
  119. def frag_joiner(ack, src_ip_port, load):
  120. '''
  121. Keep a store of previous fragments in an OrderedDict named pkt_frag_loads
  122. '''
  123. for ip_port in pkt_frag_loads:
  124. if src_ip_port == ip_port:
  125. if ack in pkt_frag_loads[src_ip_port]:
  126. # Make pkt_frag_loads[src_ip_port][ack] = full load
  127. old_load = pkt_frag_loads[src_ip_port][ack]
  128. concat_load = old_load + load
  129. return OrderedDict([(ack, concat_load)])
  130.  
  131. return OrderedDict([(ack, load)])
  132.  
  133. def pkt_parser(pkt):
  134. '''
  135. Start parsing packets here
  136. '''
  137. global pkt_frag_loads, mail_auths
  138.  
  139. if pkt.haslayer(Raw):
  140. load = pkt[Raw].load
  141.  
  142. # Get rid of Ethernet pkts with just a raw load cuz these are usually network controls like flow control
  143. if pkt.haslayer(Ether) and pkt.haslayer(Raw) and not pkt.haslayer(IP) and not pkt.haslayer(IPv6):
  144. return
  145.  
  146. # UDP
  147. if pkt.haslayer(UDP) and pkt.haslayer(IP):
  148.  
  149. src_ip_port = str(pkt[IP].src) + ':' + str(pkt[UDP].sport)
  150. dst_ip_port = str(pkt[IP].dst) + ':' + str(pkt[UDP].dport)
  151.  
  152. # SNMP community strings
  153. if pkt.haslayer(SNMP):
  154. parse_snmp(src_ip_port, dst_ip_port, pkt[SNMP])
  155. return
  156.  
  157. # Kerberos over UDP
  158. decoded = Decode_Ip_Packet(str(pkt)[14:])
  159. kerb_hash = ParseMSKerbv5UDP(decoded['data'][8:])
  160. if kerb_hash:
  161. printer(src_ip_port, dst_ip_port, kerb_hash)
  162.  
  163. # TCP
  164. elif pkt.haslayer(TCP) and pkt.haslayer(Raw) and pkt.haslayer(IP):
  165.  
  166. ack = str(pkt[TCP].ack)
  167. seq = str(pkt[TCP].seq)
  168. src_ip_port = str(pkt[IP].src) + ':' + str(pkt[TCP].sport)
  169. dst_ip_port = str(pkt[IP].dst) + ':' + str(pkt[TCP].dport)
  170. frag_remover(ack, load)
  171. pkt_frag_loads[src_ip_port] = frag_joiner(ack, src_ip_port, load)
  172. full_load = pkt_frag_loads[src_ip_port][ack]
  173.  
  174. # Limit the packets we regex to increase efficiency
  175. # 750 is a bit arbitrary but some SMTP auth success pkts
  176. # are 500+ characters
  177. if 0 < len(full_load) < 750:
  178.  
  179. # FTP
  180. ftp_creds = parse_ftp(full_load, dst_ip_port)
  181. if len(ftp_creds) > 0:
  182. for msg in ftp_creds:
  183. printer(src_ip_port, dst_ip_port, msg)
  184. return
  185.  
  186. # Mail
  187. mail_creds_found = mail_logins(full_load, src_ip_port, dst_ip_port, ack, seq)
  188.  
  189. # IRC
  190. irc_creds = irc_logins(full_load, pkt)
  191. if irc_creds != None:
  192. printer(src_ip_port, dst_ip_port, irc_creds)
  193. return
  194.  
  195. # Telnet
  196. telnet_logins(src_ip_port, dst_ip_port, load, ack, seq)
  197.  
  198. # HTTP and other protocols that run on TCP + a raw load
  199. other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt, parse_args().verbose)
  200.  
  201. def telnet_logins(src_ip_port, dst_ip_port, load, ack, seq):
  202. '''
  203. Catch telnet logins and passwords
  204. '''
  205. global telnet_stream
  206.  
  207. msg = None
  208.  
  209. if src_ip_port in telnet_stream:
  210. # Do a utf decode in case the client sends telnet options before their username
  211. # No one would care to see that
  212. try:
  213. telnet_stream[src_ip_port] += load.decode('utf8')
  214. except UnicodeDecodeError:
  215. pass
  216.  
  217. # \r or \r\n or \n terminate commands in telnet if my pcaps are to be believed
  218. if '\r' in telnet_stream[src_ip_port] or '\n' in telnet_stream[src_ip_port]:
  219. telnet_split = telnet_stream[src_ip_port].split(' ', 1)
  220. cred_type = telnet_split[0]
  221. value = telnet_split[1].replace('\r\n', '').replace('\r', '').replace('\n', '')
  222. # Create msg, the return variable
  223. msg = 'Telnet %s: %s' % (cred_type, value)
  224. printer(src_ip_port, dst_ip_port, msg)
  225. del telnet_stream[src_ip_port]
  226.  
  227. # This part relies on the telnet packet ending in
  228. # "login:", "password:", or "username:" and being <750 chars
  229. # Haven't seen any false+ but this is pretty general
  230. # might catch some eventually
  231. # maybe use dissector.py telnet lib?
  232. if len(telnet_stream) > 100:
  233. telnet_stream.popitem(last=False)
  234. mod_load = load.lower().strip()
  235. if mod_load.endswith('username:') or mod_load.endswith('login:'):
  236. telnet_stream[dst_ip_port] = 'username '
  237. elif mod_load.endswith('password:'):
  238. telnet_stream[dst_ip_port] = 'password '
  239.  
  240. def ParseMSKerbv5TCP(Data):
  241. '''
  242. Taken from Pcredz because I didn't want to spend the time doing this myself
  243. I should probably figure this out on my own but hey, time isn't free, why reinvent the wheel?
  244. Maybe replace this eventually with the kerberos python lib
  245. Parses Kerberosv5 hashes from packets
  246. '''
  247. try:
  248. MsgType = Data[21:22]
  249. EncType = Data[43:44]
  250. MessageType = Data[32:33]
  251. except IndexError:
  252. return
  253.  
  254. if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02":
  255. if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33":
  256. HashLen = struct.unpack('<b',Data[50:51])[0]
  257. if HashLen == 54:
  258. Hash = Data[53:105]
  259. SwitchHash = Hash[16:]+Hash[0:16]
  260. NameLen = struct.unpack('<b',Data[153:154])[0]
  261. Name = Data[154:154+NameLen]
  262. DomainLen = struct.unpack('<b',Data[154+NameLen+3:154+NameLen+4])[0]
  263. Domain = Data[154+NameLen+4:154+NameLen+4+DomainLen]
  264. BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
  265. return 'MS Kerberos: %s' % BuildHash
  266.  
  267. if Data[44:48] == "\xa2\x36\x04\x34" or Data[44:48] == "\xa2\x35\x04\x33":
  268. HashLen = struct.unpack('<b',Data[47:48])[0]
  269. Hash = Data[48:48+HashLen]
  270. SwitchHash = Hash[16:]+Hash[0:16]
  271. NameLen = struct.unpack('<b',Data[HashLen+96:HashLen+96+1])[0]
  272. Name = Data[HashLen+97:HashLen+97+NameLen]
  273. DomainLen = struct.unpack('<b',Data[HashLen+97+NameLen+3:HashLen+97+NameLen+4])[0]
  274. Domain = Data[HashLen+97+NameLen+4:HashLen+97+NameLen+4+DomainLen]
  275. BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
  276. return 'MS Kerberos: %s' % BuildHash
  277.  
  278. else:
  279. Hash = Data[48:100]
  280. SwitchHash = Hash[16:]+Hash[0:16]
  281. NameLen = struct.unpack('<b',Data[148:149])[0]
  282. Name = Data[149:149+NameLen]
  283. DomainLen = struct.unpack('<b',Data[149+NameLen+3:149+NameLen+4])[0]
  284. Domain = Data[149+NameLen+4:149+NameLen+4+DomainLen]
  285. BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
  286. return 'MS Kerberos: %s' % BuildHash
  287.  
  288. def ParseMSKerbv5UDP(Data):
  289. '''
  290. Taken from Pcredz because I didn't want to spend the time doing this myself
  291. I should probably figure this out on my own but hey, time isn't free why reinvent the wheel?
  292. Maybe replace this eventually with the kerberos python lib
  293. Parses Kerberosv5 hashes from packets
  294. '''
  295.  
  296. try:
  297. MsgType = Data[17:18]
  298. EncType = Data[39:40]
  299. except IndexError:
  300. return
  301.  
  302. if MsgType == "\x0a" and EncType == "\x17":
  303. try:
  304. if Data[40:44] == "\xa2\x36\x04\x34" or Data[40:44] == "\xa2\x35\x04\x33":
  305. HashLen = struct.unpack('<b',Data[41:42])[0]
  306. if HashLen == 54:
  307. Hash = Data[44:96]
  308. SwitchHash = Hash[16:]+Hash[0:16]
  309. NameLen = struct.unpack('<b',Data[144:145])[0]
  310. Name = Data[145:145+NameLen]
  311. DomainLen = struct.unpack('<b',Data[145+NameLen+3:145+NameLen+4])[0]
  312. Domain = Data[145+NameLen+4:145+NameLen+4+DomainLen]
  313. BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
  314. return 'MS Kerberos: %s' % BuildHash
  315.  
  316. if HashLen == 53:
  317. Hash = Data[44:95]
  318. SwitchHash = Hash[16:]+Hash[0:16]
  319. NameLen = struct.unpack('<b',Data[143:144])[0]
  320. Name = Data[144:144+NameLen]
  321. DomainLen = struct.unpack('<b',Data[144+NameLen+3:144+NameLen+4])[0]
  322. Domain = Data[144+NameLen+4:144+NameLen+4+DomainLen]
  323. BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
  324. return 'MS Kerberos: %s' % BuildHash
  325.  
  326. else:
  327. HashLen = struct.unpack('<b',Data[48:49])[0]
  328. Hash = Data[49:49+HashLen]
  329. SwitchHash = Hash[16:]+Hash[0:16]
  330. NameLen = struct.unpack('<b',Data[HashLen+97:HashLen+97+1])[0]
  331. Name = Data[HashLen+98:HashLen+98+NameLen]
  332. DomainLen = struct.unpack('<b',Data[HashLen+98+NameLen+3:HashLen+98+NameLen+4])[0]
  333. Domain = Data[HashLen+98+NameLen+4:HashLen+98+NameLen+4+DomainLen]
  334. BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
  335. return 'MS Kerberos: %s' % BuildHash
  336. except struct.error:
  337. return
  338.  
  339. def Decode_Ip_Packet(s):
  340. '''
  341. Taken from PCredz, solely to get Kerb parsing
  342. working until I have time to analyze Kerb pkts
  343. and figure out a simpler way
  344. Maybe use kerberos python lib
  345. '''
  346. d={}
  347. d['header_len']=ord(s[0]) & 0x0f
  348. d['data']=s[4*d['header_len']:]
  349. return d
  350.  
  351. def double_line_checker(full_load, count_str):
  352. '''
  353. Check if count_str shows up twice
  354. '''
  355. num = full_load.lower().count(count_str)
  356. if num > 1:
  357. lines = full_load.count('\r\n')
  358. if lines > 1:
  359. full_load = full_load.split('\r\n')[-2] # -1 is ''
  360. return full_load
  361.  
  362. def parse_ftp(full_load, dst_ip_port):
  363. '''
  364. Parse out FTP creds
  365. '''
  366. print_strs = []
  367.  
  368. # Sometimes FTP packets double up on the authentication lines
  369. # We just want the lastest one. Ex: "USER danmcinerney\r\nUSER danmcinerney\r\n"
  370. full_load = double_line_checker(full_load, 'USER')
  371.  
  372. # FTP and POP potentially use idential client > server auth pkts
  373. ftp_user = re.match(ftp_user_re, full_load)
  374. ftp_pass = re.match(ftp_pw_re, full_load)
  375.  
  376. if ftp_user:
  377. msg1 = 'FTP User: %s' % ftp_user.group(1).strip()
  378. print_strs.append(msg1)
  379. if dst_ip_port[-3:] != ':21':
  380. msg2 = 'Nonstandard FTP port, confirm the service that is running on it'
  381. print_strs.append(msg2)
  382.  
  383. elif ftp_pass:
  384. msg1 = 'FTP Pass: %s' % ftp_pass.group(1).strip()
  385. print_strs.append(msg1)
  386. if dst_ip_port[-3:] != ':21':
  387. msg2 = 'Nonstandard FTP port, confirm the service that is running on it'
  388. print_strs.append(msg2)
  389.  
  390. return print_strs
  391.  
  392. def mail_decode(src_ip_port, dst_ip_port, mail_creds):
  393. '''
  394. Decode base64 mail creds
  395. '''
  396. try:
  397. decoded = base64.b64decode(mail_creds).replace('\x00', ' ').decode('utf8')
  398. decoded = decoded.replace('\x00', ' ')
  399. except TypeError:
  400. decoded = None
  401. except UnicodeDecodeError as e:
  402. decoded = None
  403.  
  404. if decoded != None:
  405. msg = 'Decoded: %s' % decoded
  406. printer(src_ip_port, dst_ip_port, msg)
  407.  
  408. def mail_logins(full_load, src_ip_port, dst_ip_port, ack, seq):
  409. '''
  410. Catch IMAP, POP, and SMTP logins
  411. '''
  412. # Handle the first packet of mail authentication
  413. # if the creds aren't in the first packet, save it in mail_auths
  414.  
  415. # mail_auths = 192.168.0.2 : [1st ack, 2nd ack...]
  416. global mail_auths
  417. found = False
  418.  
  419. # Sometimes mail packets double up on the authentication lines
  420. # We just want the lastest one. Ex: "1 auth plain\r\n2 auth plain\r\n"
  421. full_load = double_line_checker(full_load, 'auth')
  422.  
  423. # Client to server 2nd+ pkt
  424. if src_ip_port in mail_auths:
  425. if seq in mail_auths[src_ip_port][-1]:
  426. stripped = full_load.strip('\r\n')
  427. try:
  428. decoded = base64.b64decode(stripped)
  429. msg = 'Mail authentication: %s' % decoded
  430. printer(src_ip_port, dst_ip_port, msg)
  431. except TypeError:
  432. pass
  433. mail_auths[src_ip_port].append(ack)
  434.  
  435. # Server responses to client
  436. # seq always = last ack of tcp stream
  437. elif dst_ip_port in mail_auths:
  438. if seq in mail_auths[dst_ip_port][-1]:
  439. # Look for any kind of auth failure or success
  440. a_s = 'Authentication successful'
  441. a_f = 'Authentication failed'
  442. # SMTP auth was successful
  443. if full_load.startswith('235') and 'auth' in full_load.lower():
  444. # Reversed the dst and src
  445. printer(dst_ip_port, src_ip_port, a_s)
  446. found = True
  447. try:
  448. del mail_auths[dst_ip_port]
  449. except KeyError:
  450. pass
  451. # SMTP failed
  452. elif full_load.startswith('535 '):
  453. # Reversed the dst and src
  454. printer(dst_ip_port, src_ip_port, a_f)
  455. found = True
  456. try:
  457. del mail_auths[dst_ip_port]
  458. except KeyError:
  459. pass
  460. # IMAP/POP/SMTP failed
  461. elif ' fail' in full_load.lower():
  462. # Reversed the dst and src
  463. printer(dst_ip_port, src_ip_port, a_f)
  464. found = True
  465. try:
  466. del mail_auths[dst_ip_port]
  467. except KeyError:
  468. pass
  469. # IMAP auth success
  470. elif ' OK [' in full_load:
  471. # Reversed the dst and src
  472. printer(dst_ip_port, src_ip_port, a_s)
  473. found = True
  474. try:
  475. del mail_auths[dst_ip_port]
  476. except KeyError:
  477. pass
  478.  
  479. # Pkt was not an auth pass/fail so its just a normal server ack
  480. # that it got the client's first auth pkt
  481. else:
  482. if len(mail_auths) > 100:
  483. mail_auths.popitem(last=False)
  484. mail_auths[dst_ip_port].append(ack)
  485.  
  486. # Client to server but it's a new TCP seq
  487. # This handles most POP/IMAP/SMTP logins but there's at least one edge case
  488. else:
  489. mail_auth_search = re.match(mail_auth_re, full_load, re.IGNORECASE)
  490. if mail_auth_search != None:
  491. auth_msg = full_load
  492. # IMAP uses the number at the beginning
  493. if mail_auth_search.group(1) != None:
  494. auth_msg = auth_msg.split()[1:]
  495. else:
  496. auth_msg = auth_msg.split()
  497. # Check if its a pkt like AUTH PLAIN dvcmQxIQ==
  498. # rather than just an AUTH PLAIN
  499. if len(auth_msg) > 2:
  500. mail_creds = ' '.join(auth_msg[2:])
  501. msg = 'Mail authentication: %s' % mail_creds
  502. printer(src_ip_port, dst_ip_port, msg)
  503.  
  504. mail_decode(src_ip_port, dst_ip_port, mail_creds)
  505. try:
  506. del mail_auths[src_ip_port]
  507. except KeyError:
  508. pass
  509. found = True
  510.  
  511. # Mail auth regex was found and src_ip_port is not in mail_auths
  512. # Pkt was just the initial auth cmd, next pkt from client will hold creds
  513. if len(mail_auths) > 100:
  514. mail_auths.popitem(last=False)
  515. mail_auths[src_ip_port] = [ack]
  516.  
  517. # At least 1 mail login style doesn't fit in the original regex:
  518. # 1 login "username" "password"
  519. # This also catches FTP authentication!
  520. # 230 Login successful.
  521. elif re.match(mail_auth_re1, full_load, re.IGNORECASE) != None:
  522.  
  523. # FTP authentication failures trigger this
  524. #if full_load.lower().startswith('530 login'):
  525. # return
  526.  
  527. auth_msg = full_load
  528. auth_msg = auth_msg.split()
  529. if 2 < len(auth_msg) < 5:
  530. mail_creds = ' '.join(auth_msg[2:])
  531. msg = 'Authentication: %s' % mail_creds
  532. printer(src_ip_port, dst_ip_port, msg)
  533. mail_decode(src_ip_port, dst_ip_port, mail_creds)
  534. found = True
  535.  
  536. if found == True:
  537. return True
  538.  
  539. def irc_logins(full_load, pkt):
  540. '''
  541. Find IRC logins
  542. '''
  543. user_search = re.match(irc_user_re, full_load)
  544. pass_search = re.match(irc_pw_re, full_load)
  545. pass_search2 = re.search(irc_pw_re2, full_load.lower())
  546. if user_search:
  547. msg = 'IRC nick: %s' % user_search.group(1)
  548. return msg
  549. if pass_search:
  550. msg = 'IRC pass: %s' % pass_search.group(1)
  551. return msg
  552. if pass_search2:
  553. msg = 'IRC pass: %s' % pass_search2.group(1)
  554. return msg
  555.  
  556. def other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt, verbose):
  557. '''
  558. Pull out pertinent info from the parsed HTTP packet data
  559. '''
  560. user_passwd = None
  561. http_url_req = None
  562. method = None
  563. http_methods = ['GET ', 'POST ', 'CONNECT ', 'TRACE ', 'TRACK ', 'PUT ', 'DELETE ', 'HEAD ']
  564. http_line, header_lines, body = parse_http_load(full_load, http_methods)
  565. headers = headers_to_dict(header_lines)
  566. if 'host' in headers:
  567. host = headers['host']
  568. else:
  569. host = ''
  570.  
  571. if http_line != None:
  572. method, path = parse_http_line(http_line, http_methods)
  573. http_url_req = get_http_url(method, host, path, headers)
  574. if http_url_req != None:
  575. if verbose == False:
  576. if len(http_url_req) > 98:
  577. http_url_req = http_url_req[:99] + '...'
  578. printer(src_ip_port, None, http_url_req)
  579.  
  580. # Print search terms
  581. searched = get_http_searches(http_url_req, body, host)
  582. if searched:
  583. printer(src_ip_port, dst_ip_port, searched)
  584.  
  585. # Print user/pwds
  586. if body != '':
  587. user_passwd = get_login_pass(body)
  588. if user_passwd != None:
  589. try:
  590. http_user = user_passwd[0].decode('utf8')
  591. http_pass = user_passwd[1].decode('utf8')
  592. # Set a limit on how long they can be prevent false+
  593. if len(http_user) > 75 or len(http_pass) > 75:
  594. return
  595. user_msg = 'HTTP username: %s' % http_user
  596. printer(src_ip_port, dst_ip_port, user_msg)
  597. pass_msg = 'HTTP password: %s' % http_pass
  598. printer(src_ip_port, dst_ip_port, pass_msg)
  599. except UnicodeDecodeError:
  600. pass
  601.  
  602. # Print POST loads
  603. # ocsp is a common SSL post load that's never interesting
  604. if method == 'POST' and 'ocsp.' not in host:
  605. try:
  606. if verbose == False and len(body) > 99:
  607. # If it can't decode to utf8 we're probably not interested in it
  608. msg = 'POST load: %s...' % body[:99].encode('utf8')
  609. else:
  610. msg = 'POST load: %s' % body.encode('utf8')
  611. printer(src_ip_port, None, msg)
  612. except UnicodeDecodeError:
  613. pass
  614.  
  615. # Kerberos over TCP
  616. decoded = Decode_Ip_Packet(str(pkt)[14:])
  617. kerb_hash = ParseMSKerbv5TCP(decoded['data'][20:])
  618. if kerb_hash:
  619. printer(src_ip_port, dst_ip_port, kerb_hash)
  620.  
  621. # Non-NETNTLM NTLM hashes (MSSQL, DCE-RPC,SMBv1/2,LDAP, MSSQL)
  622. NTLMSSP2 = re.search(NTLMSSP2_re, full_load, re.DOTALL)
  623. NTLMSSP3 = re.search(NTLMSSP3_re, full_load, re.DOTALL)
  624. if NTLMSSP2:
  625. parse_ntlm_chal(NTLMSSP2.group(), ack)
  626. if NTLMSSP3:
  627. ntlm_resp_found = parse_ntlm_resp(NTLMSSP3.group(), seq)
  628. if ntlm_resp_found != None:
  629. printer(src_ip_port, dst_ip_port, ntlm_resp_found)
  630.  
  631. # Look for authentication headers
  632. if len(headers) == 0:
  633. authenticate_header = None
  634. authorization_header = None
  635. for header in headers:
  636. authenticate_header = re.match(authenticate_re, header)
  637. authorization_header = re.match(authorization_re, header)
  638. if authenticate_header or authorization_header:
  639. break
  640.  
  641. if authorization_header or authenticate_header:
  642. # NETNTLM
  643. netntlm_found = parse_netntlm(authenticate_header, authorization_header, headers, ack, seq)
  644. if netntlm_found != None:
  645. printer(src_ip_port, dst_ip_port, netntlm_found)
  646.  
  647. # Basic Auth
  648. parse_basic_auth(src_ip_port, dst_ip_port, headers, authorization_header)
  649.  
  650. def get_http_searches(http_url_req, body, host):
  651. '''
  652. Find search terms from URLs. Prone to false positives but rather err on that side than false negatives
  653. search, query, ?s, &q, ?q, search?p, searchTerm, keywords, command
  654. '''
  655. false_pos = ['i.stack.imgur.com']
  656.  
  657. searched = None
  658. if http_url_req != None:
  659. searched = re.search(http_search_re, http_url_req, re.IGNORECASE)
  660. if searched == None:
  661. searched = re.search(http_search_re, body, re.IGNORECASE)
  662.  
  663. if searched != None and host not in false_pos:
  664. searched = searched.group(3)
  665. # Eliminate some false+
  666. try:
  667. # if it doesn't decode to utf8 it's probably not user input
  668. searched = searched.decode('utf8')
  669. except UnicodeDecodeError:
  670. return
  671. # some add sites trigger this function with single digits
  672. if searched in [str(num) for num in range(0,10)]:
  673. return
  674. # nobody's making >100 character searches
  675. if len(searched) > 100:
  676. return
  677. msg = 'Searched %s: %s' % (host, unquote(searched.encode('utf8')).replace('+', ' '))
  678. return msg
  679.  
  680. def parse_basic_auth(src_ip_port, dst_ip_port, headers, authorization_header):
  681. '''
  682. Parse basic authentication over HTTP
  683. '''
  684. if authorization_header:
  685. # authorization_header sometimes is triggered by failed ftp
  686. try:
  687. header_val = headers[authorization_header.group()]
  688. except KeyError:
  689. return
  690. b64_auth_re = re.match('basic (.+)', header_val, re.IGNORECASE)
  691. if b64_auth_re != None:
  692. basic_auth_b64 = b64_auth_re.group(1)
  693. try:
  694. basic_auth_creds = base64.decodestring(basic_auth_b64)
  695. except Exception:
  696. return
  697. msg = 'Basic Authentication: %s' % basic_auth_creds
  698. printer(src_ip_port, dst_ip_port, msg)
  699.  
  700. def parse_netntlm(authenticate_header, authorization_header, headers, ack, seq):
  701. '''
  702. Parse NTLM hashes out
  703. '''
  704. # Type 2 challenge from server
  705. if authenticate_header != None:
  706. chal_header = authenticate_header.group()
  707. parse_netntlm_chal(headers, chal_header, ack)
  708.  
  709. # Type 3 response from client
  710. elif authorization_header != None:
  711. resp_header = authorization_header.group()
  712. msg = parse_netntlm_resp_msg(headers, resp_header, seq)
  713. if msg != None:
  714. return msg
  715.  
  716. def parse_snmp(src_ip_port, dst_ip_port, snmp_layer):
  717. '''
  718. Parse out the SNMP version and community string
  719. '''
  720. if type(snmp_layer.community.val) == str:
  721. ver = snmp_layer.version.val
  722. msg = 'SNMPv%d community string: %s' % (ver, snmp_layer.community.val)
  723. printer(src_ip_port, dst_ip_port, msg)
  724. return True
  725.  
  726. def get_http_url(method, host, path, headers):
  727. '''
  728. Get the HTTP method + URL from requests
  729. '''
  730. if method != None and path != None:
  731.  
  732. # Make sure the path doesn't repeat the host header
  733. if host != '' and not re.match('(http(s)?://)?'+host, path):
  734. http_url_req = method + ' ' + host + path
  735. else:
  736. http_url_req = method + ' ' + path
  737.  
  738. http_url_req = url_filter(http_url_req)
  739.  
  740. return http_url_req
  741.  
  742. def headers_to_dict(header_lines):
  743. '''
  744. Convert the list of header lines into a dictionary
  745. '''
  746. headers = {}
  747. for line in header_lines:
  748. lineList=line.split(': ', 1)
  749. key=lineList[0].lower()
  750. if len(lineList)>1:
  751. headers[key]=lineList[1]
  752. else:
  753. headers[key]=""
  754. return headers
  755.  
  756. def parse_http_line(http_line, http_methods):
  757. '''
  758. Parse the header with the HTTP method in it
  759. '''
  760. http_line_split = http_line.split()
  761. method = ''
  762. path = ''
  763.  
  764. # Accounts for pcap files that might start with a fragment
  765. # so the first line might be just text data
  766. if len(http_line_split) > 1:
  767. method = http_line_split[0]
  768. path = http_line_split[1]
  769.  
  770. # This check exists because responses are much different than requests e.g.:
  771. # HTTP/1.1 407 Proxy Authentication Required ( Access is denied. )
  772. # Add a space to method because there's a space in http_methods items
  773. # to avoid false+
  774. if method+' ' not in http_methods:
  775. method = None
  776. path = None
  777.  
  778. return method, path
  779.  
  780. def parse_http_load(full_load, http_methods):
  781. '''
  782. Split the raw load into list of headers and body string
  783. '''
  784. try:
  785. headers, body = full_load.split("\r\n\r\n", 1)
  786. except ValueError:
  787. headers = full_load
  788. body = ''
  789. header_lines = headers.split("\r\n")
  790.  
  791. # Pkts may just contain hex data and no headers in which case we'll
  792. # still want to parse them for usernames and password
  793. http_line = get_http_line(header_lines, http_methods)
  794. if not http_line:
  795. headers = ''
  796. body = full_load
  797.  
  798. header_lines = [line for line in header_lines if line != http_line]
  799.  
  800. return http_line, header_lines, body
  801.  
  802. def get_http_line(header_lines, http_methods):
  803. '''
  804. Get the header with the http command
  805. '''
  806. for header in header_lines:
  807. for method in http_methods:
  808. # / is the only char I can think of that's in every http_line
  809. # Shortest valid: "GET /", add check for "/"?
  810. if header.startswith(method):
  811. http_line = header
  812. return http_line
  813.  
  814. def parse_netntlm_chal(headers, chal_header, ack):
  815. '''
  816. Parse the netntlm server challenge
  817. https://code.google.com/p/python-ntlm/source/browse/trunk/python26/ntlm/ntlm.py
  818. '''
  819. try:
  820. header_val2 = headers[chal_header]
  821. except KeyError:
  822. return
  823. header_val2 = header_val2.split(' ', 1)
  824. # The header value can either start with NTLM or Negotiate
  825. if header_val2[0] == 'NTLM' or header_val2[0] == 'Negotiate':
  826. try:
  827. msg2 = header_val2[1]
  828. except IndexError:
  829. return
  830. msg2 = base64.decodestring(msg2)
  831. parse_ntlm_chal(msg2, ack)
  832.  
  833. def parse_ntlm_chal(msg2, ack):
  834. '''
  835. Parse server challenge
  836. '''
  837. global challenge_acks
  838.  
  839. Signature = msg2[0:8]
  840. try:
  841. msg_type = struct.unpack("<I",msg2[8:12])[0]
  842. except Exception:
  843. return
  844. assert(msg_type==2)
  845. ServerChallenge = msg2[24:32].encode('hex')
  846.  
  847. # Keep the dict of ack:challenge to less than 50 chals
  848. if len(challenge_acks) > 50:
  849. challenge_acks.popitem(last=False)
  850. challenge_acks[ack] = ServerChallenge
  851.  
  852. def parse_netntlm_resp_msg(headers, resp_header, seq):
  853. '''
  854. Parse the client response to the challenge
  855. '''
  856. try:
  857. header_val3 = headers[resp_header]
  858. except KeyError:
  859. return
  860. header_val3 = header_val3.split(' ', 1)
  861.  
  862. # The header value can either start with NTLM or Negotiate
  863. if header_val3[0] == 'NTLM' or header_val3[0] == 'Negotiate':
  864. try:
  865. msg3 = base64.decodestring(header_val3[1])
  866. except binascii.Error:
  867. return
  868. return parse_ntlm_resp(msg3, seq)
  869.  
  870. def parse_ntlm_resp(msg3, seq):
  871. '''
  872. Parse the 3rd msg in NTLM handshake
  873. Thanks to psychomario
  874. '''
  875.  
  876. if seq in challenge_acks:
  877. challenge = challenge_acks[seq]
  878. else:
  879. challenge = 'CHALLENGE NOT FOUND'
  880.  
  881. if len(msg3) > 43:
  882. # Thx to psychomario for below
  883. lmlen, lmmax, lmoff, ntlen, ntmax, ntoff, domlen, dommax, domoff, userlen, usermax, useroff = struct.unpack("12xhhihhihhihhi", msg3[:44])
  884. lmhash = binascii.b2a_hex(msg3[lmoff:lmoff+lmlen])
  885. nthash = binascii.b2a_hex(msg3[ntoff:ntoff+ntlen])
  886. domain = msg3[domoff:domoff+domlen].replace("\0", "")
  887. user = msg3[useroff:useroff+userlen].replace("\0", "")
  888. # Original check by psychomario, might be incorrect?
  889. #if lmhash != "0"*48: #NTLMv1
  890. if ntlen == 24: #NTLMv1
  891. msg = '%s %s' % ('NETNTLMv1:', user+"::"+domain+":"+lmhash+":"+nthash+":"+challenge)
  892. return msg
  893. elif ntlen > 60: #NTLMv2
  894. msg = '%s %s' % ('NETNTLMv2:', user+"::"+domain+":"+challenge+":"+nthash[:32]+":"+nthash[32:])
  895. return msg
  896.  
  897. def url_filter(http_url_req):
  898. '''
  899. Filter out the common but uninteresting URLs
  900. '''
  901. if http_url_req:
  902. d = ['.jpg', '.jpeg', '.gif', '.png', '.css', '.ico', '.js', '.svg', '.woff']
  903. if any(http_url_req.endswith(i) for i in d):
  904. return
  905.  
  906. return http_url_req
  907.  
  908. def get_login_pass(body):
  909. '''
  910. Regex out logins and passwords from a string
  911. '''
  912. user = None
  913. passwd = None
  914.  
  915. # Taken mainly from Pcredz by Laurent Gaffie
  916. userfields = ['log','login', 'wpname', 'ahd_username', 'unickname', 'nickname', 'user', 'user_name',
  917. 'alias', 'pseudo', 'email', 'username', '_username', 'userid', 'form_loginname', 'loginname',
  918. 'login_id', 'loginid', 'session_key', 'sessionkey', 'pop_login', 'uid', 'id', 'user_id', 'screename',
  919. 'uname', 'ulogin', 'acctname', 'account', 'member', 'mailaddress', 'membername', 'login_username',
  920. 'login_email', 'loginusername', 'loginemail', 'uin', 'sign-in', 'usuario']
  921. passfields = ['ahd_password', 'pass', 'password', '_password', 'passwd', 'session_password', 'sessionpassword',
  922. 'login_password', 'loginpassword', 'form_pw', 'pw', 'userpassword', 'pwd', 'upassword', 'login_password'
  923. 'passwort', 'passwrd', 'wppassword', 'upasswd','senha','contrasena']
  924.  
  925. for login in userfields:
  926. login_re = re.search('(%s=[^&]+)' % login, body, re.IGNORECASE)
  927. if login_re:
  928. user = login_re.group()
  929. for passfield in passfields:
  930. pass_re = re.search('(%s=[^&]+)' % passfield, body, re.IGNORECASE)
  931. if pass_re:
  932. passwd = pass_re.group()
  933.  
  934. if user and passwd:
  935. return (user, passwd)
  936.  
  937. def printer(src_ip_port, dst_ip_port, msg):
  938. if dst_ip_port != None:
  939.  
  940. print_str = '[%s > %s] %s%s%s' % (src_ip_port, dst_ip_port, T, msg, W)
  941. # All credentials will have dst_ip_port, URLs will not
  942.  
  943. # Prevent identical outputs unless it's an HTTP search or POST load
  944. skip = ['Searched ', 'POST load:']
  945. for s in skip:
  946. if s not in msg:
  947. if os.path.isfile('credentials.txt'):
  948. with open('credentials.txt', 'r') as log:
  949. contents = log.read()
  950. if msg in contents:
  951. return
  952.  
  953. # Escape colors like whatweb has
  954. ansi_escape = re.compile(r'\x1b[^m]*m')
  955. print_str = ansi_escape.sub('', print_str)
  956.  
  957. # Log the creds
  958. logging.info(print_str)
  959. else:
  960. print_str = '[%s] %s' % (src_ip_port.split(':')[0], msg)
  961. if 'tor' not in msg:
  962. print print_str
  963.  
  964. def main(args):
  965. ##################### DEBUG ##########################
  966. ## Hit Ctrl-C while program is running and you can see
  967. ## whatever variable you want within the IPython cli
  968. ## Don't forget to uncomment IPython in imports
  969. #def signal_handler(signal, frame):
  970. # embed()
  971. ## sniff(iface=conf.iface, prn=pkt_parser, store=0)
  972. # sys.exit()
  973. #signal.signal(signal.SIGINT, signal_handler)
  974. ######################################################
  975.  
  976. # Read packets from either pcap or interface
  977. if args.pcap:
  978. try:
  979. for pkt in PcapReader(args.pcap):
  980. pkt_parser(pkt)
  981. except IOError:
  982. exit('[-] Could not open %s' % args.pcap)
  983.  
  984. else:
  985. # Check for root
  986. if geteuid():
  987. exit('[-] Please run as root')
  988.  
  989. #Find the active interface
  990. if args.interface:
  991. conf.iface = args.interface
  992. else:
  993. conf.iface = iface_finder()
  994. print '[*] Using interface:', conf.iface
  995.  
  996. if args.filterip:
  997. sniff(iface=conf.iface, prn=pkt_parser, filter="not host %s" % args.filterip, store=0)
  998. else:
  999. sniff(iface=conf.iface, prn=pkt_parser, store=0)
  1000.  
  1001.  
  1002. if __name__ == "__main__":
  1003. main(parse_args())
Add Comment
Please, Sign In to add comment