Skip to content

Instantly share code, notes, and snippets.

@Mayfly277
Created March 18, 2025 20:40
Show Gist options
  • Select an option

  • Save Mayfly277/f4d10d30dfa7e0da155f34988b2441bc to your computer and use it in GitHub Desktop.

Select an option

Save Mayfly277/f4d10d30dfa7e0da155f34988b2441bc to your computer and use it in GitHub Desktop.
Extraction of the Global Address List (GAL) on Exchange
# Extraction of the Global Address List (GAL) on Exchange >=2013 servers via Outlook Web Access (OWA)
# By Pigeonburger, June 2021
# https://github.com/pigeonburger
import requests, json, argparse
# argparser
parser = argparse.ArgumentParser(description="Extract the Global Address List (GAL) on Exchange 2013 servers via Outlook Web Access (OWA)")
parser.add_argument("-i", "--host", dest="hostname",
help="Hostname for the Exchange Server", metavar="HOSTNAME", type=str, required=True)
parser.add_argument("-u", "--username", dest="username",
help="A username to log in", metavar="USERNAME", type=str, required=True)
parser.add_argument("-k", "--verify", dest="verify", action='store_false', default=True)
parser.add_argument("-p", "--password", dest="password",
help="A password to log in", metavar="PASSWORD", type=str, required=True)
parser.add_argument("-o", "--output-file", dest="output",
help="Specify file to output emails to (default is global_address_list.txt)", metavar="OUTPUT FILE", type=str, default="global_address_list.txt")
args = parser.parse_args()
url = args.hostname
USERNAME = args.username
PASSWORD = args.password
OUTPUT = args.output
verify= args.verify
if not verify:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Start the session
s = requests.Session()
print("Connecting to %s/owa" % url)
# Get OWA landing page
# Add https:// scheme if not already added in the --host arg
try:
s.get(url+"/owa", verify=verify)
URL = url
except requests.exceptions.MissingSchema:
s.get("https://"+url+"/owa", verify=verify)
URL = "https://"+url
# Other URLs we need later
AUTH_URL = URL+"/owa/auth.owa"
PEOPLE_FILTERS_URL = URL + "/owa/service.svc?action=GetPeopleFilters"
FIND_PEOPLE_URL = URL + "/owa/service.svc?action=FindPeople"
# Attempt a login to OWA
login_data={"username":USERNAME, "password":PASSWORD, 'destination': URL, 'flags': '4', 'forcedownlevel': '0'}
r = s.post(AUTH_URL, data=login_data, headers={'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0"}, verify=verify)
# The Canary is a unique ID thing provided upon a successful login that's also required in the header for the next few requests to be successful.
# Even upon an incorrect login, OWA still gives a 200 status, so we can also check if the login was successful by seeing if this cookie was set or not.
try:
session_canary = s.cookies['X-OWA-CANARY']
except:
exit("\nInvalid Login Details. Login Failed.")
print("\nLogin Successful!\nCanary key:", session_canary)
# Returns an object containing the IDs of all accessible address lists, so we can specify one in the FindPeople request
r = s.post(PEOPLE_FILTERS_URL, headers={'Content-type': 'application/json', 'X-OWA-CANARY': session_canary, 'Action': 'GetPeopleFilters'}, data={}, verify=verify).json()
# Find the Global Address List id
for i in r:
if i['DisplayName'] == "Default Global Address List":
AddressListId = i['FolderId']['Id']
print("Global List Address ID:", AddressListId)
break
# Set to None to return all emails in the list (this is the search term for the FindPeople request)
query = None
# Set the max results for the FindPeople request.
max_results = 99999
# POST data for the FindPeople request
peopledata = {
"__type": "FindPeopleJsonRequest:#Exchange",
"Header": {
"__type": "JsonRequestHeaders:#Exchange",
"RequestServerVersion": "Exchange2013",
"TimeZoneContext": {
"__type": "TimeZoneContext:#Exchange",
"TimeZoneDefinition": {
"__type": "TimeZoneDefinitionType:#Exchange",
"Id": "AUS Eastern Standard Time"
}
}
},
"Body": {
"__type": "FindPeopleRequest:#Exchange",
"IndexedPageItemView": {
"__type": "IndexedPageView:#Exchange",
"BasePoint": "Beginning",
"Offset": 0,
"MaxEntriesReturned": max_results
},
"QueryString": query,
"ParentFolderId": {
"__type": "TargetFolderId:#Exchange",
"BaseFolderId": {
"__type": "AddressListId:#Exchange",
"Id": AddressListId
}
},
"PersonaShape": {
"__type": "PersonaResponseShape:#Exchange",
"BaseShape": "Default"
},
"ShouldResolveOneOffEmailAddress": False
}
}
# Make da request.
r = s.post(FIND_PEOPLE_URL, headers={'Content-type': 'application/json', 'X-OWA-CANARY': session_canary, 'Action': 'FindPeople'}, data=json.dumps(peopledata), verify=verify).json()
# Parse out the emails, print them and append them to a file.
userlist = r['Body']['ResultSet']
with open(OUTPUT, 'a+') as outputfile:
for user in userlist:
email = user['EmailAddresses'][0]['EmailAddress']
outputfile.write(email+"\n")
print(email)
print("\nFetched %s emails" % str(len(userlist)))
print("Emails written to", OUTPUT)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment