Skip to content

Commit e546db8

Browse files
committed
Find-PrivilegedApplications and more
1 parent b94cfc0 commit e546db8

File tree

3 files changed

+154
-33
lines changed

3 files changed

+154
-33
lines changed
35.4 KB
Loading

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Please refer to the [Wiki](https://github.com/mlcsec/Graphpython/wiki) for the f
147147
* **Get-ConditionalAccessPolicy** - Get conditional access policy properties
148148
* **Get-Application** - Get Enterprise Application details for app (NOT object) ID
149149
* **Get-ServicePrincipal** - Get Service Principal details
150-
* **Get-ServicePrincipalAppRoleAssignments** - Get Service Principal app role assignments
150+
* **Get-ServicePrincipalAppRoleAssignments** - Get Service Principal app role assignments (shows available admin consent permissions that are already granted)
151151
* **Get-PersonalContacts** - Get contacts of the current user
152152
* **Get-CrossTenantAccessPolicy** - Get cross tenant access policy properties
153153
* **Get-PartnerCrossTenantAccessPolicy** - Get partner cross tenant access policy
@@ -397,7 +397,7 @@ Assign a privileged role via template ID to a user or group and define permissio
397397

398398
### Spoof-OWAEmailMessage
399399

400-
Send emails using a compromised user's Outlook mail box. The --id parameter can be used to send emails as other uses within the organistion.
400+
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.
401401

402402
> Mail.Send permission REQUIRED for --id spoofing
403403
@@ -601,6 +601,7 @@ Graph permission IDs applied to objects can be easily located with detailed expl
601601
- [x] `Get-DeviceConfigurationPolicies` - tidy up the templateReference and assignmentTarget output
602602
- [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
603603
- New:
604+
- [x] `Find-PrivilegedApplications` - identify enterprise applications which have
604605
- [x] `Grant-AppAdminConsent` - grant admin consent for requested/applied admin app permissions (if `Add-ApplicationPermission` fails)
605606
- [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)
606607
- [ ] `Deploy-MaliciousWin32App` - use IntuneWinAppUtil.exe to package the EXE/MSI and deploy to devices

graphpython.py

Lines changed: 151 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,16 @@ def list_commands():
7777
["Get-UserAppRoleAssignments", "Get user app role assignments for current user (default) or target user (--id)"],
7878
["Get-ConditionalAccessPolicy", "Get conditional access policy properties"],
7979
["Get-Application", "Get Enterprise Application details for app (NOT object) ID (--id)"],
80-
["Get-ServicePrincipal", "Get Service Principal details (--id)"],
81-
["Get-ServicePrincipalAppRoleAssignments", "Get Service Principal app role assignments (--id)"],
80+
["Get-AppServicePrincipal", "Get details of the application's service principal from the app ID (--id)"],
81+
["Get-ServicePrincipal", "Get all or specific Service Principal details (--id)"],
82+
["Get-ServicePrincipalAppRoleAssignments", "Get Service Principal app role assignments (shows available admin consent permissions that are already granted)"],
8283
["Get-PersonalContacts", "Get contacts of the current user"],
8384
["Get-CrossTenantAccessPolicy", "Get cross tenant access policy properties"],
8485
["Get-PartnerCrossTenantAccessPolicy", "Get partner cross tenant access policy"],
8586
["Get-UserChatMessages", "Get ALL messages from all chats for target user (Chat.Read.All)"],
8687
["Get-AdministrativeUnitMember", "Get members of administrative unit"],
8788
["Get-OneDriveFiles", "Get all accessible OneDrive files for current user (default) or target user (--id)"],
88-
["Get-UserPermissionGrants", "Get permissions grants of current user (default) or target user (--id)"],
89+
["Get-UserPermissionGrants", "Get permission grants of current user (default) or target user (--id)"],
8990
["Get-oauth2PermissionGrants", "Get oauth2 permission grants for current user (default) or target user (--id)"],
9091
["Get-Messages", "Get all messages in signed-in user's mailbox (default) or target user (--id)"],
9192
["Get-TemporaryAccessPassword", "Get TAP details for current user (default) or target user (--id)"],
@@ -118,6 +119,7 @@ def list_commands():
118119
["Invoke-CustomQuery", "Custom GET query to target Graph API endpoint"],
119120
["Invoke-Search", "Search for string within entity type (driveItem, message, chatMessage, site, event)"],
120121
["Find-PrivilegedRoleUsers", "Find users with privileged roles assigned"],
122+
["Find-PrivilegedApplications", "Find privileged apps (via their service principal) with granted admin consent API permissions"],
121123
["Find-UpdatableGroups", "Find groups which can be updated by the current user"],
122124
["Find-SecurityGroups", "Find security groups and group members"],
123125
["Find-DynamicGroups", "Find groups with dynamic membership rules"],
@@ -651,9 +653,9 @@ def main():
651653
"get-manageddevices", "get-userdevices", "get-caps", "get-devicecategories", "get-devicecompliancepolicies",
652654
"get-devicecompliancesummary", "get-deviceconfigurations", "get-deviceconfigurationpolicies", "get-deviceconfigurationpolicysettings",
653655
"get-deviceenrollmentconfigurations", "get-devicegrouppolicyconfigurations","update-userproperties",
654-
"get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "get-scriptcontent",
656+
"get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "get-scriptcontent", "find-privilegedapplications",
655657
"get-roledefinitions", "get-roleassignments", "display-avpolicyrules", "display-asrpolicyrules", "display-diskencryptionpolicyrules",
656-
"display-firewallrulepolicyrules", "display-lapsaccountprotectionpolicyrules", "display-usergroupaccountprotectionpolicyrules",
658+
"display-firewallrulepolicyrules", "display-lapsaccountprotectionpolicyrules", "display-usergroupaccountprotectionpolicyrules", "get-appserviceprincipal",
657659
"display-edrpolicyrules","add-exclusiongrouptopolicy", "deploy-maliciousscript", "reboot-device", "shutdown-device", "lock-device", "backdoor-script",
658660
"add-applicationpermission", "new-signedjwt", "add-applicationcertificate", "get-application", "locate-permissionid", "get-serviceprincipal", "grant-appadminconsent"
659661
]
@@ -735,8 +737,8 @@ def main():
735737
"get-caps", "get-devicecategories", "display-devicecompliancepolicies", "get-devicecompliancesummary",
736738
"get-deviceconfigurations", "get-deviceconfigurationpolicies", "get-deviceconfigurationpolicysettings",
737739
"get-deviceenrollmentconfigurations", "get-devicegrouppolicyconfigurations", "grant-appadminconsent",
738-
"get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "update-userproperties",
739-
"get-scriptcontent", "get-roledefinitions", "get-roleassignments", "display-avpolicyrules",
740+
"get-devicegrouppolicydefinition", "dump-devicemanagementscripts", "update-userproperties", "find-privilegedapplications",
741+
"get-scriptcontent", "get-roledefinitions", "get-roleassignments", "display-avpolicyrules","get-appserviceprincipal",
740742
"display-asrpolicyrules", "display-diskencryptionpolicyrules", "display-firewallrulepolicyrules", "backdoor-script",
741743
"display-edrpolicyrules", "display-lapsaccountprotectionpolicyrules", "display-usergroupaccountprotectionpolicyrules",
742744
"add-exclusiongrouptopolicy","deploy-maliciousscript", "reboot-device", "add-applicationpermission", "new-signedjwt",
@@ -2537,36 +2539,54 @@ def base64url_encode(data):
25372539
print_red(response.text)
25382540
print("=" * 80)
25392541

2542+
# get-appserviceprincipal
2543+
elif args.command and args.command.lower() == "get-appserviceprincipal":
2544+
if not args.id:
2545+
print_red("[-] Error: --id <app id> argument is required for Get-AppServicePrincipal command")
2546+
return
2547+
2548+
print_yellow("\n[*] Get-AppServicePrincipal")
2549+
print("=" * 80)
2550+
api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId+eq+'{args.id}'"
2551+
2552+
user_agent = get_user_agent(args)
2553+
headers = {
2554+
'Authorization': f'Bearer {access_token}',
2555+
'User-Agent': user_agent
2556+
}
2557+
2558+
graph_api_get(access_token, api_url, args)
2559+
print("=" * 80)
2560+
25402561
# get-serviceprincipal
25412562
elif args.command and args.command.lower() == "get-serviceprincipal":
2542-
if not args.id:
2543-
print_red("[-] Error: --id <id> argument is required for Get-ServicePrincipal command")
2544-
return
2563+
if not args.id:
2564+
print_red("[-] Error: --id <id> argument is required for Get-ServicePrincipal command")
2565+
return
25452566

2546-
print_yellow("\n[*] Get-ServicePrincipal")
2547-
print("=" * 80)
2548-
api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{args.id}"
2549-
if args.select:
2550-
api_url += "?$select=" + args.select
2551-
2552-
user_agent = get_user_agent(args)
2553-
headers = {
2554-
'Authorization': f'Bearer {access_token}',
2555-
'User-Agent': user_agent
2556-
}
2567+
print_yellow("\n[*] Get-ServicePrincipal")
2568+
print("=" * 80)
2569+
api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{args.id}"
2570+
if args.select:
2571+
api_url += "?$select=" + args.select
25572572

2558-
response = requests.get(api_url, headers=headers)
2559-
if response.status_code == 200:
2560-
response_json = response.json()
2573+
user_agent = get_user_agent(args)
2574+
headers = {
2575+
'Authorization': f'Bearer {access_token}',
2576+
'User-Agent': user_agent
2577+
}
25612578

2562-
for key, value in response_json.items():
2563-
if key != "@odata.context":
2564-
print(f"{key}: {value}")
2579+
response = requests.get(api_url, headers=headers)
2580+
if response.status_code == 200:
2581+
response_json = response.json()
2582+
for key, value in response_json.items():
2583+
if key != "@odata.context":
2584+
print(f"{key}: {value}")
25652585

2566-
else:
2567-
print_red(f"[-] Failed to retrieve Service Principal details: {response.status_code}")
2568-
print_red(response.text)
2569-
print("=" * 80)
2586+
else:
2587+
print_red(f"[-] Failed to retrieve Service Principal details: {response.status_code}")
2588+
print_red(response.text)
2589+
print("=" * 80)
25702590

25712591
# get-serviceprincipalapproleassignments
25722592
elif args.command and args.command.lower() == "get-serviceprincipalapproleassignments":
@@ -3287,6 +3307,106 @@ def base64url_encode(data):
32873307
print_red(f"[-] Role: {role['displayName']}")
32883308
print("=" * 80)
32893309

3310+
# find-privilegedapplications
3311+
elif args.command and args.command.lower() == "find-privilegedapplications":
3312+
print_yellow("\n[*] Find-PrivilegedApplications")
3313+
print("=" * 80)
3314+
api_url = "https://graph.microsoft.com/v1.0/applications?$select=appId"
3315+
3316+
user_agent = get_user_agent(args)
3317+
headers = {
3318+
'Authorization': 'Bearer ' + access_token,
3319+
'Accept': 'application/json',
3320+
'User-Agent': user_agent
3321+
}
3322+
3323+
response = requests.get(api_url, headers=headers)
3324+
if response.status_code == 200:
3325+
applications = response.json()
3326+
app_ids = [app['appId'] for app in applications.get('value', [])]
3327+
else:
3328+
print_red(f"[-] Error: API request failed with status code {response.status_code}")
3329+
app_ids = []
3330+
3331+
service_principals = []
3332+
for app_id in app_ids:
3333+
sp_api_url = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId eq '{app_id}'&$select=id,appDisplayName"
3334+
sp_response = requests.get(sp_api_url, headers=headers)
3335+
3336+
if sp_response.status_code == 200:
3337+
sp_data = sp_response.json()
3338+
for sp in sp_data.get('value', []):
3339+
service_principals.append({
3340+
'id': sp['id'],
3341+
'appDisplayName': sp['appDisplayName']
3342+
})
3343+
else:
3344+
print_red(f"[-] Error: Service Principal API request failed for appId {app_id} with status code {sp_response.status_code}")
3345+
3346+
app_role_assignments = {}
3347+
for sp in service_principals:
3348+
app_role_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{sp['id']}/appRoleAssignments"
3349+
app_role_response = requests.get(app_role_url, headers=headers)
3350+
3351+
if app_role_response.status_code == 200:
3352+
assignments = app_role_response.json()
3353+
app_role_assignments[sp['id']] = {
3354+
'appDisplayName': sp['appDisplayName'],
3355+
'assignments': assignments.get('value', [])
3356+
}
3357+
else:
3358+
print_red(f"[-] Error: App Role Assignments API request failed for Service Principal ID {sp['id']}: {app_role_response.status_code}")
3359+
print_red(app_role_response.text)
3360+
3361+
def parse_roleids(content):
3362+
soup = BeautifulSoup(content, 'html.parser')
3363+
permissions = {}
3364+
for h3 in soup.find_all('h3'):
3365+
permission_name = h3.get_text()
3366+
table = h3.find_next('table')
3367+
rows = table.find_all('tr')
3368+
application_id = rows[1].find_all('td')[1].get_text()
3369+
delegated_id = rows[1].find_all('td')[2].get_text()
3370+
application_consent = rows[4].find_all('td')[1].get_text() if len(rows) > 4 else "Unknown"
3371+
delegated_consent = rows[4].find_all('td')[2].get_text() if len(rows) > 4 else "Unknown"
3372+
permissions[application_id] = ('Application', permission_name, application_consent)
3373+
permissions[delegated_id] = ('Delegated', permission_name, delegated_consent)
3374+
return permissions
3375+
3376+
script_dir = os.path.dirname(os.path.abspath(__file__))
3377+
file_path = os.path.join(script_dir, '.github', 'graphpermissions.txt')
3378+
try:
3379+
with open(file_path, 'r') as file:
3380+
content = file.read()
3381+
except FileNotFoundError:
3382+
print_red(f"\n[-] The file {file_path} does not exist.")
3383+
sys.exit(1)
3384+
except Exception as e:
3385+
print_red(f"\n[-] An error occurred: {e}")
3386+
sys.exit(1)
3387+
3388+
permissions = parse_roleids(content)
3389+
3390+
# results
3391+
for sp_id, data in app_role_assignments.items():
3392+
print(f"\nApplication: {data['appDisplayName']}")
3393+
if data['assignments']:
3394+
for assignment in data['assignments']:
3395+
app_role_id = assignment.get('appRoleId', 'N/A')
3396+
print_green(f"[+] App Role ID: {app_role_id}")
3397+
if app_role_id in permissions:
3398+
role_type, role_name, consent_required = permissions[app_role_id]
3399+
print_green(f"[+] Role Name: {role_name}")
3400+
#print_green(f"[+] Role Type: {role_type}") # can only be application for appRoleAssignments, delegated role types use oauth2PermissionGrants
3401+
#print_green(f"[+] Admin Consent Required: {consent_required}") # admin consent required for all app graph perms
3402+
else:
3403+
print_red(f"[-] Role information not found for App Role ID: {app_role_id}")
3404+
print_green(f"[+] Resource: {assignment.get('resourceDisplayName', 'N/A')}")
3405+
print("---")
3406+
else:
3407+
print_red("[-] No role assignments")
3408+
print("=" * 80)
3409+
32903410
# find-updatablegroups
32913411
elif args.command and args.command.lower() == "find-updatablegroups":
32923412
print_yellow("\n[*] Find-UpdatableGroups")

0 commit comments

Comments
 (0)